PC Games

Orb
Lasagne Monsters
Three Guys Apocalypse
Water Closet
Blob Wars : Attrition
The Legend of Edgar
TBFTSS: The Pandoran War
Three Guys
Blob Wars : Blob and Conquer
Blob Wars : Metal Blob Solid
Project: Starfighter
TANX Squadron

Android Games

DDDDD
Number Blocks
Match 3 Warriors

Tutorials

2D shoot 'em up
2D top-down shooter
2D platform game
Sprite atlas tutorial
Working with TTF fonts
2D adventure game
Widget tutorial
2D shoot 'em up sequel
2D run and gun
Roguelike
Medals (Achievements)
2D turn-based strategy game
2D isometric game
2D map editor
2D mission-based shoot 'em up
2D Santa game
2D split screen game
SDL 1 tutorials (outdated)

Latest Updates

SDL2 Versus game tutorial
Wed, 20th March 2024

Download keys for SDL2 tutorials on itch.io
Sat, 16th March 2024

The Legend of Edgar 1.37
Mon, 1st January 2024

SDL2 Santa game tutorial 🎅
Thu, 23rd November 2023

SDL2 Shooter 3 tutorial
Wed, 15th February 2023

All Updates »

Tags

android (3)
battle-for-the-solar-system (10)
blob-wars (10)
brexit (1)
code (6)
edgar (9)
games (43)
lasagne-monsters (1)
making-of (5)
match3 (1)
numberblocksonline (1)
orb (2)
site (1)
tanx (4)
three-guys (3)
three-guys-apocalypse (3)
tutorials (17)
water-closet (4)

Books


Project Starfighter

In his fight back against the ruthless Wade-Ellen Asset Protection Corporation, pilot Chris Bainfield finds himself teaming up with the most unlikely of allies - a sentient starfighter known as Athena.

Click here to learn more and read an extract!

« Back to tutorial listing

— 2D Santa game —
Part 13: Snow

Note: this tutorial assumes knowledge of C, as well as prior tutorials.

Introduction

Let It Snow! Let It Snow! Let It Snow! the song goes. So, that's what we're going to do. Our game is more or less complete, but is a bit barren to look at. As we observed in the last part, it should at least be snowing. In this part, that's exactly what we're going to add.

Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./santa13 to run the code. Press Space to play. Play the game as normal, while enjoying the fresh snowfall. When you're finished, close the window to exit.

Inspecting the code

Implementing our snow is an extremely simple thing. Our snowflakes are simply squares that move randomly around.

Let's start first by taking a look at structs.h:


typedef struct
{
	double x, y;
	double dx, dy;
	double thinkTime;
	int    size;
} Snow;

We've added in a Snow struct, to represent our snokeflakes. `x` and `y` are the positions on screen, `dx` and `dy` are the movement deltas, thinkTime is how long the snowflake will continue at its current velocities before changing direction, and `size` is the size of the snakeflake we want to render. Easy enough.

Over now to snow.c, which contains the bulk of the updates in this part. This file contains just a handful of functions, and is very easy to understand. Starting first with initSnow:


void initSnow(void)
{
	Snow *s;
	int   i;

	for (i = 0; i < MAX_SNOW; i++)
	{
		s = &snow[i];

		memset(s, 0, sizeof(Snow));
		s->x = rand() % SCREEN_WIDTH;
		s->y = rand() % SCREEN_HEIGHT;
		s->dx = 1.0 * (rand() % 10 - rand() % 10) * 0.25;
		s->dy = 1.0 * (10 + rand() % 20) * 0.1;
		s->thinkTime = rand() % (int)FPS;
		s->size = 1 + rand() % 3;
	}
}

We're looping through all the snow in our snow array (static within snow.c), and setting up each item. Each snow object is randomly positioned on screen (`x` and `y`), and given a random `dx` velocity between -2.25 and 2.25. A random `dy` is also assigned, between 1.0 and 2.9. A random thinkTime is assigned, of just below 1 second (FPS), and the `size` is randomly set between 1 and 3.

