storing metatiles and their order

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

Post Reply
Kitty_Space_Program
Posts: 53
Joined: Mon Sep 21, 2020 12:51 am

storing metatiles and their order

Post by Kitty_Space_Program »

So I'm trying to learn how to store metatiles, and I've got them all written out like so:

Code: Select all


ground:
  .db $ef, $f0;first row
  .db $f0, $ef;second row
  .db $00 ;pallete
sky:
  .db $f1,$f1
  .db $f1,$f1
  .db $02
rock:
  .db $f1,$f1
  .db $f1,$f2
  .db $02
  
  
and I'd like to be able to organize my levels with .db and reference each of the tiles with a number, but I can't figure out a way to do so. My best guess is to have a byte array of 0, 5, a, &c. or split them up into 2 or three sections (where palletet and possibly the second row are stories separately, but both of those seem a bit unwieldy. Any ideas? I'm completely lost here. Thank you!
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: storing metatiles and their order

Post by tokumaru »

The 6502 is pretty bad at accessing arrays of structures like you have there... The most common solution is to use structures of arrays instead:

Code: Select all

TopLeft:
	.db $ef, $f1, $f1
TopRight:
	.db $f0, $f1, $f1
BottomLeft:
	.db $f0, $f1, $f1
BottomRight:
	.db $ef, $f1, $f2
Palettes:
	.db $00, $02, $02
This way you can use index 0 for ground, index 1 for sky, and index 2 for rock. If you need to automate this a bit so you have more freedom to move stuff around without breaking anything, most assemblers should let you use placeholders in the table and assign values elsewhere:

Code: Select all

TopLeft:
	.db M0T0, M1T0, M2T0, M3T0, ...
TopRight:
	.db M0T1, M1T1, M2T1, M3T1, ...
BottomLeft:
	.db M0T2, M1T2, M2T2, M3T2, ...
BottomRight:
	.db M0T3, M1T3, M2T3, M3T3, ...
Palettes:
	.db PAL0, PAL1, PAL2, PAL3, ...

Code: Select all

rock = 3
M3T0 = $f1
M3T1 = $f1
M3T2 = $f1
M3T3 = $f2
PAL3 = $02
This works even better if your assembler supports macros that can manipulate labels, like ca65 does. Then you can create one macro to generate the placeholders for you, and another macro to fill in the values for each metatile and "return" their index through a symbol. Creating metatiles could then look something like this:

Code: Select all

	CreateMetatileSet 42 ;reserves space for 42 metatiles
	RegisterMetatile $f1, $f1, $f1, f2, $02, rock ;assigns values to placeholders, copies index to "rock" and increments index
	RegisterMetatile $ef, $f0, $f0, $ef, $00, ground
	;(...)
Macros can make things very readable and easy to edit, while still outputting data in a format that's friendly for the CPU to use. In an assembler like ca65 you can go really crazy with your macros.
Kitty_Space_Program
Posts: 53
Joined: Mon Sep 21, 2020 12:51 am

Re: storing metatiles and their order

Post by Kitty_Space_Program »

Great, thank you. I would have never thought to do it like that lol. I’ll try that out tomorrow.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: storing metatiles and their order

Post by tokumaru »

Let us know how it goes.

BTW the same "rules" apply to several other types of data, such as object states: instead of using contiguous sections of RAM for storing the state of each object, it's better to have separate arrays for each attribute that objects have. Something like this (ca65 syntax):

Code: Select all

MAXOBJECTS = 16
ObjectType: .res MAXOBJECTS
ObjectXLow: .res MAXOBJECTS
ObjectXHigh: .res MAXOBJECTS
;(...)
Then you can easily loop through the active objects using an index register, where index 0 is used to access the properties of the first object, index 1 the properties of the second object, and so on. Every game engine I've ever seen in 6502 does it like this.

Whenever you can use parallel arrays in 6502 code, which's usually the case when the sizes of the structures is constant (e.g. all metatiles are the same size), do it. It's always easier and faster to access data using indexing in increments of 1 than to use dynamic pointers, multi-byte increments, and the like.
Kitty_Space_Program
Posts: 53
Joined: Mon Sep 21, 2020 12:51 am

Re: storing metatiles and their order

Post by Kitty_Space_Program »

tokumaru wrote: Tue Dec 08, 2020 1:39 am Let us know how it goes.
It went really well, especially considering there wasn't any tutorial lol. It took about 200 lines of assembly code for both nametables, I assume that's a lot, but it works. *knocks on wood*
puppydrum64
Posts: 160
Joined: Sat Apr 24, 2021 7:25 am

Re: storing metatiles and their order

Post by puppydrum64 »

Kitty_Space_Program wrote: Tue Dec 08, 2020 3:49 pm
tokumaru wrote: Tue Dec 08, 2020 1:39 am Let us know how it goes.
It went really well, especially considering there wasn't any tutorial lol. It took about 200 lines of assembly code for both nametables, I assume that's a lot, but it works. *knocks on wood*
In all honesty from personal experience, lines of code don't take up nearly as much space on the cartridge as you think they do. When I ported my Aquarium Simulator from NROM to MMC1, I found that the entire source code I had so far (including the uncompressed tilemaps) which was over 1000 lines of code, fit into $E000-$FFFF with room to spare! It's the data blocks that are the real hogs of cartridge ROM.

What will get you into trouble with storage is stuff like this:

Code: Select all

attribute_data:
	.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
	.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
	.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
	.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
	.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
	.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
	.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
	.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
This is incredibly wasteful since you're essentially recording the same piece of data 64 times! One way to compress this data is called run-length encoding. It stores the number of times you're writing a value, then the value you're writing. Here's one way of implementing this type of compression that I created myself. It's not perfect and I haven't tested it with partial rows. But here it is:

Code: Select all

attribute_title_rle			;Format: Number of consecutive tiles, tile ID.
	.db $40,$FF
attribute_title_rle_size:
	.db $02				;Format: How many entries are in the above table. 
Instead of writing out $FF a total of $40 times, I'm just going to use the $40 as a loop counter and write $FF to $2007 over and over. This takes more lines of code in the drawing routine of course, but you only spend those extra bytes ONCE. However using this method, and assuming that every tile on the screen uses the same palette (they won't of course but it's just for the math) you'll save 59 bytes PER ATTRIBUTE MAP!

I'm not sure if the size is a redundant property, I used it as a secondary loop counter so the routine knows when to end.
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: storing metatiles and their order

Post by unregistered »

puppydrum64 wrote: Mon May 31, 2021 10:39 am I'm not sure if the size is a redundant property, I used it as a secondary loop counter so the routine knows when to end.
Hmmm… size could be redundant if your secondary loop is only used for size #$40 attribute-bytes.

For instance:

Code: Select all

ldx #16;starting at 16 when below reads from screenArray-16 makes the first value read from screenArray+0; it’s easier to run 240 times
- lda screenArray-16, x
sta $2007
inx;11-80
bpl -; :)
That’s one of the two tiny loops; the second one just ldx #$80, ends with a bne -, AND its inx runs from 81-00.

