Tile Maps

z80 » Graphics

The basic concept of tile maps are to take several generic texture sprites, all of equal size, and print them on the screen in order with respect to a set of data cataloging the arrangement.

Think of a person that puts down bathroom tiles. He (or she) has a set of multicolored tiles and some sort of drawing to show him (or her) the pattern that they need to be laid out. That person will pick and place the tiles on the floor according to the drawing they have. The same goes for calculator tile map routines.

Games like Joltima, DStar, and Journey use tile map routines to display the different objects on the screen. Each level in the game has it's own distinct data representing a pattern on the screen. The game also contains a table of sprites (all of equal length) that coorespond to a value that can be in the level data. Say for example the value 4 when found in the level data could refer to an 8 by 8 sprite of heart while a 5 refers to a diamond. The tile map routine will:

  1. Take a byte in the level data
  2. Match that byte to its cooresponding generic sprite from a table
  3. Render that sprite on the screen
  4. Set up the coordinates for the next tile
  5. Move on to the next byte in the level data

Sprite Image Data

Tile map image generators use a set of sprites to make up the final image on the screen. These sprites need to all be the same size. The routines usually store the sprites all in order. If the routine wants the 3rd sprite and each sprite is 8 bytes long, then it will multiply 3 and 8 and add that to the starting address of the first sprite. Since most of the sprites are 8 bytes long, we can use our commands to multiply by 2 three times (2*2*2=8). Follows is how to store the data for two sprites. The first is a picture of a square; the second is a picture of a circle.
sprites:		;start of sprite data
sprite_square:		;first sprite
	.db %00000000
	.db %01111110
	.db %01000010
	.db %01000010
	.db %01000010
	.db %01000010
	.db %01111110
	.db %00000000
sprite_circle:		;second sprite
	.db %00011000
	.db %00100100
	.db %01000010
	.db %01000010
	.db %01000010
	.db %00100100
	.db %00011000
	.db %00000000

Level Data

Level data for the sprite value can be stored in various forms: each byte, each nibble, every 2 bits, every bit. This all depends upon how many different sprite choices are available to the tilegenerator from the sprite data. If there are 256 different sprites in the sprite image data, then use a byte to store each tile's data; if there are 16 different ones, use a nibble; if there are 4, use every 2 bits; if there are only 2, use just one bit. The amount of possible tiles also weighs in to how bit the level data will be. For a 16 by 8 tile map, every byte being a tile would mean 128 bytes for each tile map's level data. If you had every nibble for a tile, that would cut that level's size in half to 64 bytes.

The tile generator routine is designed to be fast. The level data has to suit the routine. Say you have a routine, just so it can be fast, start drawing at the bottom right of the screen moving left and up. Your data might also need to start with the first byte being the bottom right, the second byte being the next byte to the left, etc. The routine should not have to accommodate the data.

Here's an example of level data (starting at the top left and reading from left to right, like a book). Following the data is a simple user chart telling you what each value encountered in the level data looks like as a sprite. These values are going to coorespond to letters. Following that will be a graphical representation of the TI86 screen after the tile map has been drawn.

level_data:
	.db 0,1,0,0,2,0,0,0,0,3,0,0,0,0,0,0
	.db 0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0
	.db 0,0,0,0,1,0,0,3,0,2,0,2,0,0,0,0
	.db 0,0,0,0,1,0,0,3,0,2,0,2,0,0,0,0
	.db 0,0,0,0,1,0,0,3,0,2,0,2,0,0,0,0
	.db 0,0,0,0,1,0,0,3,0,2,0,2,0,0,0,0
	.db 0,0,0,0,1,0,0,3,0,2,0,2,0,0,0,0
	.db 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1

Value Letter
0 ' '
1 'a'
2 'b'
3 'c'

 a  b    c
       a
    a  c b b
    a  c b b
    a  c b b
    a  c b b
    a  c b b
aaaaaaaaaaaaaaaa

If you ever start to build a game like Joltima, DStar, or Journey, you probably want to consider writting a quick C/C++ application to make the level data. Probably have it use bitmaps of the actual sprites that will be used. If you can't do it in C/C++, then try using QBASIC or batch files somehow. If you end up making tons of levels, you're going to get a headache trying to figure out all the data and what it looks like in your head; a quick application can be a life saver!

The Routine

Let's construct a routine to generate the tile map for us. The basic routine,as stated at the top, will need to:
  1. Take a byte in the level data,
  2. match that byte to its cooresponding generic sprite from a table,
  3. render that sprite on the screen,
  4. set up the coordinates for the next tile,
  5. move on to the next byte in the level data.

In the Code Tree section, you learn that before you start designing a routine, it is best to outline the routine the same way you would an essay for school. Here's the indention outline for the routine we are going to make.

draw 8 rows
	draw 16 columns
		draw sprite
			get value from level data
			find sprite for that value
			draw 8 rows of sprite
				get byte from sprite image data
				draw byte onto video memory
				increase video memory to next row
				increase sprite data by a byte
				loop drawing of sprite
		increase column and repeat
	increase row and repeat

There are only a few routines out there that print tile maps on the screen. One of the most notable comes from Assembly Coders Zenith. ACZ is one of the top programming groups around. They're routine DrawMap is one of the fastest tile map generators. It uses GridPutSprite by Dan Eble in the routine. When I started making this tutorial, I decided to make my own tile map engine. When it was done, I ran it against DrawMap and saw it was over 10% faster running about 5 times more per a second.

