The random ramblings of a French programmer living in Norway...
← Tilemap tutorial (part 1)Tilemap tutorial (part 3) →
  Tilemap tutorial (part 2)
Mon 19th November 2012   
Welcome to the second part of the Tilemap tutorial.

Hopefully at this point you should have a reasonably clear idea of what tiles and tilemaps are. Now is the time to see how this all translates in programming terms.

Conceptual description

At the core, all you need is a number of elements each identified by a unique number and some two dimensional array structure where you write numbers. Each cell contains a number that points to one of the elements.

As you see I gave a very generic definition which does not even contains the words "tile", "graphic" or "map", the reason is that this concept applies to quite many things, not only game graphics.

A good example would be a text video mode as you can see on a booting PC, or everybody's all time favorite: Windows Blue Screen of Death.

BSOD picture

At first glance this does not seem very obvious, but if you just think about it then it all makes sense:
  • A text screen is like an array typically containing 25 or 50 rows of 80 cells
  • Each cell contains a value which will be displayed as a character
In such video mode, if you write the value 65 in the first byte of the video memory, you will generally see an A appear on the screen.
You can use any of the supported codes (in my example I used the ASCII1 encoding)
in any of the rows and columns to type any message you want, or even make ASCII Art if you feel like it.

I can feel your suspicion. Right now you are wondering how one jumps from the text mode error messages of MS-DOS to 2d pixel graphics.

Text based games

Well, once upon a time there was no such things as bitmap graphics - heck, there was not even personal computers -, all you had was boring terminals able to display text.
Still some creative people managed to make graphical games using this primitive technology, using characters and symbols as graphics.

A perfect example would be Rogue:

The kestral hit you
------------------ -----------------
|................| -------------- |....*...?......|
|................+####+............| |...............|
|................| |............+############+...............|
------------+----- |............| --------+--------
# -+------------
------+------- ----
|............| |B%+
|............+##### |K*|
|...*........| ######@J.|
-------------- ----

Level: 1 Gold: 25 Hp: 19(20) Str: 16 Ac: 6 Exp: 2/10

As you can see, this "screenshot" is actually just made of text. Despite that I hope you will admit that it's not difficult to imagine that this represents some rooms linked by tunnels.

Some more recent games have been building on this idea, you can try Dwarf Fortress2 for a more colorful example.

Moving from this textual representation to something more graphical is really not that difficult: All you need is to manage your own "screen" and draw you own "characters" on it. Let's see how to do that.

Bring in the graphics

Tile picture
Because the concept is so generic, I will not use any particular language or framework. Instead I will use some pseudo code with comments to explain things. It will not be optimized either just to keep it simple.

Here is what would look the pseudo code to generate and draw the "space station"3 example I was showing in the previous article.

// Our tilemap is 8x8 tiles 
tileMapWidth =8

// '0' represents the deep space tiles, '1' and '2' are the metal plates
tileMap = [0,0,0,0,0,0,0,0]
[0,0,0,0,0,0,0,0] As Bytes

// This returns an array of 3 tiles each containing a picture
tiles = ["tile_starfield",
"tile_metal_plate_painted"] as Pictures

// We now iterate through all the cells, column by column, row by row
For row From 1 To tileMapHeight
For column From 1 To tileMapWidth
// Read the tilemap to find the number of the tile at this location
tileIndex = tileMap[column][row]
// From the index get the picture from the texture array
tileToDraw = tiles[tileIndex]
// Draw it at some magical coordinates that matches the tilemap ones :)
Next column
Next row

This example is easy to extend, all you need is to adapt the pseudo-code to fit your needs for a larger tilemap or for more tile variety.

Obviously in the real code the tilemap data would be created with a nice editing tool and then loaded from a file, but that's conceptually identical.

And the code would still be very, very inefficient.

Scalability issues

This pseudo-code sample was so simple and minimalistic that obviously nothing could be wrong. After all each cell is being read and displayed only once; can this even be improved?

There are a number of problems in what this code is doing:
  • It does not scale well when the size of the tilemap is increased
  • The number of DrawPicture calls can be very large when using small tiles on a large screen
  • Zooming or rotating the tilemap would be difficult.
The first problem will happen when you start really developing your game using some really large tilemap.
Seamless large worlds are cool, so obviously instead of a 8 by 8 tilemap you feel like your game deserves at least 1024 by 1024 tiles to look cool, perhaps even more.

With 8 by 8 tiles, we perform 64 iterations and calls to DrawPicture.

With 1024 by 1024 tiles, we perform 1 048 576 iterations and calls to DrawPicture; let say that it is a significant increase in work for the CPU. And the worse is that most of this work is not even displayed on the screen!

Indeed, when using 16 by 16 pixels tiles, our 8 by 8 tilemap is drawn as a 128 by 128 pixels square on the screen.

On the other hand, a 1024 by 1024 tilemap actually represents a 16384 by 16384 pixels square; way bigger than your screen4!

Even if you have a super awesome screen in the 2600 by 1500 resolution range, you can still only see about 1.43% of your tilemap on the screen: 98% of it is invisible, what a waste of resources!

Basic clipping

Obviously, rendering tiles that are not going to be visible is not very efficient. The solution for that is called "clipping".

Now, there are two ways (at least) to implement clipping: The good way, and the bad way.

The bad way would be to add something like that in the code:

// We now iterate through all the cells, column by column, row by row
For row From 1 To tileMapHeight
For column From 1 To tileMapWidth
// Make sure the coordinates are in the visible area
If (column>leftClip) And
(column<rightClip) And
(row>topClip) And
// Draw the tile
(previous code)
Next column
Next row

For sure, you are not going to call DrawPicture one million time anymore, but now you are going to perform one million pointless comparisons.

We can sure do better than that!

More efficient clipping

I wrote pointless comparisons, because you don't need to do any comparisons at all.

Since you know the size of your tilemap, the dimensions of your tiles, and the size of your display target, all you need to do is to draw the tiles you know are visible.

That would give us something similar to that:

// We now iterate through all the visible cells, column by column, row by row
For row From topClip To bottomClip
For column From leftClip To rightClip
// Draw the tile
(previous code)
Next column
Next row

This new code is simpler than the previous one, and will iterate through exactly only the cells that are in the visible window5.

That's all for today. The next part will explain how to draw the tilemap more efficiently by leveraging the capabilities of your GPU.

If you have questions, remarks, or simply want to point out something wrong: Don't hesitate to use the comment box, it's here for this exact reason!

1. American Standard Code for Information Interchange, defines 128 values containing upper case and lower latin letters, number and symbols.
2. You can download the game from Bay 12 Games, but be warned: This is an extremely hardcore game.
3. It should be obvious now why my example is only 8 by 8 tiles and with only 3 different types of tiles!
4. This was written in 2012, I know that in a decade or so we will have such a resolution.
5. Of course you need to take into consideration the fact that the tilemap moves, that some tiles can be half visible, etc... You need to compute the clipping rectangle accordingly.
comments powered by Disqus