How to make a tiled map for a game?

For years, I've been using a game creation program that used a language strikingly similar to C++ and that used a visual map editor where I could drag and drop terrain, objects, sprites, etc. Now I've decided to move on to writing the whole thing in C++ so that I can become more of a game programmer and less of a game designer, and really shake loose all the limitations I've been restrained by for years. However, I just can't seem to find a solution to my first, biggest problem - How do I write, process, and draw a 2-dimensional tiled map using C++?

I'm planning on having an external "spritesheet" on which the graphics will be, but then I need code to determine where the sprites are and how they will be arranged on-screen, plus how the screen will scroll when the player moves and ultimately how I'll tie the whole world together.

I understand that I will need an array that will contain numbers or names that represent which graphic to use, and their location in the array will represent their location in the world or on the local area map, and that I will need a for() loop or two to read each individual cell in the array and turn it into a 32x32 tile on-screen, but I have no idea where to start. If anyone has or knows how to write such a for() loop, or how to define the numbers in the array with a specific graphic from an external spritesheet (maybe I will have to save each individual sprite as its own bitmap file), or knows of an open source C++ game that contains a scrolling tiled map, or can come up with some other way that can help me figure this out, any and all help will be immensely appreciated.
How much skill do you have with C++? That will severely influence whether or not you will be messing with graphics any time soon.
You can probably pull this off with an MD array. But I'd advise you read Disch's cautionary article on them first, in the articles forum.
Most 2D tile engines I've seen/written involve the map itself, a "tileset" and one or more images with the graphics for all the tiles.

The tileset is just an group of structs, each containing the properties for a particular tile. Things like:
- whether or not the tile is "solid". Like a wall or an open space
- other physical properties (if this is an RPG, then something like whether or not the tile would trigger a random encounter)
- some kind of reference to the image used for drawing the tile
- the coords that the tile's graphics resides on that image.


The map then just consists of IDs for each tile.

Here's a very simplistic example. Note that there are many ways this can be improved... I'm just showing the basic idea here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
struct TileProperties
{
    bool bWall;  // true if the tile is a solid wall
    int  nGfxX;  // X coord of the graphic
    int  nGfxY;   // Y coord of the graphic
};

TileProperties myTileset[2] = {
  { true,  0, 0 },  // tile 0 = solid wall; graphics at 0,0
  { false, 32, 0 } // tile 1 = open space, graphics at 32,0
};

const int MAP_WD = 5;  // 5x5 map for simplicity
const int MAP_HT = 5;

int map[MAP_WD * MAP_HT] = {
  1,1,1,1,1,
  1,1,0,0,1,
  1,0,0,0,1,
  1,0,1,0,1,
  1,1,1,1,1
};


//================================
//  now if you want to get the properties of a tile at a specific coords:

const TileProperties& GetTile(int x, int y)
{
    return myTileset[ map[ (y*MAP_HT) + x ] ];
}

// now you can do things like:
if( GetTile(player.x, player.y).bWall )
{
  // disallow the player to move here
}

//================================
// as for drawing, it's the same idea:
int y, x;
for(y = 0; y < MAP_HT; ++y)
{
    for(x = 0; x < MAP_WD; ++x)
    {
        const TileProperties& t = GetTile(x,y);
        DrawImage( /* draw 32x32 pixels from whatever tileset image you have.  Use t.nGfxX
                 and t.nGfxY as the source coords for the image */ );
    }
}


Again -- this is a very simplistic / conceptual model and I don't recommend coding your program like this. It's just to give you the idea. Here are a few design suggestions:

1) Don't hardcode ANY maps or anything into your program. Read maps, tilesets, etc from external files. You'll also probably need to create a utility to edit maps/tilesets. Or you can find a pre-made generic map editor (but then you have to use its specific file format)

2) Don't have things global like the above. I just did that to keep the code small

