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

— A simple turn-based strategy game —
Part 11: Handling player death

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

Introduction

The final core aspect of our combat is to handle the player units' death. Our ghost can currently hurt them, but not kill them. We'll be changing that in this part.

Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./sdl2TBS11 to run the code. You will see a window open like the one above, showing three wizards in a small room, surrounded by walls, as well as a white ghost. Play the game as normal. Allow one of your wizards to be killed. Notice how they leave behind a little tombstone where they died, and the wizard is no longer selectable; they've been removed from the game completely. If all your wizards die, the ghost will walk around by itself. Once you're finished, close the window to exit.

Inspecting the code

Adding in the player death handling is a very simple process, as we'll see.

Heading first, as always, to defs.h:


enum {
	ET_WORLD,
	ET_MAGE,
	ET_GHOST,
	ET_TOMBSTONE
};

We've added in ET_TOMBSTONE, to represent the tombstone that is put in place when the player dies.

Next, let's take a look at objects.c, a new compilation unit we've created. This contains all the code to handle the tombstone. We're calling the file objects.c in case we wish to add more decoration, objects, and such in the future. Starting with initTombstone:


void initTombstone(Entity *e)
{
	e->type = ET_TOMBSTONE;
	e->solid = 1;
	e->texture = getAtlasImage("gfx/units/tombstone.png", 1);

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

Not a lot to say about this function. We're setting the passed-in entity's type to ET_TOMBSTONE, marking it as `solid`, and setting its `texture`, `tick`, and `draw` fields.

The `tick` function is nothing special:


static void tick(Entity *self)
{
}

An empty function. Our doEntities function expects this to exist and be set for every entity, so we're assigning it (we could have always had doEntities test for NULL before calling, so I suppose this is just a matter of style).

The `draw` function comes next:


static void draw(Entity *self)
{
	int x, y;

	x = MAP_TO_SCREEN(self->x);
	y = MAP_TO_SCREEN(self->y);

	blitAtlasImage(self->texture, x, y, 1, SDL_FLIP_NONE);
}

We're just rendering the tombstone the same way as other entities. Nothing special.

Now, let's move over to mage.c, to see how we're using our tombstone. We've updated the `die` function:


static void die(Entity *self)
{
	Entity *tombstone;
	char name[MAX_NAME_LENGTH * 2];

	self->dead = 1;

	addDeathEffect(MAP_TO_SCREEN(self->x), MAP_TO_SCREEN(self->y));

	tombstone = initEntity("Tombstone");

	sprintf(name, "%s (RIP)", self->name);

	STRCPY(tombstone->name, name);
	tombstone->x = self->x;
	tombstone->y = self->y;
}

Before, this function was completely empty. Now, we're doing several things. We're setting the mage's entity's `dead` flag to 1, and calling addDeathEffect, passing in the mage's screen coordinates (so, much like what happens when a ghost is killed). After that, we're calling initEntity, and passing over "Tombstone" to have our entity factory generate us a tombstone. We're assigning this to a variable called `tombstone`. We're then setting the name of the tombstone entity to be the name of the mage who died, plus "(RIP)", and the `x` and `y` positions to be those of the dead mage. Right now, there's no way for us to see data about the tombstone, but we'll fix that when it comes to updating the HUD.

Pretty simple so far, right? Once the mage's `dead` flag is set, they will be removed from the game during the doEntities loop, so we need do nothing more but put the tombstone in the spot where they stood.

Two more things to change and we're done! Important ones, though. Let's jump over to player.c, where we've updated cyclePlayerUnits:


static void cyclePlayerUnits(int dir)
{
	int i;

	for (i = 0 ; i < NUM_PLAYER_UNITS ; i++)
	{
		if (units[i] == stage.currentEntity)
		{
			do
			{
				i += dir;

				if (i < 0)
				{
					i = NUM_PLAYER_UNITS - 1;
				}
				else if (i >= NUM_PLAYER_UNITS)
				{
					i = 0;
				}

				stage.currentEntity = units[i];
			}
			while (stage.currentEntity->dead);

			updateUnitRanges();

			return;
		}
	}
}

This function is currently controlled by the mouse wheel, and allows us to loop through all our mages without having to click on them. Since they live in an entity pointer array, they won't be removed when the `dead` flag is set. However, we can still test this flag to see if they are a valid selection.

To handle this, we're using a do-loop to keep cycling through our mages until we find one whose `dead` flag isn't set. We enter this loop after locating the currently active mage (in effect, we've added in 4 lines). This check is important to ensure we don't select a dead mage, and then try and control them.

One final change to make and we're done! Heading now to stage.c, we've updated endTurn:


void endTurn(void)
{
	do
	{
		stage.turn = !stage.turn;

		stage.showRange = SHOW_RANGE_NONE;

		resetUnits();
	}
	while (stage.currentEntity == NULL);
}

Another do-loop! This one is again important. After calling resetUnits, we're checking if Stage's currentEntity is set. If so, we know that at least one unit for whose turn it is is alive. If they aren't, we'll swap turns again. What this means is that if all the ghosts are dead, the AI won't get a turn. Equally, if all the mages are dead, the player won't get a turn. This is actually just a temporary change until we put in better victory and defeat logic in. This is good right now, as it will stop our game from crashing if there are no wizards left for the player to control, always passing the turn back to the AI.

Another part done, and another important aspect of us game handled. Our mages can now be killed and the game can effectively end for the player. What we should do next is add some items to our game, for the player to collect. It's not uncommon to have items such as ammo pickups and health kits littering the battlefield, so in our next part we'll look into item handling.

Purchase

The source code for all parts of this tutorial (including assets) is available for purchase:

From itch.io

It is also available as part of the SDL2 tutorial bundle:

Mobile site