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

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 4: Santa's Sleigh

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

Introduction

We've been staring at an non-interactive scene for the past few parts, so it's time for Santa himself to make an appearance. In our game, Santa will be piloting his sleigh solo, and with the need to keep it aloft by using W to gain height.

Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./santa04 to run the code. You will see a window open like the one above, with the scene moving from right to left. To steer Santa's sleigh, using the WASD control scheme, to move up, left, down, and right. Santa will be constrained to an area of the screen, and cannot crash into anything (including the ground). Gravity will continue to drag the sleigh down. When you're finished, close the window to exit.

Inspecting the code

So, what's going on? Why are there no reindeer? Well, our story is that it's Christmas Eve and sadly all 9 of Santa's reindeer are laid up in bed with a stomach bug, after eating some dodgy sprouts. It's therefore up to Santa alone to save Christmas. His sleigh is still magic, but can't quite sustain itself fully without all of his reliable caribou buddies. Not only that, but in his haste to get things going, Santa has departed the North Pole without a full stock of gifts and coal. Not to worry, his trusty elves have used their magic to teleport the toys and rocks to him. Unfortunately, the elves' magic isn't without its problems, and so it's also brought a bunch of snowmen to life, who are now focused on ruining the night for everyone.

Hey, it's a good enough explaination, right? Right? Anyway, moving on ...

As we can see once more, this part is short and sweet, and quite easy to understand. The base code that we've built up over this series of tutorials enables us to get going quite quickly, and our established code patterns make swift work of adding in new things.

Starting first with defs.h:


enum
{
	ET_NONE,
	ET_HOUSE,
	ET_CHIMNEY,
	ET_PLAYER
};

We now have an ET_PLAYER entity enum.

Over to structs.h:


typedef struct
{
	double  speed;
	Entity  entityHead, *entityTail;
	Entity *player;
} Stage;

We've added a pointer to the player in Stage. While this isn't being used right away, it will become useful later on when we need to find out things about the player's entity.

Now over to player.c, a new compilation unit that will handle all the player's logic and rendering. One shouldn't expect anything tricky at this point. Starting with initPlayer:


void initPlayer(void)
{
	Entity *e;

	if (sleighTexture == NULL)
	{
		loadTextures();
	}

	e = spawnEntity();
	e->type = ET_PLAYER;
	e->x = 400;
	e->y = 200;
	e->dy = -8;
	e->texture = sleighTexture;

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

	stage.player = e;
}

Other than loading our required textures and setting the essentials, there's not much to be said about the creation of the player Entity. Two things of note are that the player's `dy` is set to -8, so that the seligh doesn't immediately fall from the sky when the game starts. Also of note is that we're setting Stage's `player` entity pointer to the entity we've created here (`e`).

Moving over to the `tick` function next:


static void tick(Entity *self)
{
	move(self);
}

Here, we're doing just one thing, calling a function named `move`:


static void move(Entity *self)
{
	double dx;

	dx = 0;

	self->dy += 0.2 * app.deltaTime;

	if (app.keyboard[SDL_SCANCODE_W])
	{
		self->dy -= app.deltaTime;
	}

	if (app.keyboard[SDL_SCANCODE_S])
	{
		self->dy += app.deltaTime;
	}

	if (app.keyboard[SDL_SCANCODE_A])
	{
		dx = -4;
	}

	if (app.keyboard[SDL_SCANCODE_D])
	{
		dx = 4;
	}

	if (dx != 0)
	{
		self->dx += dx * app.deltaTime;
	}
	else
	{
		self->dx = 0;
	}

	self->dx = MAX(MIN(self->dx, 5), -5);
	self->dy = MAX(MIN(self->dy, 5), -5);

	self->x = MIN(MAX(10, self->x + self->dx * app.deltaTime), SCREEN_WIDTH - self->texture->rect.w - 10);

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

	if (self->y < 50)
	{
		self->y = 50;
		self->dy = 0;
	}
	else if (self->y + self->texture->rect.h >= GROUND_Y)
	{
		self->y = GROUND_Y - self->texture->rect.h;
		self->dy = 0;
	}
}

The `move` function is concerned with moving the sleigh, as well as constraining it to an area of the screen. We start by increasing the value of the sleigh entity's (`self`) `dy`, as a means of applying gravity to the sleigh, and then set a control variable called `dx` to 0. Next, we test if any of the keys in the WASD control scheme have been pressed. If so, we'll move the sleigh as appropriate. Pressing A or D will move the sleigh left and right (by setting the value of `dx`), while pressing W or S will adjust the value of the entity's `dy`.

For both the sleigh's `dx` and `dy`, we're accelerating on the horizontal and vertical. Notice how if we don't press A or D that the sleigh comes to a complete stop. We determine this by testing the control variable `dx`, to see if there was any movment to be applied. We limit the values of the sleigh's `dx` and `dy` values to between -5 and 5, before adding them to the sleigh's `x` and `y`, to stop the sleigh from moving too fast.

To constrain the position of the sleigh, we take a couple of different approaches. For the horizontal, we limit its `x` value to 10 pixels on the left and right of the screen. The vertical is handled differently, depending on whether we are at the top or bottom of the screen. If we reach the top of the screen, we limit the sleigh to 50 pixels on the vertical and also set its `dy` to 0. This means that the sleigh will immediately fall if we release W. For the bottom, we don't allow the sleigh to be lower down than its runners on the ground (so, the position of the ground, less the height of the sleigh's texture). Again, we set `dy` to 0, so that the sleigh can lift off again immediately, without having to fully reverse the value of `dy`.

So, in summary, we're using the WASD control scheme to steer the sleigh, with gravity being applied each frame.

Notice how the player entity isn't affected by the speed of the stage. For all intents and purposes, the player is static on the screen, while everything else moves around it. If we applied the stage's speed to the player, they would move off screen like everything else, and we don't want that.

On to the `draw` function next:


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

Nothing special going on here. We're just rendering the sleigh, using its texture.

Finally, the loadTexture function:


static void loadTextures(void)
{
	sleighTexture = getAtlasImage("gfx/sleigh.png", 1);
}

We're loading just one texture right now - sleighTexture. We'll be loading more the future, and this function will serve us well when we come to do so.

That's all there is to player.c for now. We only need to do one more thing to enable our player, so it's over to stage.c, where we're updating initStage:


void initStage(void)
{
	// snipped

	initPlayer();

	stage.speed = INITIAL_GROUND_SPEED;

	houseSpawnTimer = SCREEN_WIDTH / 8;

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

Adding in initPlayer here will cause our player entity to be created, allowing the sleigh to be controlled and drawn on our stage.

And that's it. We now have the houses, chimneys, and Santa's sleigh. There are a number of other things that still need to be added, but we're making excellent progress. What we should add in next is the ability to deploy gifts and coal. This is Santa's job, after all. So, in the next part we'll introduce these two things.

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