I've pasted my TileGen Routine here. You can download this along with many other Tile Engine routines that I've come across in the Download Section. Each line in the routine is represented by a number which corresponds to an explanation at the end of the routine. Follow each line and try to picture in your head what it is doing. The easiest way to do this is to print out the TileGen Code and then refer to the explanations.

  1. ;======================================
  2. ; tilegen by James Malcolm (me@jgmalcolm.com)
  3. ; draws 16x8 tile map
  4. ;======================================
  5. tilegen:
  6. ld ix,$fc00
  7. ld b,8
  8. loop_row:
  9. push bc
  10. columns:
  11. ld b,16
  12. loop_columns:
  13. push hl
  14. ld l,(hl)
  15. ld h,0
  16. add hl,hl
  17. add hl,hl
  18. add hl,hl
  19. ld de,tile0
  20. add hl,de
  21. draw_tile:
  22. ld de,$10
  23. ld c,b
  24. ld b,8
  25. push ix
  26. draw_tile_loop:
  27. ld a,(hl)
  28. ld (ix),a
  29. add ix,de
  30. inc hl
  31. djnz draw_tile_loop
  32. pop ix
  33. ld b,c
  34. pop hl
  35. inc hl
  36. inc ix
  37. djnz loop_columns
  38. ld de,$10*7
  39. add ix,de
  40. pop bc
  41. djnz loop_row
  42. ret

Line(s) Explanation Bytes
5 TileGen is called in context as follows:
	ld hl,tile_data		;pointer to start of tile data
	call tilegen		;run tile generator
		
none
6 We almost always want our tile map to be drawn on the screen (address $fc00). Sometimes you may want to draw to a video buffer such as the graph screen (_plotSScreen at $c9fa) and then copy that to the current screen during game play. This line tells where to draw the map to. 4
7 If you divide the height of the screen by 8, you have 8 rows. Each of the tiles is probably going to be 8 bytes tall. We are drawing the routine row by row according to the code treeabove. 2
9 During the main routine we are going to be using b and so we must save it since it's our row counter as stated in line 7. 1
11 Each row has 16 tiles on it because they are 1 byte wide. This is our counter for each column. 2
13 We need to save our position in the tile data. We are going to need hl to figure out which sprite to use. 1
14-15 We need to get the tile number from the data. We want to store that number into hl and clear the MSB h so that hl is really just an 8 bit number of the tile. This makes it easier to multiply by 8 to get the sprite offset. 3
16-18 Each sprite is 8 bytes long. Our tile value can be multiplied by 8 and added to the start of the tile sprites to get the start of the tile we want. We multiply by 2 three times. 2*2*2 is 8. 3
20-21 Now that we've got the number of bytes until we have the desired tile, we need to add that the the start of the tile sprite data. This will give us the start of the sprite we want in memory. 4
24 Each row of the screen has $10 bytes (16 bytes). We are going to have to add $10 each iteration to get the the exact same column one row down. 3
25 Since we are going to need b as another loop counter, we can temporarily save it into c which is faster than pushing it. 1
26 Each sprite is 8 bytes long. We need to copy those 8 bytes of the sprite image to the screen. 2
28 Ix is currently holding where on the screen we are displaying the tile map. We want to save where we are on the screen so we can draw the sprite for 8 rows down and then get back the address of the top row and increment it to the next column (byte) over. 2
30-31 We want to copy a byte from the sprite image to the screen. 2
32 De contains $10 (the number of bytes in a row) from line 24. We add it to where we are currently to move down one row. 1
34 As stated in the explaination of line 28, we need to incrememt the column so we move right one. 1
36 Repeat drawing the sprite row by row another seven times. 2
37 Now that we're done copying the sprite to the screen we need to get back the row and column of where we started copying the sprite from, as stated in the explaination of line 28. 2
39 Now that we're done drawing the tile, we need to get back our column counter. 1
40 Get back where we are in the tile map level data. 1
42 Increment where we are in the tile map level data so we are now on the next tile. 1
43 Increase where we are in the video memory so we are one column over to the right. 2
44 We need to repeat the drawing of the columns. 2
45-46 We need to move down one whole tile's worth of rows. Since we are already on row 1 of the tile, we only need to move down 7 rows since each tile is 8 rows high. We add 7 rows of $10 bytes (16 bytes in each row of the sreen) to the current position in the video memory. 5
47 Get back our row counter. 1
48 Repeat another 7 times to get the remaining rows. 2

This routine can be modified. If you want to have the routine draw the tile map to the graph screen so you can use a virtual screen, just change line 6 to ld ix,_plotSScreen.

Let's say you want to have a status bar at the bottom of the screen. Just change line 7 to ld b,7 and it will leave the bottom row empty. You could also do the above change and move the start address indicated by line 6 to start one row down so you can have your status bar along the top. If you wanted to have your status bar along the right, it gets a little bit trickier. You need to edit lines 11 and 45 to account for not writing to the right most column. Line 45 will have to add 7 rows to go down another row and also over one column since the previous time around it didn't make it to the right most edge.

You can have multiple tile sets by adding a few lines at the beginning to accomodate the routine so that de is loaded with the address of the desired tile set to use. The routine would edit line 20 to write in that address instead of "tile0" with the new address of the first tile in the new set.

	ld (address of line 20+1),de
Make sure to put a label above line 20 to so that you can reference it in the above code.


More from z80 » Graphics
Find a Pixel // Grayscale // Pixel Manipulation // The Screen // Sprites // SDR8 Routine // Tile Maps // TileGen Routine