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.

I put off doing attributes for a long time because I didn’t want to face the nightmare. When I finally sat down to do it the other day, it was extremely fast and easy to implement. The reason? No scrolling.

What’s an Attribute?

The NES has 4 screen-sized pages that you can draw background tiles to. These pages are called nametables. Each nametable has an attribute table associated with it. An attribute table specifies what palettes are used for the different tiles in the nametable. So basically the nametables control what tiles are seen, and attribute tables control what colors those tiles are.

The NES PPU (Picture Processing Unit) maps the attribute tables to the nametables in a very unintuitive way (see this tutorial), which makes working with attribute data a real pain. This is especially true when scrolling is involved as you may have portions of two different nametables on the screen at the same time. The ultimate nightmare is four-way scrolling where you can cross horizontal and vertical screen boundaries at the same time. I foolishly attempted a four-way scrolling engine as my first NES project and attribute tables left a bad taste in my mouth. So I wasn’t looking forward to dealing with them again.

Leftover attribute nightmare from my first attempt at a 4-way scrolling engine.

Leftover attribute nightmare from my first attempt at a 4-way scrolling engine.

But as I mentioned, attributes aren’t so bad when there is no scrolling going on. The Explorer game I’m writing shows one room at a time, and I don’t think scrolling will be necessary when changing rooms. I think I will be able to get away with turning off the PPU, drawing the new room in one go, and then turning the PPU back on. This will cause the screen to black out when changing rooms, but only for a second. I don’t think it will be too distracting. We can take a peek at Sivak’s platformer demo to see how this method looks:

Looks just fine to me! So Plan A is to ditch scrolling altogether and instead toggle the PPU off and on when changing rooms. This should save me some headaches when dealing with the attribute tables.

Implementation

When I load a new room, I turn off the PPU. Then I draw the room. Then I color the room (write the attribute data to the attribute table). Then I turn the PPU on. The trickiest part of this process is figuring out what to write to the attribute table.

My test data consists of rooms built from seven possible 2×2 metatiles (16×16 pixels). The first thing I do is assign each metatile a palette number. I don’t like to hardcode any number values in my data, so I give everything an alias using ca65’s .ENUM directive. First the tiles ids, which are used in the map data:

.enum
    floor       ;these are my tiles, numbered $00-$06
    wall
    block
    water
    block_breakable
    stairs_up
    stairs_down
.endenum

Then the palette ids:

.enum
    orange ;my four bg palettes from $00-$03
    gray
    blue
    brown
.endenum

Finally, the attribute assignments for each tile in order:

tile_attribs:
    .byte gray    ;floor
    .byte brown   ;wall
    .byte orange  ;block
    .byte blue    ;water
    .byte orange  ;block_breakable
    .byte gray    ;stairs_up
    .byte gray    ;stairs_down

With this setup, I can read a tile number from my map data and then use that value as an index into the tile_attribs table. Once I’ve read the palette number from the tile_attribs table, I can do a shift/roll combo to sneak that 2-bit value into an attribute byte:

;-------------------------------
; make_attrib takes a map index in x and creates an attribute
; byte for the metatile box (32x32 px). x is the top-left tile.
make_attrib:
    lda room, x  ;top left
    tay
    lda tile_attribs, y  ;get the palette number
    sta map_temp2
    lsr map_temp2  ;shift a bit into the carry flag
    ror map_temp1  ;roll the carry flag into a temp var
    lsr map_temp2  ;and repeat for the second bit
    ror map_temp1

    lda room+1, x  ;top right
    tay
    lda tile_attribs, y
    sta map_temp2
    lsr map_temp2
    ror map_temp1
    lsr map_temp2
    ror map_temp1

    lda room+16, x   ;bottom left
    tay
    lda tile_attribs, y
    sta map_temp2
    lsr map_temp2
    ror map_temp1
    lsr map_temp2
    ror map_temp1

    lda room+17, x     ;bottom right
    tay
    lda tile_attribs, y
    sta map_temp2
    lsr map_temp2
    ror map_temp1
    lsr map_temp2
    ror map_temp1
    rts
    

Looping across the room data I can build a full attribute table in RAM. Then I can simply copy it over to the PPU when PPU rendering is off. It works!

Before:

A room before attributes

Here's what a room looks like before attributes

After:

A room after attributes

Here's how the room looks with proper attribute data.

Please forgive the ugly tiles. I’m no artist. They will all be replaced in the future. :)

Conclusion

Attributes are done! Room decompression works! I can display rooms correctly! The next step is to design a multi-floor, multi-room test level and make it possible to navigate between rooms. That will likely take me some time, so my next few posts will be about my room data format.

The game I am currently working on is called Explorer. That’s the working title, not the final one. I originally started to work on it as an entry to a Retro Coding Competition, but I got busy and couldn’t make the deadline.

Story and Gameplay

Explorer is a puzzle game. Not the kind of puzzle game where you stack blocks, but the kind where you navigate a labyrinth. The basic storyline is this:

You are a guy who has been one-upped all your life by a clever, handsome, ambitious man who is now a world-famous archaeologist. You decide that the only way you can make up for a lifetime of failure and humiliation is to finally get a one-up on him. You must beat him at his own game. To do that, you follow this archaeologist around the world, sneaking into his excavation sites and stealing the ancient treasures hidden within before he can discover them.

The game will be a series of top-down labyrinths, similar to the dungeons in Zelda or the overland map in The Guardian Legend. Each room will take up a full screen and have exits on four sides to other rooms. There will be multiple floors, pitfalls, buttons/switches, locked doors, puzzles, traps, changing terrain, special items, etc. For each excavation site you will have to navigate your way through the labyrinth, find the ancient treasure, and get out without getting caught.

Specs

I’m programming the game using the UNROM mapper. This gives me:

  • 128k of PRG-ROM
  • CHR-RAM

128k of PRG-ROM is probably more than I’ll need. If I end up having a lot of free space, I’d like to include support for user-made labyrinths. I’m thinking I’ll include a map editor and a utility that will stick a user-made map right into the ROM file.

My assembler of choice is ca65, part of the cc65 package. You can find that here.

And much thanks to SecretServiceDude for posting his UNROM linker config on the nesdev forums. The one I use is based off of his.

Progress

As of today, I’ve written:

  • UNROM support
  • a basic music engine
  • the beginnings of a map engine

The map engine deals with a few types of data:

  • dungeon
  • floor
  • room

Each dungeon has one or more floors. Each floor has one or more rooms. A room is a represented by a single screen. Right now the map engine is capable of loading any room in a dungeon and displaying it on the screen. The biggest success for me here is that I am able to read and decompress the room data format I came up with. So far all of my test data is showing up correctly on screen.

See you next time where I’ll get into attribute data (coloring background tiles)!