« Back to tutorial listing

— 2D Santa game —
Part 3: Chimneys

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

Introduction

We have our ground and our houses, but we're still missing our chimneys. Ultimately, the chimneys will be the part of the house the player must hit with the gifts and coal to score points. In this part, we're going to implment our chimneys, randomising their positions across the roof, so that they don't become monotonous.

Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./santa03 to run the code. You will see a window open like the one above, with the ground moving from right to left. Houses will appear at random intervals moving from the right side of the screen to the left. Chimneys have now been added to each houses, at varying positions on the roofs. When you're finished, close the window to exit.

Inspecting the code

As you can not doubt already tell, adding in our chimneys is a simple task. They will act as separate entities, so that we can drive their logic and rendering independently from the house itself. Let's jump in to the code and checkout the changes.

Starting with defs.h:


enum
{
	ET_NONE,
	ET_HOUSE,
	ET_CHIMNEY
};

We've added in a new enum entry - ET_CHIMNEY - as the type identity for our chimney.

Next, we've tweaked structs.h:


typedef struct
{
	int naughty;
} Chimney;

Our Chimney struct is much like the House struct right now, with just one field (`naughty`) to mark it as being a Naughty chimney.

Now on to chimney.c, the compilation unit for our chimney. As we're starting out, this is a simple file with not too much going on. Starting first with initChimney:


Entity *initChimney(int naughty)
{
	Entity  *e;
	Chimney *c;

	if (chimneyTextures[0] == NULL)
	{
		loadTextures();
	}

	c = malloc(sizeof(Chimney));
	memset(c, 0, sizeof(Chimney));
	c->naughty = naughty;

	e = spawnEntity();
	e->type = ET_CHIMNEY;
	e->texture = chimneyTextures[rand() % NUM_CHIMNEY_TEXTURES];
	e->tick = tick;
	e->draw = draw;

	e->data = c;

	return e;
}

We're just setting up our chimney here - creating the Chimney data, setting the `type`, a random `texture`, and assigning the `tick` and `draw` functions. All standard stuff. Two things of note. First, we're passing in a naughty flag, that we'll use to denote the chimney as being naughty or nice (by setting the chimney's `naughty` field). Next, we're returning the entity we created (`e`). This is so that we can tweak it in the initHouse function where we'll be creating the chimney to begin with. We'll see this shortly.

Let's look at the `tick` function next.


static void tick(Entity *self)
{
	self->x -= stage.speed * app.deltaTime;

	self->dead = self->x < -self->texture->rect.w;
}

Much like the house's `tick`, the chimney moves from right to left, according to the speed of the stage, and is removed from the game when it completely leaves the left side of the screen.

The `draw` function follows, and is nothing special right now:


static void draw(Entity *self)
{
	blitAtlasImage(self->texture, self->x, self->y, 0, SDL_FLIP_NONE);
}

We're just rendering the chimney, using its `texture`.

The loadTextures function also follows the same setup as loading our ground (in stage.c) and house textures:


static void loadTextures(void)
{
	int  i;
	char filename[MAX_NAME_LENGTH];

	for (i = 0; i < NUM_CHIMNEY_TEXTURES; i++)
	{
		sprintf(filename, "gfx/chimney%02d.png", i + 1);
		chimneyTextures[i] = getAtlasImage(filename, 1);
	}
}

A loop and a formatted string to load the textures named gfx/chimney01.png, gfx/chimney02.png, etc is all that's needed here, the results being set into the relevant index of the chimneyTextures AtlasImage array.

Now over to house.c, where we've made some changes to the initHouse function:


void initHouse(void)
{
	Entity *e, *chimney;
	House  *h;
	int     x, y;

	// snipped

	if (canAddEntity(x, y, houseTextures[0]->rect.w, houseTextures[0]->rect.h))
	{
		h = malloc(sizeof(House));
		memset(h, 0, sizeof(House));
		h->naughty = rand() % 2;

		chimney = initChimney(h->naughty);

		e = spawnEntity();
		e->type = ET_HOUSE;
		e->x = x;
		e->y = y;
		e->texture = houseTextures[rand() % NUM_HOUSE_TEXTURES];
		e->data = h;

		e->tick = tick;
		e->draw = draw;

		chimney->x = x + ((e->texture->rect.w - chimney->texture->rect.w) / 2);
		chimney->y = e->y - 30;

		x = rand() % 70 - rand() % 70;
		chimney->x += x;
		chimney->y += abs(x * 0.75);
	}
}

As previous stated, we're creating the chimney in the initHouse function. There are three things here are that are important to pay attention to. Firstly, we're creating the chimney before the house. This is to ensure our draw order is correct. Our game will be rendering entities in the order they were added to the list, and therefore we want the house to be drawn on top of the chinmey, so that it doesn't look wrong. We could always have sorted the entities prior to drawing, but this issue only affects a small number of items, so we "pre-sort" them here. Note how we're passing over the house's `naughty` field to the initChimney function.

Finally, once we've added our chimney and house, we're setting the chinmey's `x` and `y` position to be a random part of the house's roof. We first set the chimney to reside in the middle of the house, at the apex of the roof. Next, we shift the chimney randomly left or right by 70 pixels (assigning the horizontal shift to a variable called `x`). We then increase the chimney's `y` position by 75% of the value of `x`, so that it mostly follows the slope of the roof, but doesn't embed itself too deeply into the roof itself and becomes difficult to see.

There we have a it, a short simple part to add the chimneys to our houses. Yes, that's really all we need to do for now. Again, creating the chimney independently from the house will allow us to handle it with more precision going forward, rather than inspecting the house itself.

It's about time we introduced Santa himself, to give the player something to do. In the next part, we're going to introduce Santa's sleigh, and also explain a bit about what's going on in our game (such as why there are no reindeer attached!).

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

Desktop site