3) Avoid multidimentional arrays.
Well, I came up with something that works (I'm using DarkGDK):

1
2
3
4
5
6
7
8
9
10
11
12
13
void MapInit (int currentMap[], int width, int height)
{
	dbLoadImage ( "grass.bmp", 1 );
	dbLoadImage ( "water.bmp", 2 );

	int cell = 1;
	for(int H = 0; H < height; H++)
		for(int W = 0; W < width; W++)
		{
			dbSprite ( cell, 32 * W, 32 * H, currentMap[cell - 1]);
			cell++;
		}
}


So in order to draw a map, I just have to define a 1D array representing the map data and call MapInit(mapArray, arrWidth, arrHeight). For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
	const int width = 10
	const int height = 10;
	int SuperMap[width * height] = {
		2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
		2, 2, 1, 1, 1, 1, 1, 1, 2, 2,
		2, 1, 1, 1, 1, 1, 1, 1, 1, 2,
		2, 1, 1, 1, 1, 1, 1, 1, 1, 2,
		2, 1, 1, 1, 1, 1, 1, 1, 1, 2,
		2, 1, 1, 1, 1, 1, 1, 1, 1, 2,
		2, 1, 1, 1, 1, 1, 1, 1, 1, 2,
		2, 1, 1, 1, 1, 1, 1, 1, 1, 2,
		2, 2, 1, 1, 1, 1, 1, 1, 2, 2,
		2, 2, 2, 2, 2, 2, 2, 2, 2, 2};

	MapInit(SuperMap, width, height);


Okay, that works fine, but I'm not sure where to go from here. Let's say I have a MapLibrary.cpp or .txt or whatever, containing all of the map arrays. How can I refer to one of the maps in this library when I call MapInit()?

I like the tileset idea, but I don't know how to use the data from TileProperties to pick the tile from the bitmap image. I understand nGfxX and nGfxY refer to the position of the tile on the tileset image, but I don't understand how to use these coordinates in DrawImage() or dbLoadImage(), or whatever function I end up using for this particular task.
Last edited on
I'm afraid I can't help you with that. I know nothing about DarkGDK or its drawing methods.
Well, what would you do? How would you use DrawImage(), and what would I need to install or #include to get that to work?
DrawImage is not a real function, it was just pseudocode

In most 2D graphic libs I've seen/used, you "blit" rectangles from one surface to another. Usually it's pretty straightforward on how to do it, but with DarkGDK I couldn't say.
what is x and y coords of graphics? i'm trying to write an example and i'm not experienced with 2d libraries.
The position of the graphics on the screen, usually defined by the upper left corner.
If you've gone through basic math in school and you know what a Cartesian plane is you should have no problem understanding that.
Okay, well, I'll try poking around the DarkGDK community to see if anyone can enlighten me. In case I don't find anything, let's say I uninstall DarkGDK and install a better, more widely used 2D graphics library. Which one should I get and where can I get it? I've tried googling it but I come up with a myriad of different graphics applications that do all sorts of weird things, but nothing that I'm looking for.

Well, there are two x's and two y's, the first pair being the coordinates of the graphic on the sprite sheet, defined by the upper-left corner. So let's say I have a 64x64 spritesheet consisting of four 32x32 sprites. To access the first in the upper-left corner, I would define x=0 and y=0. To access the fourth in the bottom-right corner, I would define x=32 and y=32. I would then also need to define the width and height of the image taken from the sprite sheet. The second pair of x and y are the screen coordinates at which the selected image will be drawn.
I don't know if it's "better" but I typically use SDL since it's simple and has OpenGL bindings. And because I'm already familiar with it, heh.

http://www.libsdl.org
Good news ~ I figured out how to do it in DarkGDK. dbSetSpriteTextureCoord() can be used to select the coordinates of the four corners of the sprite's texture. Here's my finished code for MapInit() :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void MapInit ( int arr[], int width, int height)
{
	dbLoadImage ( "tileset.bmp", 1 );

	int cell = 1;
	for(int H = 0; H < height; H++)
		for(int W = 0; W < width; W++)
		{
			// First we draw the sprite, which is clipped and sized later:
			dbSprite ( cell, 32 * W, 32 * H, 1);

			// All the math is done here:
			int cellValue = currentMap[cell - 1] - 1;
			int iX = cellValue % 2;
			int iY = cellValue / 2;
			float fOffset = 1.0f / 2.0f;
			float U = iX * fOffset;
			float V = iY * fOffset;

			// And here the math is used to clip the sprites:
			dbSetSpriteTextureCoord (cell, 0, U, V);
			dbSetSpriteTextureCoord (cell, 1, U + fOffset, V);
			dbSetSpriteTextureCoord (cell, 2, U, V + fOffset);
			dbSetSpriteTextureCoord (cell, 3, U + fOffset, V + fOffset);
			
			// The clip must be resized, otherwise it will retain the original size of tileset.bmp:
			dbSizeSprite (cell, 32, 32);

			// And then we move on to the next cell in the map array:
			cell++;
		}
}


tileset.bmp is a 64x64 image with four 32x32 tiles, namely:
GRASS | WATER
---------|---------
SNOW | SAND

The dimensions of the image in tiles (in this case, 2x2) comes into play in the lines:
int iX = cellValue % 2;
int iY = cellValue / 2;
float fOffset = 1.0f / 2.0f;

If I had a tileset with nine tiles, with dimensions of 3x3, the lines would have to read:
int iX = cellValue % 3;
int iY = cellValue / 3;
float fOffset = 1.0f / 3.0f;

That means that, as far as this function goes, the tileset must be square. I'm sure there's a way to change that... probably add another offset for the second dimension... but this works fine for me. Other than that, everything else should run like clockwork. Thanks for the help, everyone!
Topic archived. No new replies allowed.