Metatile troubles

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

zkip
Posts: 67
Joined: Tue Nov 20, 2012 1:59 pm

Metatile troubles

Post by zkip »

I've tried several attempts at getting a working metatile loader thrown together with very little success. Anyway, I have this routine here that should be taking the contents of a map data string and copying what should go into the nametable into RAM. I've tried many methods for this and the current is what I like best because it's sleek and nice looking. Anyway, I've tried debugging it and I'm still unsure where it's happening, but it seems like something is getting overwritten because when I ran the program it's showing the signs of going into an infinite loop.

Here is the routine:

Code: Select all

Scratch .rs 2			;reserve 2 bytes for scratch
Buffer  .rs 64			;reserve 64 bytes for level decompression
MetaTileData_Ptr  .rs 2    	;2 bytes for the pointer currently proccessed metatile
ColumnNumX_Ptr	  .rs 2		;2 bytes for column number
ColumnNumY_Ptr	  .rs 2		;2 bytes for column number
[...]
;$00
InitMap:
 LDY #$00
 LDA #$03		;Pos of buffer
 STA ColumnNumX_Ptr
 LDA ColumnNumX_Ptr+32
 STA ColumnNumY_Ptr
LoadMap:
 LDA LevelData,y
 STY Scratch
 JSR WriteTileToBuffer	;a = metatilenumber to process
 LDY Scratch
 INY
 CPY #$15
 BNE LoadMap
 RTS

WriteTileToBuffer:
 LDX MT_Table,y
 STX MetaTileData_Ptr
 LDA #HIGH(MT_Table)
 STA MetaTileData_Ptr+1
 LDY #$00			;start at 0
 LDX #$00
 LDA [MetaTileData_Ptr],y	;do 1
 STA [ColumnNumX_Ptr,x]
 INY
 INX
 LDA [MetaTileData_Ptr],y	;do 2
 STA [ColumnNumX_Ptr,x]
 INY
 INX
 LDA [MetaTileData_Ptr],y	;do 3
 STA [ColumnNumY_Ptr,x]
 INY
 INX
 LDA [MetaTileData_Ptr],y	;and finally the 4th byte
 STA [ColumnNumY_Ptr,x]
 LDA ColumnNumX_Ptr
 INC A				;\ We wrote 4 bytes so increase the ptrs accordingly
 INC A				; |
 STA ColumnNumX_Ptr		; |
 LDA ColumnNumY_Ptr		; |
 INC A				; |
 INC A				;/
 STA ColumnNumY_Ptr
 RTS

Here are my tables:

Code: Select all

MT_Table:
 .db LOW(MetaTileBlock00),LOW(MetaTileBlock01),LOW(MetaTileBlock02),LOW(MetaTileBlock03)

MetaTileBlock00:
  .db $23,$23,$23,$23
MetaTileBlock01:
  .db $20,$21,$30,$31
MetaTileBlock02:
  .db $22,$23,$32,$33
MetaTileBlock03:
  .db $01,$01,$01,$01
LevelData:
  .db $02,$02,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01
User avatar
koitsu
Posts: 4203
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: Metatile troubles

Post by koitsu »

Code: Select all

 LDA ColumnNumX_Ptr
 INC A            ;\ We wrote 4 bytes so increase the ptrs accordingly
 INC A            ; |
 STA ColumnNumX_Ptr      ; |
 LDA ColumnNumY_Ptr      ; |
 INC A            ; |
 INC A            ;/
 STA ColumnNumY_Ptr
I can tell you one thing: there is no INC A on the 6502 ("A" here is obviously a reference to the accumulator, not a variable or memory location that you've labelled "A") The assembler you're using is letting you use 65c02 or newer opcodes; the 6502 does not have a way to increment the accumulator aside from clc ; adc #1 (or in this case, you'd want to use clc ; adc #2 since you're wanting to add 2 to something).

Your INC A is probably being assembled into opcode $1a, which is an undefined opcode on the 6502, and is probably causing you chaos.

Get yourself an assembler that adheres strictly to 6502. :-)
zkip
Posts: 67
Joined: Tue Nov 20, 2012 1:59 pm

Re: Metatile troubles

Post by zkip »

Okay, that makes sense. However, it is producing different results but still seems to go into an infinite loop. Anyone know why? :shock:
unregistered
Posts: 1193
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: Metatile troubles

Post by unregistered »

koitsu wrote:INC A is probably being assembled into opcode $1a, which is an undefined opcode on the 6502, and is probably causing you chaos.
Yes, executing undefined instructions is really bad... probably the cause of your infinite loop.
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Metatile troubles

