Best viewed in 1024x768

Tilemaps

Tilemaps are a way of creating complex levels without alot of data.  A titlemap is basically a collection of numbers that represent what sprite to draw at that location.  The tilemap data for the above screenshot is below.  Note that the hero and the bullet are not represented on the tilemap.  The tilemap data only contains the "map" sprites to be drawn:

 .db $27,$00,$00,$00,$00,$00,$00,$00
 .db $28,$00,$00,$00,$80,$00,$00,$00
 .db $29,$00,$00,$00,$00,$00,$82,$00
 .db $89,$00,$81,$00,$00,$00,$00,$00
 .db $89,$00,$00,$00,$00,$00,$00,$00
 .db $89,$00,$00,$00,$00,$80,$00,$28
 .db $27,$87,$85,$85,$85,$85,$83,$29
 .db $29,$88,$86,$86,$86,$86,$84,$27
 .db $27,$28,$00,$00,$00,$00,$00,$28
 .db $8c,$8b,$00,$00,$00,$80,$00,$27
 .db $8c,$8b,$80,$00,$00,$00,$00,$27
 .db $03,$8b,$00,$00,$00,$00,$27,$29
 .db $04,$02,$00,$00,$81,$00,$28,$27
 .db $05,$02,$00,$00,$00,$00,$28,$29
If you look at the numbers and the picture closely, you can see that the numbers correlate to the sprites in the picture.  Please note that the format for this tilemap is kind of different than what you would expect. It goes bottom to top, left to right, instead of the "traditional" left to right, top to bottom.  The format was chosen because it simplified the coding for this game.  There will be many times that you will redo your data, and probably not just once, to make it easier on your routines while programming.  Data should fit the code, not vice-versa.

Notice how the same numbers are used for the same sprites.  The right part of the column in the middle is made up of sprite $85 and the left part is made up of 86.  The rocky ground is made up of sprites $27 to $29.  The water is made up of $89, the lava of $8c and $8b. This is one huge advantage of tilemaps.  A small set of sprites can be put together in many, many combinations over and over to create huge levels.  This is the technique used by many of your favorite games: Sqrxz, Super Mario 86, Insane Game, Slippy, etc.

Screen Sized Tilemaps

The simplest types of tilemaps are ones that are the size of the screen. You see these in games like Slippy, Vexed, etc.  These could be used for an RPG as well, if you wanted to use "paged scrolling".  Pages scrolling is where the screen changes to a different screen whenever you get to the edge.  This kind of scrolling is much easier to implement than smooth scrolling and allows you to use more tricks to save space than is possible with a side scroller.  It is also much faster and easier to draw because all of the Sprites are aligned. The screen is made up of 8 by 16 sprites (using 8x8 sprites):

Map:
 .db $8,$8,$3,$5,$1,$7,$0,$9,$2,$6,$9,$7,$4,$0,$5,$3
 .db $9,$3,$7,$6,$6,$8,$5,$1,$7,$4,$1,$5,$2,$6,$4,$5
 .db $4,$2,$1,$7,$4,$3,$1,$6,$8,$8,$5,$3,$7,$6,$5,$3
 .db $2,$4,$3,$0,$4,$3,$2,$8,$2,$7,$7,$5,$9,$3,$1,$4
 .db $0,$9,$8,$7,$9,$6,$6,$0,$8,$6,$4,$2,$6,$5,$1,$4
 .db $3,$2,$3,$6,$0,$9,$1,$1,$6,$1,$0,$5,$9,$4,$5,$3
 .db $4,$9,$8,$1,$2,$1,$4,$7,$2,$5,$6,$3,$6,$4,$1,$8
 .db $3,$4,$3,$0,$8,$0,$0,$9,$6,$2,$6,$7,$3,$0,$1,$9
Each byte of the map stands for the sprite to draw at that position. The map goes left to right, top to bottom.  This is a good configuration because it simplifies creating the map, especially if it is made by hand and not with an editor.  The sprites for the map will normally be in a contiguous block, meaning each sprite will follow right after the one before it:
Sprites:

Sprite0:
        .db %00000000
        .db %00000000
        .db %00000000
        .db %00000000
        .db %00000000
        .db %00000000
        .db %00000000
        .db %00000000

Sprite1:
        .db %00000000
        .db %01111111
        .db %01000000
        .db %01000000
        .db %01000000
        .db %01000000
        .db %01111111
        .db %00000000

...
Having the sprites is just about the only possible way they could be.   If they were all scattered out, the DrawMap routine would be a nightmare to program.  The basic idea of drawing the screen is to go through the map tile by tile, multiply the current tile by the 8 (the size of the sprite in bytes), add that to the start of the sprites (which gives us the correct sprite to draw), determine the correct location on the screen then finally draw the sprite.  If we were doing this in a high level language, it would look something like the pseudo code below:
FOR y = 1 to 8
    FOR x = 1 to 16
        sprite = Sprites + (Map[y][x] * 8)
        GridPutSprite(x, y, sprite)
    NEXT x
