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


The Battle for the Solar System (Complete)

The Pandoran war machine ravaged the galaxy, driving the human race to the brink of destruction. Seven men and women stood in its way. This is their story.

Click here to learn more and read an extract!

« Back to tutorial listing

— Mission-based 2D shoot 'em up —
Part 19: Intermission: Stats

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

Introduction

The next section we're going to look into is our statistics display. This is really just for fun, for the curious types that like to know how much of everything they have been doing during the game. In this part, we'll be listing the stats on their own screen.

Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./shooter3-19 to run the code. You will see a window open like the one above. Use the mouse to control the cursor. Aside from watching the time played counter increase, there is not much more to say about this screen. Once you're finished, close the window to exit.

Inspecting the code

Okay, so this is a stats screen. It displays stats. Not much more to say, so let's jump into the code and see what we've done to achieve this.

Starting with structs.h:


typedef struct
{
	// snipped
	struct
	{
		int    shotsFired;
		int    shotsHit;
		int    catnipCollected;
		int    catnipSpent;
		int    ammoUsed;
		int    enemiesDestroyed;
		int    damageTaken;
		int    missionsStarted;
		int    missionsCompleted;
		int    powerupsCollected;
		double timePlayed;
	} stats;
} Game;

To our Game struct, we've added an inner struct called `stats`, that will hold all the values we are interested it. The names (in camel case) should already describe which statistic the variable represents, so we'll just move right on.

stats.c is the new compilation unit that we've created to handle our statistics viewing. It's a very simple file, as we'll see. We'll start by looking at initStats:


void initStats(void)
{
	timePlayedTimer = 0;

	accuracy = 0;

	if (game.stats.shotsFired > 0)
	{
		accuracy = ((1.0 * game.stats.shotsHit) / game.stats.shotsFired) * 100;
	}
}

This first zeros a variable called timePlayedTimer (static in stats.c). This timer is used to control how often we update the text that represents the Time Played display. Since we're only resolving the time played down to the second, we don't want to be generating the string all the time. Next, we're zeroing a variable call `accuracy` (also a static variable in stats.c), before testing if any shots have been fired (shotsFired), and then calculating the player's accuracy by dividing by shotsHit, and converting a percentage. We do it here so we don't keep calculating it each time it needs to be drawn to the screen.

Next up is doStats:


void doStats(void)
{
	int s, m, h;

	timePlayedTimer -= app.deltaTime;

	if (timePlayedTimer <= 0)
	{
		s = game.stats.timePlayed / FPS;
		m = s / 60;
		h = m / 60;

		m %= 60;
		s %= 60;

		sprintf(timePlayed, "Time played: %02d:%02d:%02d", h, m, s);

		timePlayedTimer = FPS;
	}
}

This is the main logic processing function of the section. Here we're decreasing the value of timePlayedTimer, and then, if it's 0 or less, we're using sprintf to generate our "Time played" string. We're producing seconds, minutes, and hours(!) as the time here.

That's all there is to the logic, so we can move onto the drawing. drawStats is where we render the data for this section:


void drawStats(void)
{
	int y;

	y = 75;

	app.fontScale = 1.5;

	drawStat("Shots fired", game.stats.shotsFired, 0, &y);
	drawStat("Accuracy", accuracy, 1, &y);
	drawStat("Catnip collected", game.stats.catnipCollected, 0, &y);
	drawStat("Catnip spent", game.stats.catnipSpent, 0, &y);
	drawStat("Ammo used", game.stats.ammoUsed, 0, &y);
	drawStat("Enemies destroyed", game.stats.enemiesDestroyed, 0, &y);
	drawStat("Damage taken", game.stats.damageTaken, 0, &y);
	drawStat("Missions started", game.stats.missionsStarted, 0, &y);
	drawStat("Missions completed", game.stats.missionsCompleted, 0, &y);
	drawStat("Powerups collected", game.stats.powerupsCollected, 0, &y);

	drawText(timePlayed, SCREEN_WIDTH / 2, SCREEN_HEIGHT - 225, 255, 255, 255, TEXT_ALIGN_CENTER, 0);

	app.fontScale = 1;
}

All this function does is renders each stat to the screen, with the help of a function named drawStat. We're using this function to help with alignment and vertical spacing of our stats. Otherwise, there isn't much else to say about this function..! Let's look at drawStat, to see how it works:


static void drawStat(char *name, int value, int isPerecent, int *y)
{
	char text[16];

	if (isPerecent)
	{
		sprintf(text, "%d%%", value);
	}
	else
	{
		sprintf(text, "%d", value);
	}

	drawText(name, (SCREEN_WIDTH / 2) - STAT_X_SPACING, *y, 255, 255, 255, TEXT_ALIGN_RIGHT, 0);
	drawText(text, (SCREEN_WIDTH / 2) + STAT_X_SPACING, *y, 255, 255, 255, TEXT_ALIGN_LEFT, 0);

	*y += STAT_Y_SPACING;
}

Again, pretty simple. The function takes the stat `name`, `value`, a variable called isPercent to control whether we want to render the output as a percentage (for formatting purposes, such as with accuracy), and also a pointer to our vertical position on screen (`y`). We'll then test if we want to format the output text to include a percent (%), before then drawing the name and the value, aligned around the middle of the screen (with padding determined by STAT_X_SPACING). We'll then increase the value of `y` by the value of STAT_Y_SPACING.

Again, the function exists to help with the layout and keep things consistent (and our code neat).

That's it for the stats page, so let's move onto incorporating it into the main intermission screen. So, over to intermission.c we go. Starting with initIntermission:


void initIntermission(void)
{
	// snipped

	initShop();

	initStats();

	// snipped

	section = IS_STATS;

	starfieldTimer = 0;

	app.delegate.logic = logic;
	app.delegate.draw = draw;
	app.mouse.showCursor = 1;
}

We've added in the call to initStats. Next, we update `logic`:


static void logic(void)
{
	// snipped

	switch (section)
	{
		case IS_PLANETS:
			doPlanets();
			break;

		case IS_COMMS:
			doComms();
			break;

		case IS_SHOP:
			doShop();
			break;

		case IS_STATS:
			doStats();
			break;

		default:
			break;
	}
}

We've added the IS_STATS case, to call doStats. We also update `draw`:


static void draw(void)
{
	drawBackground(background);

	drawStarfield();

	switch (section)
	{
		case IS_PLANETS:
			drawPlanets();
			break;

		case IS_COMMS:
			drawComms();
			break;

		case IS_SHOP:
			drawShop();
			break;

		case IS_STATS:
			drawStats();
			break;

		default:
			break;
	}

	drawSectionIcons();
}

A similar IS_STATS statement has been added, to call drawStats:

That's it for the screen in our intermission. All that needs adding are the stats updates themselves. We've sprinkled these throughout the code in various places, where appropriate to the stat itself. We did a similar thing in the Medals tutorial (#2, #3, #4), so we won't repeat ourselves here (examples can be found in bullets.c, fighters.c, and collectables.c).

That's it for this screen. In summary, this is just a simple stats display screen, with the stats themselves being increased by various functions and events during gameplay. Ultimately, this isn't very impactful, but something that's nice to see.

Something far more impactful is the ability for us to load and save our game. While our game isn't long, it would still be nice to save and reload our progress at a time of our choosing. In the next part, we're going to look at introducing the save system.

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