Post by tokumaru »

You appear to be doing unnecessary complex pointer operations, and making lots of mistakes along the way. For example, I don't see you ever setting the high bytes of ColumnNumX_Ptr or ColumnNumY_Ptr, so when you use those pointers you'll hardly get the addresses you expect. Also, you have the index of the metatile you want to draw in A when you call WriteTileToBuffer, but then you trash it with the LDA #HIGH(MT_Table) command.

Still, errors aside, the solution you're trying to come up with is way more complicated than it should be. Is there any reason why you can't copy your tiles like this:

Code: Select all

WriteTileToBuffer:
	tay
	lda MetaTileBlock00, y
	sta Buffer+0, x
	lda MetaTileBlock01, y
	sta Buffer+1, x
	lda MetaTileBlock02, y
	sta Buffer+32, x
	lda MetaTileBlock03, y
	sta Buffer+33, x
	inx
	inx
	rts
You don't need pointers unless you have more than 256 types of metatiles throughout the game, but even if you do you can use this exact same code, except you'd use indirect addressing instead of absolute addressing for accessing the 4 corners of the metatile (you have to setup the 4 pointers whenever the tileset changes, which is usually between levels):

Code: Select all

WriteTileToBuffer:
	tay
	lda (MetaTileTopLeft), y
	sta Buffer+0, x
	lda (MetaTileTopRight), y
	sta Buffer+1, x
	lda (MetaTileBottomLeft), y
	sta Buffer+32, x
	lda (MetaTileBottomRight), y
	sta Buffer+33, x
	inx
	inx
	rts
zkip
Posts: 67
Joined: Tue Nov 20, 2012 1:59 pm

Re: Metatile troubles

Post by zkip »

,there any reason why you can't copy your tiles like this:
Yes. I'm not sure if I understand your code there. It's using the labels I've made just for readability. wouldn't that be hardcoding them? What I'm trying to say here is how would I use your code but instead of the "block 01" "block 02" ect loading make it load from the beginning of a long set of metatile data?

edit: also, using your method is only viable for one single metatile right? because there's nothing to stop the next jsr to that routine to keep overwriting itself
User avatar
Movax12
Posts: 529
Joined: Sun Jan 02, 2011 11:50 am

Re: Metatile troubles

Post by Movax12 »

Code: Select all

 
STA [ColumnNumY_Ptr,x]
I doubt this is doing what you want, this is not the same as [pointer],y. There is nowhere in this code that needs pointers. Maybe if your metatile definitions were various sizes, you would need a pointer table to find the beginning of the metatile data.

