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 Third Side (Battle for the Solar System, #2)

The White Knights have had their wings clipped. Shot down and stranded on a planet in independent space, the five pilots find themselves sitting directly in the path of the Pandoran war machine as it prepares to advance The Mission. But if they can somehow survive and find a way home, they might just discover something far more worrisome than that which destroyed an empire.

Click here to learn more and read an extract!

« Back to tutorial listing

— Simple 2D adventure game —
Part 3: Loading a map

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

Introduction

Now we can move around our map and be constrined by the tiles (cannot cross holes or walls), it's time we worked with a proper map. The best map we have up to this point contains just random scatters of holes and walls. Loading map data from a file is easy, as it's a list of number, enough to fill our map array.

Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./adventure03 to run the code. You will see a window open like the one above, showing the prisoner on a tiled background. Use WASD to move around. You will find this map more interesting to explore, as it's more maze-like in nature. Close the window to exit.

Inspecting the code

Only two files have changed in this update: map.c and player.c. Before we look at those changes, let's take a look at the map data itself:

1 1 1 1 1 40 40 1 1 1 1 40 0 40 40 40 40 40 40 0 0 0 0 40 40 40 1 1 40 0 0 0 0 0 0 40 40 40 40 0 0 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 0 40 1 1 1 1 40 0 0 0 40 40 1 1 1 1 40 40 40 0 0 0 0 40 1 1 40 40 40 40 1 1 1 1 1 1 1 1 40 40 40 40 40 40 40 1 1 1 40 40 40 40 1 1 1 1 40 0 40 1 1 1 1 40 40 40 40 40 1 1 1 1 1 1 1 40 40 0 0 0 40 1 1 1 1 1 1 1 40 40 40 1 1 1 1 40 0 0 0 0 0 0 1 1 1 40 0 0 40 1 1 1 1 40 0 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 0 0 0 40 1 40 40 40 40 1 1 40 0 40 1 1 1 1 40 40 40 0 0 0 0 1 1 1 40 0 0 40 40 40 40 40 40 0 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 0 0 0 40 1 40 40 40 40 1 1 40 0 40 1 1 1 1 1 1 40 40 0 0 0 1 1 1 40 0 0 0 0 0 0 0 0 0 40 1 1 1 1 1 1 40 40 40 40 40 40 1 1 1 1 1 40 0 0 0 40 40 40 40 1 1 1 1 40 0 40 1 1 1 1 1 1 1 40 0 0 0 1 1 1 40 40 40 40 40 40 40 40 40 40 40 1 1 40 1 1 1 40 0 0 0 0 40 1 1 1 1 1 40 0 0 0 0 0 0 40 40 40 40 40 40 0 40 1 1 1 1 1 1 1 40 40 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 1 1 1 40 0 1 1 0 40 1 1 1 1 1 40 0 0 0 0 0 0 0 0 0 0 0 0 0 40 1 1 1 1 1 1 1 1 40 40 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 1 1 1 40 0 1 1 0 40 1 1 1 1 1 40 0 40 40 40 40 40 40 40 40 40 40 40 40 40 1 1 40 40 40 40 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 1 1 1 40 0 1 1 0 40 1 1 1 1 1 40 0 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 0 0 0 40 1 1 1 1 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 1 1 40 40 1 1 1 1 1 40 0 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 0 0 0 40 40 40 40 40 40 0 0 0 0 0 0 1 1 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 0 40 40 40 40 40 40 40 1 1 1 1 1 1 1 40 40 0 0 0 0 0 0 0 0 40 0 40 40 40 40 0 1 1 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 0 0 0 0 0 0 0 40 40 40 40 1 1 1 1 40 0 0 0 40 40 40 40 40 40 40 0 40 1 1 40 0 1 1 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 0 0 0 0 0 0 0 0 0 0 40 1 1 1 1 40 0 0 0 40 1 1 1 1 1 40 0 40 1 1 40 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 0 0 0 0 0 0 0 0 0 0 40 1 1 1 1 40 0 0 0 40 1 1 1 1 1 40 0 40 1 1 40 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 40 40 0 0 0 0 0 0 0 0 0 0 40 1 1 1 1 40 0 0 0 40 40 40 1 1 1 40 1 1 1 1 1 1 1 40 40 40 40 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 40 0 0 0 0 0 0 0 0 0 0 0 0 40 1 1 1 1 40 0 0 0 0 0 40 1 1 1 40 1 1 1 1 1 1 1 40 0 0 0 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 0 0 0 0 0 0 0 0 0 0 0 0 0 40 1 1 1 1 40 40 40 0 0 0 40 1 1 1 40 1 1 1 1 1 1 1 40 0 0 0 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 0 0 0 0 0 0 0 0 0 0 0 40 40 40 1 1 1 1 1 1 40 0 0 0 40 1 1 1 40 1 1 1 1 1 40 40 40 0 0 0 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 1 40 0 0 0 0 0 40 40 40 40 40 40 40 1 1 1 1 1 1 1 1 40 0 0 0 40 1 1 1 40 40 40 1 1 1 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 40 1 40 40 40 40 40 40 40 1 1 1 1 1 1 1 40 40 40 40 40 40 40 40 0 0 0 40 1 1 1 40 1 1 1 1 1 40 0 0 0 40 40 40 40 40 40 40 40 40 40 40 40 0 0 0 0 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 0 0 0 0 0 0 0 0 0 0 40 1 1 1 40 1 1 1 1 1 40 0 0 0 40 1 1 1 1 1 1 1 1 1 1 40 0 0 0 0 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 40 40 40 0 0 0 40 40 40 40 40 1 1 1 40 1 1 1 1 1 40 0 0 40 40 1 1 1 1 1 1 1 1 1 1 40 0 0 0 0 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 0 0 0 40 1 1 1 1 1 1 1 40 1 1 1 1 1 40 40 40 40 1 1 1 1 1 1 1 1 1 1 1 40 0 0 0 0 40 1 1 40 40 40 40 40 40 40 40 40 40 40 40 40 1 1 1 40 0 0 0 40 40 40 40 40 40 1 1 40 1 1 1 1 1 1 1 1 1 1 40 40 40 40 40 40 40 1 1 1 40 40 0 0 40 40 1 1 40 0 0 0 0 0 0 0 0 0 0 0 40 1 1 1 40 0 0 0 0 0 0 0 0 40 1 1 1 1 1 40 40 1 40 40 1 1 1 40 0 0 0 0 0 40 1 1 1 1 40 40 40 40 1 1 1 40 0 0 0 0 0 0 0 0 0 0 0 40 40 1 1 40 40 0 0 0 0 0 0 0 40 1 1 1 1 1 40 0 1 0 40 1 1 1 1 1 1 1 1 0 40 1 1 1 1 1 1 1 1 1 1 1 40 40 40 40 40 40 40 40 40 0 0 0 0 40 1 1 1 40 0 0 0 0 0 0 0 40 1 1 1 1 1 40 0 1 0 40 1 1 1 40 0 1 1 1 0 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 40 0 0 0 0 40 1 1 1 40 40 40 40 40 40 40 40 40 1 1 1 1 1 40 0 0 0 40 1 1 1 40 0 1 1 1 0 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 40 0 0 0 0 40 1 1 1 1 1 1 1 1 1 1 1 1 1 1

As I said, it's just one long list of numbers. In this case, 1710..! Quite a lot, and our map is only 57 x 30. The map for the final dungeon will be bigger. Still, the file size of this map is just under 4kb, so it's not terrible. This map wasn't produced by hand. A simple map editor was rolled to help with creating it. A future tutorial will look into how to make such an editor. Now let's look at the code:

Starting with map.c, the initMap function now calls loadMap, to load the map data, before then calling another function called decorateMap to add details:


void initMap(void)
{
	loadTiles();

	loadMap();

	decorateMap();
}

The loadMap function itself is simple:


static void loadMap(void)
{
	int x, y;
	char *data, *p;

	data = readFile("data/map.data");

	p = data;

	for (y = 0 ; y < MAP_HEIGHT ; y++)
	{
		for (x = 0 ; x < MAP_WIDTH ; x++)
		{
			dungeon.map.data[x][y] = atoi(p);

			do {p++;} while (*p != ' ');
		}
	}

	free(data);
}

We call a function called readFile, to load the map data into a char array, then point another char pointer (p) at the start of the array. Now ready to load our map data, we set up y and x variables to read the data by row and column; so, we'll read all the values on the first row, then the second row, etc., until we have read all the data. We call atoi on p (which will initially be pointing at the first number in our map data), to read the tile value and assign it to the appropriate index in our map. After this, we'll increment the value of p while the character isn't a space, to move onto the next number. Our numbers are space delimited, and so setting up this while loop will jump to the next one. Remember, calling atoi will read the next number found, but won't move the pointer on! If we didn't do this, we would end up reading the same number constantly and never finish. If we told the pointer to move on just one character, numbers composed of more than one digit would cause the data to read incorrectly. Once we're finished reading the data, we free it.

Note that that loadMap function assumes there are exactly the right number of values and performs no error checking or correction..!

Onto the decorateMap function. This function serves to randomly change the ground and walls tiles:


static void decorateMap(void)
{
	int x, y;

	srand(144893);

	for (x = 0 ; x < MAP_WIDTH ; x++)
	{
		for (y = 0 ; y < MAP_HEIGHT ; y++)
		{
			if (dungeon.map.data[x][y] == TILE_GROUND && rand() % 5 == 0)
			{
				dungeon.map.data[x][y] += (1 + rand() % 4);
			}

			if (dungeon.map.data[x][y] == TILE_WALL)
			{
				dungeon.map.data[x][y] += rand() % 6;
			}
		}
	}
}

We're seeding the random with a fixed number, so that the random picks are always the same, and then looping through our map data. If we encounter a ground tile, there's a one in five chance we'll change it. Effectively, this step turns some normal ground tiles into cracked tiles. We know from our atlas that the four cracked tiles follow directly after the regular ground tile, which is why we increment the number by 1-4. Similarly, when we encounter a wall tile we'll randomly add 0-5 to the value. This will either leave the wall as default or change it to one of the other wall image types.

There's no real reason for not manually doing this in the map data itself, other than to do away with the repetitive nature of doing so in an editor, where the process could take some time.

Moving on to player.c, we've made just some simple adjustment to the player's starting position. Instead of starting at 0,0, we're moving the player over to 14,28. This is just to give them a new starting position to start exploring from.


void initPlayer(void)
{
	player = spawnEntity();

	player->x = 14;
	player->y = 28;
	player->texture = getAtlasImage("gfx/entities/prisoner.png", 1);
	player->facing = FACING_LEFT;

	movePlayer(0, 0);

	moveDelay = 0;
}

Before we finish, a quick look at the readFile function, which lives in util.c. This function simlpy reads a file into a char array and returns it. If the file doesn't exist, we print an error and exit the program. Because the data read is malloc'd, need to always free it when we're done using it (as happens in loadMap).


char *readFile(char *filename)
{
	char *buffer;
	long length;
	FILE *file;

	file = fopen(filename, "rb");

	if (file == NULL)
	{
		SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_CRITICAL, "No such file '%s'", filename);
		exit(1);
	}

	fseek(file, 0, SEEK_END);
	length = ftell(file);
	fseek(file, 0, SEEK_SET);

	buffer = malloc(length);
	memset(buffer, 0, length);
	fread(buffer, 1, length, file);

	fclose(file);

	return buffer;
}

So, there we have it - a basic map loading routine. We'll be using this map for a few more tutorials, but the final dungeon that will form our core game will actually be a few times larger; consider this map to be a test bed for various bits and pieces to come.

Next, we'll look at how we can handle interacting with other entities, such as items.

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