Today I’m going to talk a little bit about the format I use to store my room data. In Explorer, the player views rooms from the top. The background doesn’t scroll at all when the player moves. Everything stays put until the player hits the edge of the screen. When that happens, a new room is loaded and drawn to the screen.

A consequence of this style of gameplay is that rooms will have many things in common. For example, they will all be the same size. They will all be enclosed by walls. They will all have gaps somewhere in the walls to allow passage between rooms. They will all have floor space.

To avoid having redundant data for every room in the game, I have a blank room stored uncompressed in ROM. It looks like this:

This is the default blank room.  It has 4 solid walls enclosing a field of floor tiles.

This is the default blank room. It has 4 solid walls enclosing a field of floor tiles.

When I load a new room, the first thing I do is copy this blank room into RAM. Then I read the data for the new room and write the new data over the copy of the blank room in RAM. This saves me a lot of bytes in ROM, as I don’t have to store all of those wall and floor tiles in the data for each individual room. I assume four walls and a floor and draw the extras on top.

Gaps in the Walls

The default room is nice, but it traps the player inside. There are no gaps in the walls. So my map data needs a way to tell me where to stick gaps so that player can navigate to other rooms.

Explorer is a puzzle game. It’s a maze. I can’t just have one exit per direction ala Zelda. I need each wall to potentially have several exits. Some will lead to dead ends, some will allow access to areas that others won’t. I want a lot of flexibility but at the same time I don’t want the wall-gap data to take up too much ROM space. There are going to be hundreds of rooms, so I want to keep them as compact as possible.

My current solution is to use 4 bytes, one per wall. Each bit represents a gap. Look at this picture:

Each wall has a byte telling where the gaps are.  The numbers in the picture show which bit represents which gap.

Each wall has a byte telling where the gaps are. The numbers in the picture show which bit represents which gap.

The numbers in the above picture show which bit controls which section of wall. For example, if my byte for the north wall of a room were #%10000001, the gaps marked 7 and 0 would be open, while 1-6 would be blocked, like this:

The north wall is represented by the value #%10000001

The north wall is represented by the value #%10000001

The first four bytes in a room’s data are the wall/gap data, in order of North, South, West, East. North and south have the same bit mapping scheme, as do west and east.

Let’s take a look at the room I posted up in my post about attributes:

A nice room

A nice room

The first four bytes in the room data are: %00100100, %00011000, %00011000, %00011000. Notice how the set bits correspond to the gaps in the room.

Code Sample

The code to generate these walls is pretty straightforward. First I read the byte from the map data, then I call a subroutine which will translate that byte into a buffer of wall/floor tiles. This subroutine just shifts out the bits one at a time and plugs the appropriate value into the buffer. Here’s the version for making north-south walls:

    lda (temp_ptr1), y ;read the top wall byte
    iny
    jsr room_build_ns_wall ;this fills a buffer which we can 
                           ;copy to the tile map in RAM

room_build_ns_wall:
    sta map_temp1

    ldx #$0B

@loop:
    cpx #$01  ;these four positions are always walls.
    beq @wall ;they are not controlled by the wall byte.
    cpx #$03
    beq @wall
    cpx #$08
    beq @wall
    cpx #$0A
    beq @wall

    lsr map_temp1 ;shift a bit out
    bcc @wall     ;if it's zero, wall
    lda #floor    ;else it's a floor
    beq @write
@wall:
    lda #wall
@write:
    sta room_wall_buffer, x
    dex
    bpl @loop ;end the loop at x==$FF
    rts

Once I have my buffer filled, I copy it onto the blank room in RAM.

The west-east wall version is almost identical, it just has different “always-wall” values.

If you are wondering what #floor and #wall are, they are just constants. I try to avoid hard-coding values whenever possible. My tile ids are declared using ca65’s .ENUM directive:

.enum
    floor
    wall
    block
    water
    block_breakable
    stairs_up
    stairs_down
.endenum

#floor will evaluate to #$00, and #wall will evaluate to #$01.

Conclusion

I think I can improve even further on this wall/gap format. There is a lot of redundant data still. For example, adjacent rooms will have the same byte values for their shared wall. If I can come up with a scheme to eliminate this redundancy, I can save even more bytes. That’s a battle for another day though. This format works for now and I’m going to move forward. But if you have any suggestions, please let me know!

Next time I’ll talk about the rest of my room format: ie, how I store tiles in the room interior.

5 Responses to “Explorer: 3 – Room Data”

  1. Rob says:

    Hey, I like the idea of the base template room. I could probably use something like that in !Clik!. There are no openings for each level, but maybe I could have a smaller map for each section (base room for level 1, smaller inside map for 1-1, smaller inside map for 1-2, etc.) Nifty!

    About the data redundancy deal, I’m not quite sure how this could be approached off the top of my head, but what about making a separate map? For instance, I would guess that you have an overall table map that describes what room is what:

    .db $30,$31,$20

    or whatever. What if there was a second map, but it was a mapping of the openings?

    .db %00100100, %00011000, %00011000, %00011000

    So when you go into whatever room, read from that offset and subtract/add to get to be able to get all four sides maybe? I don’t know, that’s the best I could come up with off-the-cuff haha

  2. Thomas says:

    @Rob: That’s a really good idea about the wall data redundancy! I worked it out on paper today and discovered I could get greater than 50% savings by doing what you suggested.

    It doesn’t look like it will be difficult to implement either. I’m going to have two tables: one for East-West walls and one for North-South walls. Having the two directions separated makes it easier to find an index based on floor (x,y) coordinates.

    The savings are huge. On a 3×3 (9 room) floor, using the old 4-byte per room method I’d need 36 bytes. But with the new method I’ll only need 12. And for other sizes:

    4×4: old = 64 bytes, new = 24 bytes
    5×5: old = 100 bytes, new = 40 bytes
    10×10: old = 400 bytes, new = 180 bytes
    20×20: old = 1600 bytes, new = 760 bytes

    I’m going to change it over ASAP! Thanks a lot!

  3. Rob says:

    Wow, after seeing the numbers like that, it _is_ great savings indeed. Nice way to capitalize on it by using two different tables (N/S and E/W)! Keep up the outstanding work : D

  4. [...] Tummai Games development blog for NES games « Explorer: 3 – Room Data [...]

  5. [...] my first post about Explorer’s room data format, I said that I used 4 bytes to store the wall/gap information for each room. After some thinking, I [...]

Leave a Reply