You can leave your data as is and multiply the metatile(0 to 3) to by 4 to find the start index of the relevant data or arrange it so all top left, top right etc are together: (all $23 cause I'm lazy)

Code: Select all

MetaTileBlock00: .db $23,$23,$23,$23 ; all top left
MetaTileBlock01: .db $23,$23,$23,$23 ; all top right 
MetaTileBlock02: .db $23,$23,$23,$23 ; all bottom left
MetaTileBlock03: .db $23,$23,$23,$23 ; all bottom right
Then look at tokumaru's code again.
User avatar
Kasumi
Posts: 1293
Joined: Wed Apr 02, 2008 2:09 pm

Re: Metatile troubles

Post by Kasumi »

zkip wrote:
,there any reason why you can't copy your tiles like this:
What I'm trying to say here is how would I use your code but instead of the "block 01" "block 02" ect loading make it load from the beginning of a long set of metatile data?
Edited because Kasumi is very silly... :oops:
By changing the value in A.
This writes tile $00.

Code: Select all

     lda #$00
     jsr WriteTileToBuffer

Code: Select all

     lda #$0F
     jsr WriteTileToBuffer
This writes tile $0F.

I'm not sure what you mean that that will only write a single metatile. It will, but you put it in a loop to load the whole map. Something like:

Code: Select all

levelloop:
     sty scratch
     lda leveldata,y
     jsr WriteTileToBuffer
     ldy scratch
     iny
     dec tilesleft
     bpl levelloop
I'm not sure what you mean by "there's nothing to stop the next jsr to that routine to keep overwriting itself".

edit2: Also, I probably misunderstood your first question:
What I'm trying to say here is how would I use your code but instead of the "block 01" "block 02" ect loading make it load from the beginning of a long set of metatile data?
Currently, Tokumaru suggests this:

Code: Select all

WriteTileToBuffer:
   tay
   lda MetaTileBlock00, y
   sta Buffer+0, x
   lda MetaTileBlock01, y
   sta Buffer+1, x
   lda MetaTileBlock02, y
   sta Buffer+32, x
   lda MetaTileBlock03, y
   sta Buffer+33, x
   inx
   inx
   rts
For the table you have:

Code: Select all

MetaTileBlock00:
  .db $23,$23,$23,$23
MetaTileBlock01:
  .db $20,$21,$30,$31
MetaTileBlock02:
  .db $22,$23,$32,$33
MetaTileBlock03:
  .db $01,$01,$01,$01
If you needed more sets, he's suggesting you do this:

Code: Select all

WriteTileToBuffer:
   tay
   lda (MetaTileTopLeft), y
   sta Buffer+0, x
   lda (MetaTileTopRight), y
   sta Buffer+1, x
   lda (MetaTileBottomLeft), y
   sta Buffer+32, x
   lda (MetaTileBottomRight), y
   sta Buffer+33, x
   inx
   inx
   rts
Say these are your tables:

Code: Select all

;Yeah, they're all the same. Pretend they're different
;And long!
GrasslandsMetaTileBlock00:
  .db $23,$23,$23,$23
GrasslandsMetaTileBlock01:
  .db $20,$21,$30,$31
GrasslandsMetaTileBlock02:
  .db $22,$23,$32,$33
GrasslandsMetaTileBlock03:
  .db $01,$01,$01,$01

WaterlevelMetaTileBlock00:
  .db $23,$23,$23,$23

WaterlevelMetaTileBlock01:
  .db $20,$21,$30,$31

WaterlevelMetaTileBlock02:
  .db $22,$23,$32,$33

WaterlevelMetaTileBlock03:
  .db $01,$01,$01,$01
To use indirect code, MetaTileTopLeft etc now refer to RAM rather than ROM.

Code: Select all

;MetaTileTopLeft (and MetaTileTopLeft+1) stores a 16bit address.

;To make it load from the grasslands set:
   lda #low(GrasslandsMetaTileBlock00);Not sure the syntax on your assembler
;But this loads the low byte of the address GrasslandsMetaTileBlock00
;Begins at
   sta MetatileTopLeft

   lda #high(GrasslandMetaTileBlock00);Same for High byte
  sta MetaTileTopLeft+1
;Do the same for the other 3 blocks
From there

Code: Select all

   lda (MetaTileTopLeft), y
   sta Buffer+0, x
Would load the Grasslands data as you'd expect. If you write the address for the waterlevel block to MetatileTopleft etc, that same code will load the waterlevel data.
Last edited by Kasumi on Mon Sep 16, 2013 4:47 pm, edited 5 times in total.
unregistered
Posts: 1193
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: Metatile troubles

Post by unregistered »

Kasumi wrote:This writes tile $00.

Code: Select all

     ldy #$00
     jsr WriteTileToBuffer

Code: Select all

     ldy #$0F
     jsr WriteTileToBuffer
This writes tile $0F.
How does WriteTileToBuffer write tile $0f? The first instruction is tay... that would copy the accumulator into y overwriting y. Right?
User avatar
Kasumi
Posts: 1293
Joined: Wed Apr 02, 2008 2:09 pm

Re: Metatile troubles

Post by Kasumi »

That's true. My bad, so you'd load A with the tile you want instead of Y.
zkip
Posts: 67
Joined: Tue Nov 20, 2012 1:59 pm

Re: Metatile troubles

Post by zkip »

Man this is confusing lol..... I guess I should have worded that better. I don't have separate banks of metatiles. Just one. No separate tileset..just the one. What i don't understand is in your code your lda'ing with all of the block labels. why? I was under the assumption that all one needed to do was supply the starting address of the metatiles data.

edit: say those labels weren't there. Could I just make a big table of the metatile data and make just one label?
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Metatile troubles

Post by tokumaru »

zkip wrote:Yes. I'm not sure if I understand your code there.
Fair enough, you shouldn't use code you don't understand (don't ever copy/paste chunks of code together hoping they'll work if you don't understand the purpose of each chunk), but that's also not a reason to go for a solution way too more complex for your problem, specially if it's getting out of hand like in this case.
It's using the labels I've made just for readability.
Not at all. The subroutine I wrote is indeed not a direct replacement of yours, but it's close enough. The only thing missing is the initialization of the X register, which has to be set to 0 before the decoding loop begins (since there is no pointer being updated anymore). Something like this (adding to what kasumi wrote):

Code: Select all

	ldx #$00 ;star filling the buffer from 0
levelloop:
	sty scratch ;backup Y
	lda leveldata, y ;get the index of the metatile
	jsr WriteTileToBuffer ;decompress it to the buffer
	ldy scratch ;restore Y
	iny ;go to the next metatile
	cpx #$20 ;is the buffer full yet?
	bne levelloop ;if not, get another metatile
wouldn't that be hardcoding them? What I'm trying to say here is how would I use your code but instead of the "block 01" "block 02" ect
With a loop similar to the above, like Kasumi said.
edit: also, using your method is only viable for one single metatile right? because there's nothing to stop the next jsr to that routine to keep overwriting itself
The value in X is kept between calls to the function, so nothing is overwritten.
zkip wrote:Man this is confusing lol..... I guess I should have worded that better. I don't have separate banks of metatiles. Just one. No separate tileset..just the one.
Then forget about pointers altogether, you really don't need them for this.
What i don't understand is in your code your lda'ing with all of the block labels. why?
Because you need to load the four corners of the metatile, so we have one LDA instruction for each corner.
I was under the assumption that all one needed to do was supply the starting address of the metatiles data.
Not necessary unless you have more than 256 metatiles. The index register (n this case, Y) can address up to 256 values, so if it contains #$05 and you do LDA MetaTileBlock00, y, that means "get the top left tile of metatile number $05". That will work up until metatile number 255, after that you'll need pointers.

You will maybe need pointers to read from your level map though, unless they're all < 256 metatiles (which could be the case if there's no scrolling, since you need 240 metatiles to fill the whole screen).
zkip
Posts: 67
Joined: Tue Nov 20, 2012 1:59 pm

Re: Metatile troubles

Post by zkip »

Please forgive my abundance of question asking, and my un-ability to grasp this concept, but I still don't get it. :E
The index register (n this case, Y) can address up to 256 values, so if it contains #$05 and you do LDA MetaTileBlock00, y, that means "get the top left tile of metatile number $05". That will work up until metatile number 255, after that you'll need pointers.
I understand this, but the way the labels are being used is confusing me. Say I've got 16 metatiles instead of the 4 we have now. MetaTileBlock00-MetaTileBlock10 in order. Now what? Would the routine still be effective? If so, why are only the first 4 corner offsets loaded? (edit: I know 4 tiles make up a 16x16 tile, but why load the first 4 labels instead of say the last 4 or middle 4?)
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Metatile troubles

Post by tokumaru »

zkip wrote:I understand this, but the way the labels are being used is confusing me. Say I've got 16 metatiles instead of the 4 we have now. MetaTileBlock00-MetaTileBlock10 in order. Now what?
Oh, now I get why you're confused! Each label in your table represents a metatile, I assumed that each label represented a corner of the metatiles... The code I posted assumed the data was arranged like this:

Code: Select all

TopLeftTile: .db $00, $01, $02, $03, $04, $05, (...)
TopRightTile: .db $20, $21, $22, $23, $24, $25, (...)
BottomLeftTile: .db $60, $61, $62, $63, $64, $65, (...)
BottomRightTile: .db $80, $81, $82, $83, $84, $85, (...)
Which means that metatile #0 is made of the following tiles: $00, $20, $60, $80. I often arrange data like this because the 6502 is better at reading structures of arrays rather than arrays of structures. In this case, even if you have 200 tiles or more you'll still have only 4 labels in the code, because they represent the corners, and all metatiles have 4 corners.

You can do it your way and store the metatiles linearly, but then you can only have 64 of them before you need to use pointers. To read linear metatiles, my routine would look like this:

Code: Select all

WriteTileToBuffer:
	asl ;multiply by 4 because each metatile uses 4 bytes
	asl
	tay ;use this as an index into the array of metatiles
	lda MetaTiles+0, y ;copy top left tile
	sta Buffer+0, x
	lda MetaTiles+1, y ;copy top right tile
	sta Buffer+1, x
	lda MetaTiles+2, y ;copy bottom left tile
	sta Buffer+32, x
	lda MetaTiles+3, y ;copy bottom right tile
	sta Buffer+33, x
	inx ;increment the output index by 1 metatile horizontally
	inx
	rts
Like I said, this can only access 64 metatiles, which is why I prefer the other method (which is a better option than using pointers, because it's simpler and faster).
zkip
Posts: 67
Joined: Tue Nov 20, 2012 1:59 pm

Re: Metatile troubles

Post by zkip »

Ah! Now I see. As I'm not planning on using very many metatiles at all. This seems to be perfect for my style. Thank you very much tokumaru. :)
Post Reply