NEXT y
Of course, doing this in assembly takes a little more thinking, but it's not all that different.  The basic idea stays the same, but the implementation is based around the actual assembly instructions.  I would never say that my routine is the fastest way to draw a screen sized tilemap, but it is pretty fast:
;============================================================
; DrawMap -- draws an 8x16 tilemap  [Assembly Coder's Zenith]
;  input: HL = pointer to tilemap to draw
;  total: 34b/91529t (including ret and entire GridPutSprite)
;============================================================
DrawMap:
 ld de,$0000            ; start drawing at (0, 0)
DrawMapL:
 push hl                ; save map pointer
 ld l,(hl)              ; get the current tile
 ld h,0                 ; clear the upper byte
 add hl,hl              ; multiply by 8 (* 2)
 add hl,hl              ; * 4
 add hl,hl              ; * 8
 ld bc,Sprites          ; load the start of the sprites
 add hl,bc              ; add togehter to get correct sprite
 push de                ; save sprite draw location
 call GridPutSprite     ; draw the sprite
 pop de                 ; restore sprite draw location
 pop hl                 ; restore map pointer
 inc hl                 ; go to next tile
 inc e                  ; move location to the right
 bit 4,e                ; if the 5th bit (e = 16), we're done
 jr z,DrawMapL          ; only jump if the row isn't complete
 ld e,0                 ; draw at the left again
 inc d                  ; move location down a row
 bit 3,d                ; check if it's done (e = 8)
 ret nz                 ; return if we're done
 jr DrawMapL            ; jump back to the top of the loop
The code to call the routine would look something like this:
 ld hl,Map              ; point to the first byte of the map
 call DrawMap           ; draw the map
An example of all of this can be found here.

Working with Tilemaps

Tilemaps can be thought of as an array of memory.  Each "element" is the value of the sprite to draw for that location.  To access element N of the tilemap, you need to add N to the starting address of the map:

; input: a = number of tile to get
; returns: hl = pointer to tile
GetTileN:
 ld e,a                ; load N into low byte
 ld d,0                ; clear the high byte
 ld hl,Map             ; load the starting address of the map
 add hl,de             ; add the offset (N) to the map's address
 ret                   ; return

This routine is the most basic of tilemap routines.  It will most likely not be useful, but is very important for understanding the concepts.  The offset N is passed to the routine.  It is loaded into the low byte of a 16-bit register, then the high byte of the register is cleared.  The address or starting location of the map is loaded, and the offset N is added to it.  The pointer to tile N of tilemap Map is returned in HL.  The address would be used to load data from the map or modify the data already there.  

Interacting with Sprites

Now, I'm sure that the first thing you want to do after you draw the map is to have a little guy walk around on the map.  I'm going to assume that you are familiar the concept of sprites (drawing, moving, erasing, etc.).  If not, then check out the sprites section.  Therefore, I'm not going to load this tutorial with more sprite stuff than is necessary.  We'll concentrate more on the actual tilemap stuff.

Moving a sprite around on a tilemap isn't very different from moving a sprite around normally.  It just involves a little more checking.   Since most putsprite routines use BC for the X,Y coords of the sprite, that's what we'll use here.  The most used function for tilemaps will probably be GetTile.  This routine returns a pointer to the tile at the sprite coords BC.  So, if you have a sprite at (8, 9), it will return tile 17, or the 18th tile.  Since tiles are 8x8, the tile number can be found by this formula:

 tile = ((y / 8) * 16) + (x / 8)

The X and the Y coords are divided by the size of the sprite, which is 8.  The Y coord is then multiplied by the width of the map, 16, and added to the X coord.  The first thing that should jump out at you is that Y is divided by 8, then multiplied by 16.  So why divide in the first place, my not just multiply by 2?  Good question.  The divisions are integer divisions, meaning that the result is truncated.   So what really happens is that Y is rounded down (truncated) to the nearest 8, then multiplied by 2.  In assembly terms, this means that the lower 3 bits are masked off.  The assembly implementation of this routine is a good example of shifting for multiplication and divsion:

; bc = coords to get tile at (x, y)
; returns: hl = pointer to map tile
GetTile:
 srl b             ; divide x by 2
 srl b             ; / 4
 srl b             ; / 8
 ld a,c            ; get y coord
 and %11111000     ; clear lower 3 bits (truncate to 8)
 add a,a           ; y * 2
 add a,b           ; add to x (was divided by 8)
 ld l,a            ; load into low byte
 ld h,0            ; clear high byte
 ld bc,Map         ; point to start of the map
 add hl,bc         ; compute offset in map
 ret               ; return

You would call this routine with your sprite coordinates in BC.   It will return a pointer to the tile that the sprite is over in HL.   A note about this is that the sprite's coords are not actually the center of the sprite, but the upper-left corner.  If you want the center of the sprite, then you would center the coords first:

; bc = coords (x, y) to be centered
; returns: b + 3, c + 3
CenterCoords:
 ld a,3        ; the coords are off by 3 pixels
 add a,b       ; add 3 to the x coord
 ld b,a        ; save the x coord
 ld a,3        ; again, need to add 3
 add a,c       ; now center the y coord
 ld c,a        ; save the new y coord
 ret           ; all done

Perhaps the most interesting thing about assembly is that there is always another way to to do things.  If you don't mind trashing HL, you can speed up the above routine by 1 t-state and make it 2 bytes shorter:

; bc = coords (x, y) to be centered
; returns: b + 3, c + 3
CenterCoords:
 ld hl,$0303   ; add 3 to each of the coords
 add hl,bc     ; add to the coords
 ld b,h        ; save the x coord