« Back to tutorial listing

— 2D Shoot 'Em Up Tutorial —
Part 3: Moving the player

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.

In the first tutorial, we learned how to open a window in SDL2. We're now ready to do something a little more interesting: moving the player. Extract the archive, run cmake CMakeLists.txt, followed by make to build. Once compiling is finished type ./shooter03 to run the code.

A 1280 x 720 window will open, coloured a light shade of blue. 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. Close the window by clicking on the window's close button.

Inspecting the code

There have been a number of changes to the code to allow us to move the player about the screen. Let's start with structs.h.


typedef struct {
	SDL_Renderer *renderer;
	SDL_Window *window;
	int up;
	int down;
	int left;
	int right;
} App;

We've added four new integers: up, down, left, and right. These variables will track our movement requests. When we push the arrow keys on the keyboard we'll set the relevant variable value. In order to facilitate this, we need to make a few changes and additions to input.c:


void doInput(void)
{
	SDL_Event event;

	while (SDL_PollEvent(&event))
	{
		switch (event.type)
		{
			case SDL_QUIT:
				exit(0);
				break;

			case SDL_KEYDOWN:
				doKeyDown(&event.key);
				break;

			case SDL_KEYUP:
				doKeyUp(&event.key);
				break;

			default:
				break;
		}
	}
}

The first change is that we've now added two additional case statements to handle new SDL event types - one to handle SDL_KEYDOWN and the other to handle SDL_KEYUP. These two event types will occur whenever a key on the keyboard is pushed down and whenever one is released. The doKeyDown or doKeyUp functions will be called whenever this happens, passing the SDL_KeyboardEvent that triggered this along. Let's look at the doKeyDown function.


void doKeyDown(SDL_KeyboardEvent *event)
{
	if (event->repeat == 0)
	{
		if (event->keysym.scancode == SDL_SCANCODE_UP)
		{
			app.up = 1;
		}

		if (event->keysym.scancode == SDL_SCANCODE_DOWN)
		{
			app.down = 1;
		}

		if (event->keysym.scancode == SDL_SCANCODE_LEFT)
		{
			app.left = 1;
		}

		if (event->keysym.scancode == SDL_SCANCODE_RIGHT)
		{
			app.right = 1;
		}
	}
}

Glacing at this, we can see that we're testing the scancode of the keyboard event to see which key it was (there are A LOT of scan codes available. A full list can be found here: https://wiki.libsdl.org/SDL_Scancode). Whenever we detect an SDL_SCANCODE_UP scancode, we set the app.up variable to 1. app.down, app.left, and app.right will be set as per the SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, and SDL_SCANCODE_RIGHT scancodes. That should be quite easy to understand. One thing we have done is tested to see whether or not the keyboard event that was sent was a result of a keyboard repeat event (event->repeat). In the main, we want to ignore keyboard repeat events; these could queue up and cause some unexpected things to happen, and so we only want to deal with events where the key has been pushed down for the first time. Now let's look at doKeyUp:


void doKeyUp(SDL_KeyboardEvent *event)
{
	if (event->repeat == 0)
	{
		if (event->keysym.scancode == SDL_SCANCODE_UP)
		{
			app.up = 0;
		}

		if (event->keysym.scancode == SDL_SCANCODE_DOWN)
		{
			app.down = 0;
		}

		if (event->keysym.scancode == SDL_SCANCODE_LEFT)
		{
			app.left = 0;
		}

		if (event->keysym.scancode == SDL_SCANCODE_RIGHT)
		{
			app.right = 0;
		}
	}
}

As you can see, it's pretty much the same as doKeyDown, except that we're now setting the app.up, etc. variables to 0. Finally, let's turn to main.c where we'll be dealing with the player movement.


int main(int argc, char *argv[])
{
	memset(&app, 0, sizeof(App));
	memset(&player, 0, sizeof(Entity));

	initSDL();

	atexit(cleanup);

	player.texture = loadTexture("gfx/player.png");
	player.x = 100;
	player.y = 100;

	while (1)
	{
		prepareScene();

		doInput();

		if (app.up)
		{
			player.y -= 4;
		}

		if (app.down)
		{
			player.y += 4;
		}

		if (app.left)
		{
			player.x -= 4;
		}

		if (app.right)
		{
			player.x += 4;
		}

		blit(player.texture, player.x, player.y);

		presentScene();

		SDL_Delay(16);
	}

	return 0;
}

As you can see, we've added a few tests of the app.up et al. variables. When these are set to 1, the player x and y will be updated. app.up and app.down will add and subtract 4 from the player y coordinate. app.left and app.right will do the same to the player x coordinate.

Now, at this point you're probably wondering why we're using these app.up etc. variables, instead of moving the player in the doKeyDown function in input.c. The answer to this is simple: those events fire only once when the key is pushed down (or a few more times if we're considering repeat events). This means that in order to move the player the arrow keys would need to be tapped constantly to make the ship cross the screen; keyboard repeat events are even worse in this case, as it leads to jerky movement all over the place. This isn't what we want. Setting the app.up family to 1 when a key is pushed down and 0 when the key is released means that the relevant key need only be held down to keep the player moving and released when we want the player to stop.

We've ticked off a few essentials on our road to creating a shooter - we can draw graphics and we can respond to player input. The next step will be to add the ability to fire bullets so that the player can defend themselves from the waves of enemies we will also soon add.

Exercises

  • Increase or decrease the speed at which the player moves.
  • Restrict the player to the bounds of the screen.

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:

Desktop site