The second loop is the same otherwise… it does lda screenArray-16, x. That makes no sense, unless you run the math in your head. It works wonderfully. 🙂

Before those two small loops there’s a zeropage flg that determines what loop to run. If the branch is taken, it’s the second time reading from new screenArray. :)


Hope this gives you ideas. :)
puppydrum64
Posts: 160
Joined: Sat Apr 24, 2021 7:25 am

Re: storing metatiles and their order

Post by puppydrum64 »

I like the trick with -16 which lets you avoid the "CMP tax" since you never need it for 0 or 80. That's clever. But I'm not sure how this applies to attribute data which is only $40 bytes at most. Tile drawing maybe.
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: storing metatiles and their order

Post by unregistered »

Well, my trick doesn’t work for #$40… sorry. :( see post below for correction

But, if the attribute data is always size #$40, then just cmp #$40. The compares, (cmp, cpx, cpy), with immediate values each take only 2 cycles, so that’s pretty good. :)


Your size for RLE seems good now, but can be cumbersome to adjust if you ever have a lot of RLE values.

Note: If you are using asm6 and attempting to code many RLE values, forget about .db and use asm6’s .hex. Others have encouraged use of binary files… but .hex works for me if each .hex line is limited to a max of 64 hex codes. asm6 has problems translating .hex lines containing 65 or more bytes. With text editors that show how many characters are highlighted, like Programmer’s Notepad, it’s relaxing to make sure the text following .hex doesn’t exceed 128 characters. :)


edit: added strike
Last edited by unregistered on Mon Jun 07, 2021 1:20 pm, edited 1 time in total.
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: storing metatiles and their order

Post by unregistered »

unregistered wrote: Wed Jun 02, 2021 12:47 pm Well, my trick doesn’t work for #$40… sorry. :(
Hmmm… while getting coffee, my trick seems to work for #$40 too.

So since #$40 + #$40 = #$80, and since #$40 == 64, then you could do something like:

Code: Select all

ldx #$40
- lda attributes-64, x
  sta somewhere interesting
  inx;41-80
  bpl - ;loop will now end when inx sets 7th bit of X register

EDIT: If you do something like this, then be aware that page crossing adds a cycle to lda. You could save that cycle by starting attributes somewhere between $xx40 and $xxC0. (Remember that attributes is always size #$40. #$C0 + #$40 = #$100. :) $xxC0 will work bc 0 through 63 is actually 64 values.)
Post Reply