This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Attributes with 32x32 Metatiles

Attributes with 32x32 Metatiles
by on (#60238)
I am working on a 32x32 metatile background engine, but started thinking about this when I started making some dummy data to test with. Would it be better to store the attribute byte with the metatile, or separate from it? As an example, if I were to store it like this:

; 32x32 metatiles = tile_name: four bytes for 16x16 metatiles and one byte for the attribute table
   .byte $00,$00,$00,$00,$00
   .byte $01,$02,$00,$00,$00
   .byte $00,$00,$01,$02,$00

... I would then have the attribute attached to the end of each tile. There is also the option of this:

   .addr tile32_001, tile32_001, tile32_001, tile32_001, tile32_001, tile32_001, tile32_001, tile32_001
   .addr tile32_000, tile32_000, tile32_000, tile32_000, tile32_000, tile32_000, tile32_000, tile32_000
   .addr tile32_000, tile32_000, tile32_000, tile32_000, tile32_000, tile32_000, tile32_000, tile32_000
   .addr tile32_000, tile32_000, tile32_000, tile32_000, tile32_000, tile32_000, tile32_000, tile32_000
   .addr tile32_000, tile32_000, tile32_000, tile32_000, tile32_000, tile32_000, tile32_000, tile32_000
   .addr tile32_002, tile32_002, tile32_002, tile32_002, tile32_002, tile32_002, tile32_002, tile32_002
   .byte $00, $00, $00, $00, $00, $00, $00, $00
   .byte $00, $00, $00, $00, $00, $00, $00, $00
   .byte $00, $00, $00, $00, $00, $00, $00, $00
   .byte $00, $00, $00, $00, $00, $00, $00, $00
   .byte $00, $00, $00, $00, $00, $00, $00, $00
   .byte $00, $00, $00, $00, $00, $00, $00, $00

I'm starting to think that maybe I should store them separate, because there is the possibility of compressing the attributes. I don't know which is really more beneficial, or if I'm overlooking anything. Any thoughts?

by on (#60240)
SMB1 does it the first way, but it hardcodes the attribute number as equal to the upper two bits of the metatile number.

by on (#60241)
It sure is nice to work with 32x32 metatiles so that you can store the attribute bytes already formed, it pretty much eliminates the complexity of messing with attribute bits. If you scroll vertically you still have to deal with nibble manipulation, but I don't think this is your case.

Anyway, I think it's best to keep the attributes as part of the information of metatiles. If you stored them separately for each screen, you'd be storing a lot of repeated information, because you would have to specify the exact same attribute byte for each instance of the metatile that uses it, something that would probably kill the gains from any compression scheme you used.

If you think about it, making that information part of the metatiles *IS* a form of compression. Instead of repeating certain combinations of attribute bits over and over you are assigning them to the blocks that commonly use them. If you have 256 metatiles, there will be 256 attribute bytes. That same space would only be enough to represent 4 raw attribute tables. So if your level has more than 4 screens you are already wasting space that way.

You will probably save a lot of space by compressing the metatile layout for each screen though.

by on (#60242)
tokumaru wrote:
... you would have to specify the exact same attribute byte for each instance of the metatile that uses it, something that would probably kill the gains from any compression scheme you used.

Yeah, that would eat up more space. Sounds like attaching it to the metatile is the best way to go. Thanks!

That's an interesting way to do the attributes in SMB1. It seems like that would be something you would do if you knew you had xx amount of metatiles to deal with from the get go (design stage of development!). Does anyone know how many metatiles it has total?

by on (#60243)
SMB1 has fewer than 256 metatiles and fewer than 64 metatiles with each attribute value. This means it can use four metatile to hardware tile tables, one for each attribute value, each shorter than 256 bytes. (Source: Doppelganger's disassembly)

by on (#60244)
I was thinking of using a method down the road that went like this:

Since under most circumstances there can only be 4 palettes, as long as you have less then 64 meta-tiles total you can steal 2 bits from each byte to assign the palette.

so if i had "%01000001", the first 2 bits are the attribute data (palette 2), the next 6 bits assign the tile as meta-tile number 1. Use a bitmask to split the byte.

As long as you have less then 64 meta-tiles, your attributes are "free" (minus the code to split them).

by on (#60265)
I don't think there is a correct or wrong way to do it. It's often a trade between compact and flexibility.[/img]

by on (#60279)
For sure, you're right Bregalad! It's always nice to see other perspectives and ideas though, of course.

I was just laying in bed (long night last night) and had a thought. The way I have it set up now, I have two bytes for each address for each 32x32 tile. I basically did this so it wouldn't matter if I happened to go over 256 metatiles for some reason. What if instead, I employed the technique that Tokumaru showed me when we were discussing 16x16 metatiles? I could cut the size of each screen in half! I suppose if I thought I was going to go over 256 tiles, I could employ some sort of jump table that decides which batch of tiles to use (so they'd be grouped):

; rocky tiles
; 00-transparent tile, 01-right rock, 02-left rock
   .byte $00, $00, $02 ; etc.
   .byte $00, $01, $00 ; etc.
   .byte $00, $00, $02 ; etc.
   .byte $00, $01, $00 ; etc.
   .byte $00, $55, $55 ; etc.

; watery tiles
; 00-left wave, 01-right wave
   .byte $01, $04 ; etc.
   .byte $00, $05 ; etc.
   .byte $01, $06 ; etc.
   .byte $00, $07 ; etc.
   .byte $65, $56 ; etc.

... and this would make a seemingly endless supply of metatiles. The RTS trick would decide which ones to point at I guess:

   lda #<rocky_a
   sta pointer_a
   lda #>rocky_a
   sta pointer_a+1
   lda #<rocky_b
   sta pointer_b
   lda #>rocky_b
   sta pointer_b+1
   lda #<rocky_c
   sta pointer_c
   lda #>rocky_c
   sta pointer_c+1
   lda #<rocky_d
   sta pointer_d
   lda #>rocky_d
   sta pointer_d+1
   lda #<rocky_attribute
   sta pointer_attribute
   lda #>rocky_attribute
   sta pointer_attribute+1

; similar code for other sets of tiles (since it would be the same code used over and over for each, maybe there will be some crazy way I can reuse the code or something)

I think that would work okay. Of course the whole jump table thing would only have to be deployed if I happened to go over 256 metatiles. But, I definitely like the idea of trimming down the tile data for each room from 192 bytes to just 96 bytes!

by on (#60280)
What I have for my game, and I think it is pretty much optimal, altough I might be wrong, is as follows :
I have 32x32 metatiles which are made of 4 16x16 metatiles, attribute data and collision data for each 16x16 metatile. There is 32 16x16 metatiles, the first 16 are customizable, while the last 16 are hardcored tile values (I did that to save ROM space as much as I could). So it is 4 bits * 4 for metatile #, 1 bit * 4 for selecting between hardcored or customizable metatile, 4 bits for collision and 8 bits for attribute which makes a great total of 4 bytes for a 32x32 metatile.
Although it's just my system - color and collision data comes with the 32x32 block itself, not with the 16x16 blocks it uses - it might sound weird but I think it makes it more compact, although I haven't made complex analysis of it.

by on (#60283)
Roth, it seems you have described exactly what I do in my game. For each level I define a set of pointers indicating where to get the block data from. This allows for 256 metatiles per level, which should be enough. But even if it isn't, nothing prevents me from changing the pointers halfway through the level, as long as the blocks that are on screen at the time are defined in both sets.

In fact I could easily do what they do in Sonic 3 (& Knuckles), where there are no cuts between acts, each act is like a direct continuation of the previous. All I'd have to do is draw the place/room where the transition happens to both level maps, and adjust the positions of the objects (including the camera) to put them in the new place/room when the pointers to level data are changed. I could even do this seamlessly to create insanely large levels. I don't think I will though! =)

by on (#60291)
For my current project which doesn't scroll vertically, I do something similar to what has been described with 32x32 pixel tiles made of 4 16x16 metatiles. But instead of using 32x32 pixel metatiles, I use 64x16 pixel metatiles, which are still made of 4 16x16 metatiles. I assign collision information to the 16x16 tiles, but I assign attribute information to the 64x16. In my experience, it is much better to assign the attribute information to the 32x32 or 64x16 pixel metatiles, mainly because it saves space and time because it's simple. Also you may find you have more versatility since you can choose to color any 16x16 metatile whatever you want; you don't have to define another 16x16 metatile just so you can have a different color.

Also, I thought I should point out a good reason for using 64x16 metatiles instead of 32x32. The reason I decided to go with 64x16 rather than 32x32 is because of my screen size and the fact that I don't scroll vertically. The status bar takes up 32 pixels on the top of the screen, meaning 240 - 32 = 208 pixels of screen height is left, which isn't divisible by 32 pixels (so only half of the bottom 32x32 metatile would be displayed). It actually saves 4 bytes per screen definition to use my sized tiles, since each screen is made of 52 64x16 metatiles rather than 56 32x32. And to make things better, it is MUCH easier to define general purpose 64x16 metatiles than it is to define general purpose 32x32 metatiles. Though it does make updating attributes more complicated, it's a sacrifice I'm willing to make for saving space and making level design easier.

by on (#60301)
Well 64x16 metatiles sounds weird to me, but if it works for you why not.
My status bar is also 32 pixels tall, but there is also 16 pixels of overscan, which makes it 6 rows altogether, I'm left with 24 rows of tiles which make a screen 8x6 32x32 metatiles.

by on (#60324)
Rygar used vertical strips of 16x16 metatiles, so weird shapes aren't really that weird.

by on (#60325)
Dwedit wrote:
Rygar used vertical strips of 16x16 metatiles

The Legend of Zelda used vertical columns as well.

Another technique is to make a special short code for the metatile that's most likely to be below a given metatile. For example, you could set up a table
sky -> sky
grass -> dirt
dirt -> dirt
water surface -> water below surface

Then you can encode a column of "sky, sky, sky, grass, dirt, dirt" as "sky, follow, follow, grass, follow, follow".

by on (#60329)
Well I guess 32x32 metatiles are better for screen-at-a-time games like Zelda or other top-down games like it. I can't really pinpoint why it is, but I just had a really hard time trying to reuse 32x32 tiles in my side scroller. Oh! I also know why I used 64x16 metatiles. My levels are made of 13 rows of tile data, and each row can be 256 metatiles long. Since my game scrolls only horizontally, making the tiles 64x16 allows me to have levels up to 64 screens long rather than 32, which is what my limit would be if I used 32x32 metatiles. I guess though that I'm never going to have a level that's 64 screens long, as this is an NROM project and that would take up a lot of space just for one level. I also plan to have action on each screen make up for small levels.

by on (#60334)
Battletoads used 32x32 metatiles.

by on (#60358)
So did Castlevania and Mega Man games I belive.

by on (#60503)
Well even if they did, I still like my 64x16 metatiles! :)

by on (#60511)
Celius wrote:
Well even if they did, I still like my 64x16 metatiles! :)

And they seem appropriate for your particular situation. You shouldn't worry if people say your methods are non-standard. In homebrew scenes there is usually a lot of copying and little inventing, so congratulations to you for being a person that invents.