Last time I talked about how I store walls in my map data for Explorer. Today I’m going to talk about how I store tiles for the room interior.

After the walls are put down, there is a 12×11 field of floor space for me to work with for the room interior. This picture should make it clear:

The room interior is 12x11 metatiles big

The room interior is 12x11 metatiles big

The basic way I place a tile on the map interior is with a coordinate byte. A coordinate byte has the x-coordinate in the left nibble and the y-coordinate in the right nibble. For example, let’s say I want to place a tile in the spot designated by the blue square in the following screenshot:

The blue square is at coordinate (8, 7)

The blue square is at coordinate (8, 7)

This spot is 8 to the right and 7 down, so the coordinates would be (8,7). The byte I use to represent this position is $87. If I wanted to place a tile in the very top-left square, I’d use the byte $00. The bottom-right-most square is $BA. So using the values $00-$BA, I can represent any single position on the map interior.

It would be wasteful if I declared each and every tile one byte at a time. I should try to save bytes by somehow compressing repeating data. I currently do this by drawing lines. After a coordinate byte in the room data, there can be an optional run byte. A run byte tells the map engine which direction to draw a line and how many tiles long that line should be. The left nibble represents the direction:

C=Right
D=Down
E=Left
F=Up. (That’s F’ed up.)

The right nibble tells how long the line should be.

For example, let’s say we are drawing block tiles. In our room data we encounter the bytes: $11, $CA. The first byte says “place a tile at (1,1).” The second byte says “draw a line to the right, 10 tiles long.” This screenshot shows the result of these two bytes:

2 bytes make a line of tiles

2 bytes make a line of tiles

Run bytes can be tagged onto the end of other run bytes. In this case, the starting point of the new line is the ending point of the previous line. Check out these five bytes: $11, $CA, $D9, $EA, $F8. If you were to translate these bytes into English, they’d say:

“Place a tile at (1,1), then draw a line to the right, 10 tiles long. Then draw a line down, 9 tiles long. Then go left 10 tiles, and finally up 8 tiles.”

The result:

5 bytes makes a box

5 bytes makes a box

Implementation

My test data currently has 7 possible tiles it can use in the room interior. They are:

.enum
    floor
    wall
    block
    water
    block_breakable
    stairs_up
    stairs_down
.endenum

I store my room data as a series of strings. The first byte in the string tells which tile to use. Next comes a series of coordinate bytes and run bytes. Finally, a string is terminated with $FF. A list of strings is also terminated with $FF. Here’s are some example strings:

    .byte block, $11, $CA, $D9, $EA, $F8, $FF
    .byte water, $22, $C8, $D7, $E8, $F6, $C7, $D5, $E6, $F4
        .byte $C5, $D3, $FF
    .byte stairs_up, $00, $45, $FF
    .byte block_breakable, $02, $20, $FF
    .byte stairs_down, $BA, $66, $FF
    .byte $FF  ;no more strings
    

That data will generate this room interior:

A full room.  Careful, those steps might be slippery.

A full room. Careful, those steps might be slippery.

Here is my code that parses the inner room data.

    ldy #$ff
@mainloop:
    iny
    lda (temp_ptr1), y  ;which tile?
    cmp #$FF
    beq @end            ;FF terminates the room
    sta map_temp1     ;store tile number in a temp variable
    iny
    lda (temp_ptr1), y ;coordinate byte
@innerloop:
    iny
    jsr calc_room_position ;will take the coords in A and 
                           ;calculate an offset, output in X
    lda map_temp1    ;our tile number
    sta room, x

@run_check_loop:
    lda (temp_ptr1), y  ;peek at the next byte
    cmp #$FF
    beq @mainloop   ;FF terminates a tile string, so loop to 
                    ;the next tile string
    cmp #$C0
    bcc @innerloop  ;if less than $C0, there is no tile run, 
                    ;so loop back to get a new coordinate byte
    jsr tile_run  ;else we have a run.  This will take the 
                  ;run byte in A and draw a line in our RAM 
                  ;copy of the room.  We still have x loaded
                  ;with our offset.
    iny

    jmp @run_check_loop  ;check for another run byte

@end:
    rts

Conclusion

I don’t know if this is the best way to do it, but it works! I’m always looking to improve, so if you have any better ideas for compressing the room data, let me know in the comments!

Speaking of better ideas, Roth left a comment on my last post with a cool idea for saving bytes on my wall data. I’m talking hundreds of bytes. I’ve already implemented it and I’ll talk about it next time. In the meantime, feel free to subscribe to the RSS feed.

2 Responses to “Explorer: 4 – Inner Room Data”

  1. Rob says:

    Interesting, it’s like a kind of RLE, but I always think of using that in terms of left->right. It’s neat to see it used for blocks going in all directions!

    So how do you handle room-to-room? When changing rooms, do you reload the base template, grab the wall data to write, then do the interior data? If that’s the case, do you know how many frames it’s taking, by chance?

    I’m also curious if you have already planned the entire game on paper, or if you have started doing the coding, then plan to make out the puzzles as you go. Basically, I like to hear how other people approach the whole dev life-cycle dealy-o hehe

    I’m loving this blog Thomas (Tom?, Metalslime?, not sure if you have a preference)! Also, thanks for the kudos : )

  2. Thomas says:

    @Rob: I’m thinking about adding an opcode that straight up draws a box and fills it. Could build a lot of tiles using only 3 bytes (top left corner, box opcode and bottom right corner).

    As for room-to-room, you got it exactly. I have no idea how many frames it takes, but the new room comes on screen faster than I can snap. It’s something I ought to keep an eye on though, especially when I add objects and object logic to the rooms.

    I don’t have the entire game planned on paper. I have an idea of how I want the gameplay to be and how the player will interact with the environment, but I’ve only designed one test level. Once I have the basic game engine finished I’ll work more on level design. At some point I’ll want to make a map editor utility, but this early in the game I’m still making changes to how the data is stored which makes writing a map editor difficult.

    I do make sure I have a plan before I start coding though. I like to know exactly what I’m going to write before I write it. I work out data structures, logic, algorithms, etc on paper first and then when I feel like I understand what I want to do I code it up.

    Thanks for the encouragement! I’m glad you like the blog. btw, I don’t really have a preference what people call me.

Leave a Reply