Hey, I wanted to do something special for my 400th post, so I wrote a quick tutorial.
Tilemaps are a very important part of many, many games. For more information on what a tilemap is and how it relates to games, see this:
http://en.wikipedia.org/wiki/Tile_engine So, let's jump right in:
Getting started: The DataThe tilemap data actually defines which tiles go where. Now, in Axe, we use the [] tokens to store hex data. If you are not clear on what hex is, go here:
http://vlaurie.com/computers2/Articles/hexed.htm. A hex pair consists of two digits. Now, we want to compress our map data, and using a special routine from BuilderBoy, we can cut our map data in half. We have to limit ourselves to 16 tiles per map, but I'll address how to get around that later. Anyways, here is an example map, 4x4 for the purposes of the demonstration:
[1111->GDB1
[1001
[1001
[1111
Let's explain this. How can we store 4 tiles to two bytes? Well, we use each digit to store a tile. We will talk about how to get each digit alone later. Let me explain, however, why I put "->GDB1" at the top as opposed to the bottom, like you would do for a sprite. GDB1 is just a pointer. You see, Axe is going to add this data to the bottom of the program output. It also will set GDB1 to point at the very first one. However, if we were to re-arrange the code like so:
[1111
[1001
[1001
[1111->GDB1
Then we would have GDB1 point to the 7th byte
after the start of the map data. This would be bad because it would make the first row all "1" tiles, and the rest of the rows the random gibberish sitting in the memory after the last row. Not good. So, we put "->GDB1" on the first line. Also, for the sake of demonstration, please have your sprites in Pic1.
Drawing the Tile MapHaving the data is nice and all, but at some point you want to show it off. So, we have a simple method to draw it. The following method uses a 14x9 tile map with 7x7 tiles (hmm, isn't that what Half-Life 2: OC uses?
). However, it should be easily customizable to fit with your needs.
ClrDraw
For(I,0,7 // Start at zero, and go until your tile map width diveded by two (because we store two tiles for every one byte)
For(J,0,8 // The Y axis has one byte to each tile, so you can just use the height minus one here.
{J*9+I+GDB1->A // J*9 gets us the row offset (see below) and we just add the X value.
A^16->B // Get the second digit of the hex into B
A/16->A // And the first digit into A
If A
Pt-On(I*14,J*7,Pic1+A-8 // I*14 because of the two tiles to a byte, J*7 because of 7 pixel tiles
End
If B
Pt-On(I*14+7,J*7,Pic1+B-8
End
End
End
A^16?!? A/16?!? How is that supposed to get each digit?!? Honestly, I don't know. Ask BuilderBoy. It vaugely makes sense in my mind, but I haven't put a lot of brainpower towards it. Anyway, this routine will draw your tilemap on-screen, without updating the screen. It's your job to call DispGraph eventually, but you can still draw a character or something without having to worry about flicker.
Now, this line: "{J*9+I+GDB1->A" is weird. Let me explain. If you have a tilemap that looks like this:
11111111111111
10000000000001
10000000000001
10000000000001
10000000000001
10000000000001
10000000000001
10000000000001
11111111111111
It doesn't get stored in memory looking nice and layed out for you. It gets stored like this:
111111111111111000000000000110000000000001100000000000011000000000000110000000000001100000000000011000000000000111111111111111
So, what we can do to find the correct tile is this. We start with the row. We know each row is 7 bytes wide (2 tiles to a byte, remember?), so we can multipy J by seven. So if we have J=2, then we get J=14. That gives us an offset of 14 bytes. So, we have eliminated tiles and we have a current estimate of which byte we are going to draw: (X for an eliminated tile)
XXXXXXXXXXXX
XXXXXXXXXXXX
10000000000001
10000000000001
10000000000001
10000000000001
10000000000001
10000000000001
11111111111111
Now, we just add the I value to get the current X offset, getting us the actual byte we need (I=3):
XXXXXXXXXXXX
XXXXXXXXXXXX
XXXXX
00000001
10000000000001
10000000000001
10000000000001
10000000000001
10000000000001
11111111111111
Ta-da! We have the correct byte. Now we can use voodoo magic to extract each individual digit from the byte, and draw it on-screen.
Getting a TileHooray! You drew a tilemap! Congradulations! Now, if you want to use something better than Pxl-Test to check collisions with it, you need to be able to get the value of a single tile. I won't bother explaining this routine in detail, but just know I spent a half hour on it so you don't have to:
Lbl GT ; X position in S, Y position in T. Returns tile
S/7->S // Swap 7 for the size of your tiles
T/7*7->T // Yes, I know it is /7*7. Leave it that way. Unless you don't have 7x7 tiles, in which case you should change the values accordingly
If S^2
{(S/2)+T+GDB1}^16
Return
End
{(S/2)+T+GDB1}/16
Return
More Than 16 TilesYou may get to here and realize that, using this method, you can only have up to 16 tiles, one for each digit 0-F. This is true. However, you can have different tiles for different maps. What? You can only have 16 tiles per map, but you can have 16
unique tiles per map. So, you can have different "themes" for each map. It's easy. Here is a revised draw method:
ClrDraw
For(I,0,7 // Start at zero, and go until your tile map width diveded by two (because we store two tiles for every one byte)
For(J,0,8 // The Y axis has one byte to each tile, so you can just use the height here.
{J*9+I+GDB1->A // J*9 gets us the row offset (see below) and we just add the X value.
A^16->B // Get the second digit of the hex into B
A/16->A // And the first digit into A
If A
Pt-On(I*14,J*7,O*8+Pic1+A-8 // I*14 because of the two tiles to a byte, J*7 because of 7 pixel tiles. O is the offset
End
If B
Pt-On(I*14+7,J*7,O*8+Pic1+B-8
End
End
End
You'll notice we added "O*8+" to the Pt-On lines. This will make it so that you can specify O as an offset sprite. So, if you wanted an entirely new set of tiles, you can specify O as 16, which would point to the sprites after the first set. If you wanted to add one new sprite and lose the first old sprite, you could specify 1. If you want the original sprites, specify O=0.
If you have any questions, feel free to post them, and I will do my best to answer.
Also, 400th post!