Personal Log »

Encoding schemes for map data

This seems to be a recurring topic in my Twitter, because every time I start a new project I try to find what is the best way to deal with the map data. I guess I haven’t found the perfect encoding or, at least, an encoding that is good for all cases.

In this post I’m going through the different approaches I’ve used in my games.

Of course, use compression!

This is common to all my games: I compress the map data. Which is funny because, there’s always someone on Twitter suggesting it: “have you tried compression?”. Sure!

Unfortunately compression is not enough targeting some microcomputers and a big number of screens.

For example, looking at Golden Tail, it uses screens of 20x10 tiles with tiles of 8x16 (although it is irrelevant for what we are looking at here, it means the tile map is smaller). The game had a total of 40 screens –not a large map–, which adds up to 8000 bytes with no compression. That is a bit too big for a 64K single-load Amstrad CPC game.

So the idea is compressing each screen independently and you uncompress the screen you need to draw. You may get better compression ratios by grouping screens, but that means a larger memory area to uncompress a group. In Golden Tail I was still using UCL compression –which is good, but the compression library is very fiddly–, and it really helps to reduce those 200 bytes.

Now, this example may not be ideal because in Golden Tail I was already limiting the amount of tiles you can use per screen –explained on the next point– and the screens are optimised for that, but in this case UCL gives us an average of 88.4 bytes per screen, which is 3536 bytes in total, saving us a 55.8%. Not bad!

Bit packing

It is possible that you want to reduce even further the size of the map data. For example, it could be that your tile/sprite engine is using a lot of memory, or the fact that you are writing the game in C –which will use much more space than hand written assembler–. Bit packing can help to go beyond what compression can provide.

One way to do that is limiting the amount of tiles you can use on a single screen, so the tile index uses less than 8-bits, and bit packing the map data before compressing.

Continuing with Golden Tail’s example, using 4-bit per tile, one screen is now 100 bytes (we store 2 tiles in one byte), and the average UCL compressed screen is 79.5 bytes. The 40 screens are now 3180 bytes, giving us a further 4.45% of savings. Again, this is not a good example because Golden Tail’s data is already optimized to use 4-bit per tile, so the plain compression is already giving very good results even without bit packing.

What is the downside of this approach? You have less tiles to design your screens, which makes things harder, but it is just matter of practice. Golden Tail didn’t look too bad despite using a maximum of 16 tiles per screen, if I say so myself.

Golden Tail

Golden Tail screen

Let’s look at a more extreme example: Night Knight.

Night Knight screens are 16x22, which is 352 bytes per screen, and the game has 80 screens. That’s 28160 bytes!

In this MSX game I used 2-bit per tile, allowing up to 4 tiles per screen. It also has some information that is not stored in the map data and instead is added by the game: shadows and brick patterns.

By using 2-bit per tile, our screens are now 88 bytes, making the complete set 7040 bytes. Although I’m talking about only map data, game usually include entities –like enemies– that need to be added to the screen, and those are stored in sequences of bytes –in this game is 3 or 4 bytes per entity–, and accounting those the real total is 9864 (123.30 bytes per screen, on average).

If we apply compression –I used UCL in this game as well–, we get a total of 7028 bytes with an average of 87.85 bytes per screen.

Night Knight is a type of game that can use 2-bit per tile, using different tile-sets during the game, and the screen still looks nice. It always depends on the game, but I would say 4-bit per tile is a good compromise. Besides we can always cheat using an entity that allows adding tiles out of the tile-set, in exchange for some memory, of course.

Night Knight

Night Knight screen

Using objects instead of tile maps

So far the methods I have described are more or less level design friendly, because is just drawing your scene using tiles with your favourite map editor –I use tiled, and I explained how on a video–.

A different approach is to not use a tile map at all but describe objects instead, that will be interpreted by your map renderer to draw the scene.

Brick Rick uses 20x22 screens, which would be a tile map of 440 bytes per screen. The game has 50 screens, which adds up to 22000 bytes –without counting the entities–.

By describing each screen with a set of objects (“fill area”, “draw platform”, etc) –plus adding shadows, brick patterns, etc–, those 50 screens use 3112 bytes, with and average of 62 bytes per screen.

Obviously we want to use the smaller amount of memory to describe those objects. Brick Rick implemented 3 objects encoded as 1 byte offset from the previous object, 3 bits for the type of object, 5 bits for width, and then depending the object, there could be one extra byte.

The end result is very optimized, but the level design is tedious because we have to draw those objects, which is less direct that putting together a tile map, and we won’t have a visual cue of what the screen looks like. This may be more a mismatch of the tool I’m using than an issue with the technique, but I ended drawing the tile map and then putting the objects on top of that using a different layer. Not the end of the world, just a bit of extra work.

Brick Rick on tiled

Brick Rick's level design

In this case, the end result is so tight that there is no benefit from using compression.

Like in extreme bit packing, this method depends on the type of game. If I had to design screens as detailed as in Golden Tail, I would probably need more object types, and the end result wouldn’t be as good. In the case of Brick Rick, the resulting screens are richer than what I could get with 2 or 3-bit per tile.

Brick Rick

The in-game render of the same screen

Using meta-tiles

Some people call this super-tiles, but the idea is the same: use an intermediate table grouping your base tiles, and then use those groups –the meta-tiles– in your map data.

This is the system I’m using in my WIP title for the ZX Spectrum 48K: Outpost.

This game is not finished, so I don’t have a lot of data to show final numbers here, but I can provide some approximations that may be useful.

Outpost screens are 32x20 tiles, which is 640 bytes. Say we go with a small game like Golden Tail, that would add up to 25600 bytes. I plan to add more screens than that, and I’m targeting the 48K model with single-load, so I should do better than that!

If we use 4x4 meta-tiles, 64 meta-tiles would be 256 bytes –the speccy is 1-bpp, and the meta-tiles don’t need to store attributes; is just an indirection to the actual tile data–, and this reduces our screen size to 160 bytes (width and height are the half of the size with regular tiles). Besides, the meta-tiles based maps have the property of generally compressing better than small tiles, so when we apply compression –in this project I’m using aPLib compression, with the awesome apultra–, we reduce this to an average of 62 bytes –with the caveat that I have designed only a few screens, but the number sounds about right to me–.

Outpost in tiled

Outpost level design with meta-tiles of 4x4

So going back to our hypothetical 40 screens, that’s now 2480 bytes –plus the 256 bytes overhead of the meta-tiles table, and probably a bit of extra code–.

The downside of this method is that the level design is tricky because we have to draw our tiles and group them in meta-tiles, and then use those meta-tiles to design our screens. In my case I threw Python to it until the problem was simple enough. At the end the game expands the meta-tiles to just tiles, so things like the collision detection are just as usual.

Conclusion

Obviously the game size doesn’t necessarily translate into more or less fun, but if the game works, more game is likely to be better. We will always want to cram in as many screens as possible, and considering that other parts of the game are harder to make smaller –graphics, the code itself–, keeping the map data under control is very important.

As you can see, there is not a “one size fits all” approach, so knowing what are your options when you start to plan a new game, is always useful to avoid ending with a good game that is too short!

Would you like to discuss the post? You can send me an email!