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


Alysha

When her village is attacked and her friends and family are taken away to be sold as slaves, Alysha Tanner sets out on a quest across the world to track them down and return them home. Along the way, she is aided by the most unlikely of allies - the world's last remaining dragon.

Click here to learn more and read an extract!

« Back to tutorial listing

— 2D Shoot 'Em Up Tutorial —
Part 11: Bitmap font rendering

Introduction

Note: this tutorial builds upon the ones that came before it. If you aren't familiar with the previous tutorials in this series you should read those first.

We can shoot the enemies to destroy them, but right now they just vanish. That's no fun. How about we make them explode, instead? Extract the archive, run cmake CMakeLists.txt, followed by make to build. Once compiling is finished type ./shooter11 to run the code.

A 1280 x 720 window will open, with a colorful background. A spaceship sprite will also be shown, as in the screenshot above. The ship can now be moved using the arrow keys. Up, down, left, and right will move the ship in the respective directions. You can also fire by holding down the left control key. Enemies (basically red versions of the player's ship) will spawn from the right and move to the left. Shoot enemies to destroy them. Enemies can fire back, so you should avoid their shots. Score points by destroying the enemy ships. Close the window by clicking on the window's close button.

Inspecting the code

We're going to be adding support for drawing text on screen. We're going to keep things simple for this tutorial and work with a bitmap font, extracting the glyphs from a texture. We'll also only be supporting a limited ASCII range - numbers, uppercase letters, and some symbols. The glyphs themselves are fixed width, so we can hardcode the width and height for the glyphs. This is a simple, quick solution and will suffice for now. Let's start with looking at defs.h:


#define MAX_LINE_LENGTH 1024

We've added a new define to specify the maximum length of a character string. This will come into play when we're building our string to draw onscreen. Our buffer will be 1024 characters, plenty of room. Next, structs.h:


typedef struct {
	...
	int score;
} Stage;

To Stage, we've added a variable to hold our score. Nothing special. Now onto the actual font and text handling code. For this, we've created a new compilation unit called text.c. There are just two function within - initFonts and drawText. First, initFonts:


void initFonts(void)
{
	fontTexture = loadTexture("gfx/font.png");
}

Here, we're calling our loadTexture function to grab the font texture we want to work with. Nothing we've not seen before. The drawText function is a little more complex, but should be quite easy to get your head around:


void drawText(int x, int y, int r, int g, int b, char *format, ...)
{
	int i, len, c;
	SDL_Rect rect;
	va_list args;

	memset(&drawTextBuffer, '\0', sizeof(drawTextBuffer));

	va_start(args, format);
	vsprintf(drawTextBuffer, format, args);
	va_end(args);

	len = strlen(drawTextBuffer);

	rect.w = GLYPH_WIDTH;
	rect.h = GLYPH_HEIGHT;
	rect.y = 0;

	SDL_SetTextureColorMod(fontTexture, r, g, b);

	for (i = 0 ; i < len ; i++)
	{
		c = drawTextBuffer[i];

		if (c >= ' ' && c <= 'Z')
		{
			rect.x = (c - ' ') * GLYPH_WIDTH;

			blitRect(fontTexture, &rect, x, y);

			x += GLYPH_WIDTH;
		}
	}
}

There's a lot going on here, so we'll consider the parameters first. This function takes a minimum of 6 arguments: The x and y position that we want draw our text at; the colour of the text, in r, g, b; and the text itself, as format. You'll notice this function supports varargs. This is so that we can use text formatting (we'll see this in action later).

As our text glyphs exist on a single texture, we'll want to extract them for drawing. As we saw when we were doing the debris for the ships, we set up an SDL_Rect to specify the region of the texture to use. We'll be doing the same thing here. The first thing we're doing in our drawText function is clearing our drawTextBuffer (a static variable in text.c). After this, we're using C's varargs functionality to format our string. We're grabbing the length of the resulting string and then setting up our SDL_Rect's w and h values using our defined GLYPH_WIDTH and GLYPH_HEIGHT values that exist in text.h. Next, we're setting the colour that we want to draw our text with, by calling SDL_SetTextureColorMod.

We then create a for loop and step through each of the characters in our string. As mentioned earlier, we are only supporting certain ASCII characters. If the character we encounter falls outside of this range, we don't draw it. For a valid character, we setup our rect's x and y. We calculate the x by subtracting the int value of space (' '). This is because a space is the first character in our font texture and therefore has an x position of 0. This basically aligns our glyph coordinates on the x axis. As our glyphs are all on one line, we don't have to do anything with y other than set it to 0. With our rect calculated, we call our blitRect function and increment x by GLYPH_WIDTH so that the letters don't all render over the top of one another.

Phew! It seems complicated, but when you think about it, it actually makes good sense. Now let's look at stage.c, where we've put this all to work:


static void resetStage(void)
{
	...
	stage.score = 0;

In resetStage, we're doing little more than resetting stage.score back to 0. We've also updated our draw function:


static void draw(void)
{
	...
	drawHud();

We're calling a new function called drawHud (which we're calling last, so that the hud info is drawn over everything else). drawHud is where we actually call our drawText function:


static void drawHud(void)
{
	drawText(10, 10, 255, 255, 255, "SCORE: %03d", stage.score);

	if (stage.score > 0 && stage.score == highscore)
	{
		drawText(960, 10, 0, 255, 0, "HIGH SCORE: %03d", highscore);
	}
	else
	{
		drawText(960, 10, 255, 255, 255, "HIGH SCORE: %03d", highscore);
	}
}

We want to draw two string here: one showing the player's current score and the other the highscore. In both cases we're making use of drawText's varargs. You can see that it works rather like printf statements, where we're specifying the format of the number. The first drawText function draws the player's score in white, on the left-hand side of the screen. The second call draws the highscore. To demonstrate the colour function, we're checking if the player has the current highscore and, if so, we're drawing the text in green. Otherwise, it's drawn in white.

Showing the score wouldn't be any good if we never incremented it. We'll update the bulletHitFighter function to award the player a point any time they kill an enemy:


static int bulletHitFighter(Entity *b)
{
	...
	playSound(SND_ALIEN_DIE, CH_ANY);

	stage.score++;

	highscore = MAX(stage.score, highscore);

We'll also set the highscore to the player's score if it's greater (using the MAX macro). Finally, we need to update main.c to initialize our text drawing functions:


int main(int argc, char *argv[])
{
	...
	initFonts();

We need only call initFonts to get our font rendering working.

Hurrah! We now have some text drawing capabilities (if somewhat limited). This will now allow us to create things like a highscore table, a title screen, and other pieces of information that could rely on showing text. Our game is currently quite easy, though: you'll probably rack up quite a score playing the game. What we'll do in the next tutorial is add points pods that the player will need to collect in order to up their score.

Exercises

  • Add some other pieces of information to the HUD.
  • Try centre and right aligning the text.
  • Experiment with scaling the font (note: due to the nature of scaling text it might not read all that well).

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