So, we end up with 500 snow objects (MAX_SNOW) that are placed randomly on the screen, and can move left, right, and down. Note that we don't set a negative `dy`, as we don't want our snow to ever move up the screen. Now let's look at the doSnow function, which will drive the logic of our snow:


void doSnow(void)
{
	Snow *s;
	int   i;

	for (i = 0; i < MAX_SNOW; i++)
	{
		s = &snow[i];

		s->x += s->dx * app.deltaTime;

		if (s->x <= -s->size)
		{
			s->x = SCREEN_WIDTH;
		}

		if (s->x >= SCREEN_WIDTH)
		{
			s->x = 0;
		}

		s->y += s->dy * app.deltaTime;

		if (s->y >= GROUND_Y)
		{
			s->y = -s->size;
		}

		s->thinkTime -= app.deltaTime;

		if (s->thinkTime <= 0)
		{
			s->dx = 1.0 * (rand() % 10 - rand() % 10) * 0.25;

			s->thinkTime += FPS;
		}
	}
}

Once again, we're looping through all our snow objects, and updating their `x` by their `dx`, to make them move. Next, we're testing if any of them have moved off the left or right hand sides of the screen, and if so teleporting them to the other side. The small size and sheer amount of snowflakes means that we don't notice any "popping" that might occur.

Next, we add the snowflake's `dy` to its `y`, to make it travel down screen. Once it hits the ground (y >= GROUND_Y) we move it back to the top of the screen (taking into account the snowflake's `size`). Once again, we don't notice any popping.

Finally, we reduce the snowflake's thinkTime. Once it hits 0 or less, we update its `dx` value, assigning it a new value between -2.25 and 2.25, as we did to begin with. We then add FPS to the thinkTime. This means that our snowflakes will randomly change direction and velocity once a second. Once more, due to the sheer number of snowflakes falling, this results in a rather pleasant effect. The small `dx` values means direction changes aren't too intense either.

I'm sure you'll agree it looks quite convincing.

Now for our rendering section, drawSnow:


void drawSnow(void)
{
	Snow *s;
	int   i;

	for (i = 0; i < MAX_SNOW; i++)
	{
		s = &snow[i];

		drawRect(s->x, s->y, s->size, s->size, 255, 255, 255, 255);
	}
}

This couldn't get easier. We're looping through all our snow, and calling drawRect for each, using the snow's `x`, `y`, and `size`, to render a white square on screen at its current position.

That's it for snow.c! We need only make some updates to stage.c to setup, drive, and draw our snow, and we'll be all done. Let's start with initStage:


void initStage(void)
{
	// snipped

	initSnow();

	stage.state = SS_DEMO;

	houseSpawnTimer = FPS;

	objectSpawnTimer = FPS * 5 + ((int)FPS * rand() % 5);

	gameOverTimer = FPS * 5;

	app.delegate.logic = doStage;
	app.delegate.draw = drawStage;
}

We've added the call to initSnow. Next up is doStage:


void doStage(void)
{
	// snipped

	if (stage.pauseTimer == 0)
	{
		// snipped

		doEntities();

		doSnow();

		if (stage.xmasSpirit == 0 && stage.player != NULL)
		{
			killPlayer(-1, -1);
		}
	}
}

Here, we're calling doSnow, just after doEntites. Finally, we've updated drawStage:


void drawStage(void)
{
	drawGround();

	drawEntities();

	drawSnow();

	if (stage.state != SS_DEMO)
	{
		drawHUD();
	}

	// snipped
}

Right after drawEntities, we're calling drawSnow.

And that's that. Adding snow to our game couldn't have been easier. SDL2 Santa is now much more pleasant and seasonal to look at. But we could still add more. What about some snowy hills in the background, to further enhance the wintery landscape?

Well, in the penultimate part, we're going to add in some hills that move from right to left, using a small parallax effect. We'll dot some trees around for extra good measure.

Purchase

The source code for all parts of this tutorial (including assets) is available for purchase, as part of the SDL2 tutorials bundle:

From itch.io

Mobile site