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
SDL 1 tutorials (outdated)

Latest Updates

SDL2 Rogue tutorial
Wed, 29th September 2021

SDL2 Gunner tutorial
Thu, 26th August 2021

SDL2 Shooter 2 tutorial
Tue, 13th July 2021

SDL2 Widget tutorial
Fri, 18th June 2021

SDL2 Adventure tutorial
Tue, 8th June 2021

All Updates »

Tags

android (3)
battle-for-the-solar-system (9)
blob-wars (9)
brexit (1)
code (6)
edgar (6)
games (37)
lasagne-monsters (1)
making-of (5)
match3 (1)
numberblocksonline (1)
orb (2)
site (1)
tanx (4)
three-guys (3)
three-guys-apocalypse (3)
tutorials (8)
water-closet (3)

Books

« Back to tutorial listing

— 2D platformer tutorial —
Part 4: Interacting with entities

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 tutorial will explain how to add and control a character on a basic 2D map. Unpack the code and then type make to build. Once compiling is finished type ./ppp04 to run the code.

A 1280 x 720 window will open, with a series of coloured squares displayed over a light blue background. Pete, the player character, will drop to the ground from the top-left of the screen. He can be controlled using A and D to move him left and right, and using I to jump. Pressing Space will reset Pete to the starting point. If you're not fond of these controls, they can be easily changed in player.c. Black squares with white outlines are entities that Pete can stand on. Close the window by clicking on the window's close button.

Inspecting the code

Allowing Pete to interact with other entities is quite a simple task. First, we should define some flags to help with our collision detection and response. In defs.h we'll add a few:


#define EF_NONE       0
#define EF_WEIGHTLESS (2 << 0)
#define EF_SOLID      (2 << 1)

These will be used to specify things like whether the entity is affected by gravity and if it's solid (meaning that it will block movement). With those done, we can move onto entities.c and make some changes. The first function we'll update is move:


static void move(Entity *e)
{
	if (!(e->flags & EF_WEIGHTLESS))
	{
		e->dy += 1.5;
		e->dy = MAX(MIN(e->dy, 18), -999);
	}

	...

	e->x += e->dx;
	moveToWorld(e, e->dx, 0);
	moveToEntities(e, e->dx, 0);

	e->y += e->dy;
	moveToWorld(e, 0, e->dy);
	moveToEntities(e, 0, e->dy);

	...

Right away, we're testing whether our entity is weightless. If it is, we're not applying gravity to it, meaning that it will remain suspending in the air (unless it is told to move all by itself). Next, we want to update our movement steps. We've now got a new function called moveToEntities, which will take the same parameters as moveToWorld. This function is used purely for moving and interacting with entities in the world. It's not a hugely complicated function, especially compared to moveToWorld, and actually takes some cues from it:


static void moveToEntities(Entity *e, float dx, float dy)
{
	Entity *other;
	int adj;

	for (other = stage.entityHead.next ; other != NULL ; other = other->next)
	{
		if (other != e && collision(e->x, e->y, e->w, e->h, other->x, other->y, other->w, other->h))
		{
			if (other->flags & EF_SOLID)
			{
				if (dy != 0)
				{
					adj = dy > 0 ? -e->h : other->h;

					e->y = other->y + adj;

					e->dy = 0;

					e->isOnGround = dy > 0;
				}

				if (dx != 0)
				{
					adj = dx > 0 ? -e->w : other->w;

					e->x = other->x + adj;

					e->dx = 0;
				}
			}
		}
	}
}

We're stepping through all the entities in the stage and checking to see if our subject has collided with any of them (first checking to see that the subject and target are different). If a collision does take place, we're then checking to see if the other entity is solid. If so, we're checking the direction the entity was moving in and aligning them to the correct side of the target (left, right, top, or bottom). This response is much the same as what we were doing when testing against the world. The only major difference here is that the subject will be aligned according to the target entity's width and height, rather than TILE_SIZE. In all cases, the subject's dx and dy is zeroed during a collision, and the isOnGround flag is set if they are were moving down when the hit occurred.

This works perfectly, and allows Pete to stand and walk on solid entities as if they were a part of the world map. This could be used to create things like crumbling blocks, bridges, doors, etc.

With that done, let's take a quick look at how we're loading and setting up other entities in the world. A couple of new functions have been added to entities.c - loadEnts and addEntFromLine. We'll look at these in order:


static void loadEnts(const char *filename)
{
	char line[MAX_LINE_LENGTH];
	char *data, *p;
	int n;

	data = readFile(filename);

	p = data;

	n = 0;

	memset(line, '\0', MAX_LINE_LENGTH);

	while (*p)
	{
		if (*p == '\n')
		{
			addEntFromLine(line);
			memset(line, '\0', MAX_LINE_LENGTH);
			n = 0;
		}
		else
		{
			line[n++] = *p;
		}

		p++;
	}

	free(data);
}

loadEnts reads a file into memory using the readFile function and then fills a char array called line with a line from the data. It's not hugely effecient, but gets the job done. Once the \n character is hit (specifying a line break), the line array is passed to a function called addEntFromLine. The line array is then cleared and character data is read into the array until the next line break is hit (or until the end of the data is encountered).

The addEntFromLine function is quite simple:


static void addEntFromLine(char *line)
{
	char name[MAX_NAME_LENGTH];
	Entity *e;

	e = malloc(sizeof(Entity));
	memset(e, 0, sizeof(Entity));
	stage.entityTail->next = e;
	stage.entityTail = e;

	sscanf(line, "%s", name);

	if (strcmp(name, "BLOCK") == 0)
	{
		sscanf(line, "%*s %f %f", &e->x, &e->y);

		e->texture = loadTexture("gfx/block.png");
		SDL_QueryTexture(e->texture, NULL, NULL, &e->w, &e->h);
		e->flags = EF_SOLID+EF_WEIGHTLESS;
	}
}

An entity is created and then the entity name in the line passed over is read, using sscanf. A strcmp is called on the name to determine the type of entity. If it's "BLOCK" we create a block entity, loading the texture we want to use, setting its size, and then adding the EF_SOLID and EF_WEIGHTLESS flags to it. There is a small issue here that an unknown entity will set up nothing and perhaps even crash later, due to things like missing textures, etc. For now, BLOCK is the only entity we're supporting, so it's not a huge deal. In a full game, error checks should be performed across this step to reject unknown entities.

Before we finish, let's look at the format of the entity data in the file:

BLOCK 896 512
BLOCK 1728 448
BLOCK 1664 320
...

As you can see, each line contains the entity identifier, the x coordinate, and y coordinate in the world. Adding more entities would simply be a case of adding more data to the file and supporting it in code.

Our game is making great progress. We can load the map and entities, and traverse both. What we should do next is support moving platforms. They should carry Pete around when they move, both up, down, left, and right.

Purchase

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

It is also available as part of the SDL2 tutorial bundle (with on-going updates):

If you do not wish to create an itch.io account, you can also purchase the tutorial bundle using PayPal. This method will be slower, however, as it will require manual verification of the transaction.

Comments

Share your comments and thoughts below. All comments are anonymous and cannot be edited.

 

Mobile site