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


The Honour of the Knights (First Edition) (The Battle for the Solar System)

When starfighter pilot Simon Dodds is enrolled in a top secret military project, he and his wingmates begin to suspect that there is a lot more to the theft of a legendary battleship and an Imperial nation's civil war than either the Confederation Stellar Navy or the government are willing to let on.

Click here to learn more and read an extract!

« Back to tutorial listing

— 2D Top-down shooter tutorial —
Part 2: Angles and rotation

Introduction

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

This first tutorial will explain how to read the mouse in SDL2. Extract the archive, run cmake CMakeLists.txt, followed by make to build. Once compiling is finished type ./bad02 to run the code.

A 1280 x 720 window will open, with a dark grey background. A targetter will be shown that will track the mouse movements. The main character, Donk, will also display. Donk can be moved with the WSAD control scheme[1] and will always face the targetter. Close the window by clicking on the window's close button.

Inspecting the code

For drawing our titular protangonist, Donk, we need to be able to rotate his texture. Luckily, SDL supplies functions for doing this. Before we get there, we need to do some setup. We'll start with what's new in defs.h:


...
#define PI 3.14159265358979323846

#define PLAYER_SPEED 6

We've defined a value of PI, as well as the player's speed. This will control how fast Donk will move when we press the WASD keys. Next, we've added an Entity struct to structs.h:


struct Entity {
	float x;
	float y;
	int w;
	int h;
	float dx;
	float dy;
	int health;
	int angle;
	SDL_Texture *texture;
	Entity *next;
};

typedef struct {
	Entity entityHead, *entityTail;
} Stage;

As you can see, we've also introduced a Stage struct to hold a linked list of entities. Like in the 2D shoot 'em up, the Stage object will be used to hold all the crucial information about the game in progress.

We've added a new file called util.c. This file contains just one function - getAngle:


float getAngle(int x1, int y1, int x2, int y2)
{
	float angle = -90 + atan2(y1 - y2, x1 - x2) * (180 / PI);
	return angle >= 0 ? angle : 360 + angle;
}

getAngle will do just as its name suggests: it will return the angle between two points. The function makes use of our PI define, as you can see. There are many, many functions for calculating the angle between two points, so we'll not go into any sort of explanation (such things are beyond the scope of this article).

Now let's look at draw.c, where we've added a new function called blitRotated:


void blitRotated(SDL_Texture *texture, int x, int y, float angle)
{
	SDL_Rect dstRect;

	dstRect.x = x;
	dstRect.y = y;
	SDL_QueryTexture(texture, NULL, NULL, &dstRect.w, &dstRect.h);
	dstRect.x -= (dstRect.w / 2);
	dstRect.y -= (dstRect.h / 2);

	SDL_RenderCopyEx(app.renderer, texture, NULL, &dstRect, angle, NULL, SDL_FLIP_NONE);
}

This function takes four arguments: the texture we want to draw, the x and y coordinates that we want to draw it at, and the angle of rotation. To achieve our rotation, we're calling an SDL function called SDL_RenderCopyEx. This function takes several arguments of its own: the renderer, the texture, the source rectangle, the destination rectangle, the angle we want to rotate it at, the center point about which we want to rotate, and a SDL_RendererFlip value to state which type of flipping action we want to use. There's quite a lot to take in here, but in our case things are simple. We want to use our renderer and the texture we supplied, so we pass these two as the first couple of arguments. The next two arguments will use NULL as the source, meaning we want to copy the entire texture over, and the destRect will be use as our destination rect. This rect has been setup to use the x and y coordinates we supplied, and also the width and height of the texture. Note how we're always shifting destRect's x and y by half the width and height of the texture, to center it correctly. The final three arguments of SDL_RenderCopyEx are the angle we've supplied, and NULL and SDL_FLIP_NONE. NULL will mean that SDL will rotate the texture about its center (which is what we want) and SDL_FLIP_NONE will mean that no flipping (horizontal or vertical) will take place. More info on the function can be found on the SDL wiki: https://wiki.libsdl.org/SDL_RenderCopyEx

With that in place, let's look at actually making use of it. We'll start with updating stage.c:


void initStage(void)
{
	...
	initPlayer();
}

The initStage function now calls a new function call initPlayer, where we'll setup Donk (see later). We've also made some additions to logic:


static void logic(void)
{
	doPlayer();

	doEntities();
}

doPlayer and doEntities are external functions that live in player.c and entities.c. We want to call these each frame. Next, draw has also been updated:


static void draw(void)
{
	drawEntities();
	...
}

Of course, we want to draw our entities, so no surprises here. Equally, if we inspect entities.c we find two functions. None of the code in here should come as a shock. We want to process and draw our entities, which is what doEntities and drawEntities will do:


void doEntities(void)
{
	Entity *e;

	for (e = stage.entityHead.next ; e != NULL ; e = e->next)
	{
		e->x += e->dx;
		e->y += e->dy;

		if (e == player)
		{
			e->x = MIN(MAX(e->x, e->w / 2), SCREEN_WIDTH - e->w / 2);
			e->y = MIN(MAX(e->y, e->h / 2), SCREEN_HEIGHT - e->h / 2);
		}
	}
}

doEntities simply adds each entity's dx and dy to their x and y, causing them to move. One thing that we do in doEntities is test if the entity is the player and prevent them from leaving the screen. The calculation on the x and y, using the MIN and MAX macros, will ensure the player can never stray beyond the bounds of the screen dimensions. Next, we come to drawEntities:


static void drawEntities(void)
{
	Entity *e;

	for (e = stage.entityHead.next ; e != NULL ; e = e->next)
	{
		blitRotated(e->texture, e->x, e->y, e->angle);
	}
}

Here we're actually calling our blitRotated function. We're stepping through all the entities and drawing them using their texture and positions. We're also supplying their angle, so that we can rotate them as required.

Finally, we'll take a look at player.c:


void initPlayer(void)
{
	player = malloc(sizeof(Entity));
	memset(player, 0, sizeof(Entity));
	stage.entityTail->next = player;
	stage.entityTail = player;

	player->texture = loadTexture("gfx/donk.png");
	player->health = 5;
	player->x = SCREEN_WIDTH / 2;
	player->y = SCREEN_HEIGHT / 2;

	SDL_QueryTexture(player->texture, NULL, NULL, &player->w, &player->h);
}

initPlayer will create the player, set the texture, health, etc., and add them to the entities linked list in Stage. Again, no surprises. doPlayer is also very simple in nature:


void doPlayer(void)
{
	player->dx *= 0.85;
	player->dy *= 0.85;

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

	if (app.keyboard[SDL_SCANCODE_S])
	{
		player->dy = PLAYER_SPEED;
	}

	if (app.keyboard[SDL_SCANCODE_A])
	{
		player->dx = -PLAYER_SPEED;
	}

	if (app.keyboard[SDL_SCANCODE_D])
	{
		player->dx = PLAYER_SPEED;
	}

	player->angle = getAngle(player->x, player->y, app.mouse.x, app.mouse.y);
}

The player will move when the WSAD keys are used. We're also doing two other things - first, we're slowing the player by multiplying their dx and dy by 0.85 at each call. This will mean that the player will decelerate, making their movement a little smoother. Next, we're setting the player's angle by calling our getAngle function, passing over the player's coordinates and the mouse's. In effect Donk will also face the mouse cursor.

We've made some big steps in this tutorial. Next, we'll look at accessing the mouse's buttons (including use of the scroll wheel) and firing weapons so that Donk can protect himself.

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:

Footnotes

[1] - WASD controls are up, left, down, right.

Mobile site