Drawing Metatiles

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
puppydrum64
Posts: 158
Joined: Sat Apr 24, 2021 2:18 pm

Drawing Metatiles

Post by puppydrum64 » Sat May 15, 2021 10:49 pm

I've been trying to find literature on how metatiles are implemented but I haven't been able to find much detail on how they are drawn to the screen. This is what I've come up with, it's so close to working but the tiles are drawn partially over top of each other like so:

Image

Somewhere I need to add 10 to temp16Lo so that the correct address is written to $2006, but I can't figure out where it goes.

Here's the source code.

Code: Select all

DrawMetatiles:
	txa
	pha
	tya
	pha
	
	ldx #0
	ldy #0
	lda #0
	sta looptemp
	sta looptemp2
	sta temp16Lo
	lda #$20
	sta temp16Hi

DrawMetatiles_loop:
	lda looptemp
	tax
	lda MetatilesToDraw, x
	asl a
	asl a
	tay
	
	lda $2002
	lda temp16Hi
	sta $2006
	lda temp16Lo
	sta $2006
	
	;Draw top 2 pieces of metatile
	lda metatiles,y
	sta $2007
	iny
	lda metatiles,y
	sta $2007
	iny
	
	;move to next row
	lda temp16Lo
	clc
	adc #$20
	sta pointerLo	;use a different pointer since we want to go back to the first row for the next metatile
	lda temp16Hi
	adc #00
	sta pointerHi	;if temp16Lo wrapped around, add 1 to temp16Hi, otherwise leave it unchanged.
	
	
	lda $2002
	lda pointerHi
	sta $2006
	lda pointerLo
	sta $2006
	
	;draw bottom two pieces of metatile
	
	lda metatiles,y
	sta $2007
	iny
	lda metatiles,y
	sta $2007
	iny	; after this, y should be pointing at the top left of the next metatile.
	
	inc temp16Lo
	inc temp16Lo
	bcc dontIncrementHi
	lda temp16Hi
	adc #$00
	inc temp16Hi
	sta temp16Hi
dontIncrementHi:	
	inc looptemp
	lda looptemp
	cmp #64
	beq exitloop
	jmp DrawMetatiles_loop
	
exitloop:
	pla
	tay
	pla
	tax
	rts


metatiles:
	.db $45, $45, $47, $47  ; tile 0, bricks
	.db $53, $54, $55, $56  ; tile 1, question blocks
	.db $c6, $c7, $c8, $c9  ; tile 2, bullet bill cannon
	.db $b4, $b5, $b6, $b7 ; tile 3, ground tile
	.db $ab, $ad, $ac, $ae ; tile 4,  hard block
  
MetatilesToDraw:
	.db $00, $01, $02, $03, $04, $00, $01, $02
	.db $03, $04, $00, $01, $02, $03, $04, $00
	.db $01, $02, $03, $04, $00, $01, $02, $03
	.db $04, $00, $01, $02, $03, $04, $00, $01
	.db $02, $03, $04, $00, $01, $02, $03, $04
	.db $00, $01, $02, $03, $04, $00, $01, $02
	.db $03, $04, $00, $01, $02, $03, $04, $00
	.db $01, $02, $03, $04, $00, $01, $02, $03
	
	
Current Projects: Aquarium Simulator (MMC1), Astro Birds (VRC6)

User avatar
Controllerhead
Posts: 289
Joined: Tue Nov 13, 2018 4:58 am
Location: $4016
Contact:

Re: Drawing Metatiles

Post by Controllerhead » Sat May 15, 2021 11:38 pm

puppydrum64 wrote:
Sat May 15, 2021 10:49 pm

Code: Select all

	inc temp16Lo
	inc temp16Lo
	bcc dontIncrementHi
	lda temp16Hi
	adc #$00
	inc temp16Hi
	sta temp16Hi
Well, this won't work for a few reasons. INC doesn't set the carry flag, only ADC / SBC do (when adding / subtracting). Also, when you hit the right of the screen to go to the next row, you'll need to account for skipping the bottom row of the metatiles that you already drew. I haven't compiled this code, but try something like:

Code: Select all

INC temp16Lo
INC temp16Lo
LDA temp16Lo
AND #%00011111
BNE sameRow
  LDA temp16Lo
  CLC
  ADC #$20
  STA temp16Lo
  LDA #$00
  ADC temp16Hi
  STA temp16Hi
sameRow:
Image

User avatar
tokumaru
Posts: 12103
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Drawing Metatiles

Post by tokumaru » Sat May 15, 2021 11:53 pm

It looks like you're not incrementing the pointer after each row is drawn, so the two INCs after the last metatile just push the pointer to the beginning the next row of tiles. At that point you need to skip an extra row of tiles. Speaking of the two INCs, you're checking the carry right after those, but the INC instruction does NOT affect the carry flag, so that part is wrong.

EDIT: Ninja'd!
Last edited by tokumaru on Sat May 15, 2021 11:54 pm, edited 1 time in total.

puppydrum64
Posts: 158
Joined: Sat Apr 24, 2021 2:18 pm

Re: Drawing Metatiles

Post by puppydrum64 » Sat May 15, 2021 11:53 pm

I knew there was some way to avoid drawing over what was already there. I find my ability to code weakens late at night :lol: I remember using the AND $1F logic for skilldrick.github.io/easy6502 when drawing to the screen (that thing is so useful for quick testing but the vram is even harder to work with than the nes)
Current Projects: Aquarium Simulator (MMC1), Astro Birds (VRC6)

puppydrum64
Posts: 158
Joined: Sat Apr 24, 2021 2:18 pm

Re: Drawing Metatiles

Post by puppydrum64 » Sun May 16, 2021 8:28 am

Controllerhead wrote:
Sat May 15, 2021 11:38 pm
puppydrum64 wrote:
Sat May 15, 2021 10:49 pm

Code: Select all

	inc temp16Lo
	inc temp16Lo
	bcc dontIncrementHi
	lda temp16Hi
	adc #$00
	inc temp16Hi
	sta temp16Hi
Well, this won't work for a few reasons. INC doesn't set the carry flag, only ADC / SBC do (when adding / subtracting). Also, when you hit the right of the screen to go to the next row, you'll need to account for skipping the bottom row of the metatiles that you already drew. I haven't compiled this code, but try something like:

Code: Select all

INC temp16Lo
INC temp16Lo
LDA temp16Lo
AND #%00011111
BNE sameRow
  LDA temp16Lo
  CLC
  ADC #$20
  STA temp16Lo
  LDA #$00
  ADC temp16Hi
  STA temp16Hi
sameRow:
Darn it! I had a long reply and it got deleted due to a timeout. First I want to thank you for your help.

Second I had a few questions (if I can remember what they were):

[*] Let's say I'm making a side-scrolling game. Could I save a whole bunch of ROM by only having the screens that you warp to directly be loaded like this? In other words any "screen" that you can only reach by scrolling can only be reached by loading in columns as the player moves rather than drawing every single metatile. Is that a good or bad practice?
[*] In FCEUX the top and bottom 8 pixels are cut off unless your region is set to PAL. I've heard FCEUX isn't very accurate so I don't know if that's how it works on real hardware. If so, should I have my code set up using conditionals like so?

Code: Select all

LDA #$20
STA $2006

ifdef PAL
LDA #$00
endif

ifdef NTSC
LDA #$20
endif

STA $2006
Thing is I don't know how to tell the assembler if the game is set to PAL or NTSC. Maybe I should draw the tiles anyway and let the user decide.

[*] This is how I ended up making the code fill the screen. It feels a bit amateurish with the use of my temp variable "TimesToDraw" but I couldn't think of a better way.

Code: Select all


;this section is in the reset routine
	lda #64
	sta TimesToDraw
	
	lda #$04
	sta looptemp2
	
	lda #$00
  	sta temp16Lo
	lda #$20
	sta temp16Hi
	
	
	
	jsr DrawMetatiles	;4 rows per subroutine call
	jsr DrawMetatiles
	jsr DrawMetatiles
	
	lda #48
	sta TimesToDraw
	
	jsr DrawMetatiles	;3 rows for this one
	
Forever:
	jmp Forever
	
DrawMetatiles:
	txa
	pha
	tya
	pha
	
	ldx #0
	ldy #0
	sty looptemp


DrawMetatiles_loop:
	lda looptemp
	tax
	lda MetatilesToDraw, x
	asl a
	asl a
	tay
	
	lda $2002
	lda temp16Hi
	sta $2006
	lda temp16Lo
	sta $2006
	
	;Draw top 2 pieces of metatile
	lda metatiles,y
	sta $2007
	iny
	lda metatiles,y
	sta $2007
	iny
	
	;move to next row
	lda temp16Lo
	clc
	adc #$20
	sta pointerLo	;use a different pointer since we want to go back to the first row for the next metatile
	lda temp16Hi
	adc #00
	sta pointerHi	;if temp16Lo wrapped around, add 1 to temp16Hi, otherwise leave it unchanged.
	
	
	lda $2002
	lda pointerHi
	sta $2006
	lda pointerLo
	sta $2006
	
	;draw bottom two pieces of metatile
	
	lda metatiles,y
	sta $2007
	iny
	lda metatiles,y
	sta $2007
	iny	; after this, y should be pointing at the top left of the next metatile.
	
	inc temp16Lo
	inc temp16Lo
	lda temp16Lo
	and #%00011111
	bne sameRow
		lda temp16Lo
		clc
		adc #$20
		sta temp16Lo
		lda #$00
		adc temp16Hi	;we want the carry if there is one
		sta temp16Hi
sameRow:
		inc looptemp
		lda looptemp
		cmp TimesToDraw	;normally 64 but before the 4th JSR this is set to 48
		beq exitloop
		jmp DrawMetatiles_loop
	
	
exitloop:
	pla
	tay
	pla
	tax
	rts
metatiles:
	.db $45, $45, $47, $47	;$00, brick block
	.db $53, $54, $55, $56	;$01, question block
	.db $c6, $c7, $c8, $c9	;$02, bullet bill cannon
	.db $b4, $b5, $b6, $b7	;$03, ground tile
	.db $ab, $ad, $ac, $ae	;$04, hard block
	
MetatilesToDraw:
	.db $00, $01, $02, $03, $04, $00, $01, $02
	.db $03, $04, $00, $01, $02, $03, $04, $00
	.db $01, $02, $03, $04, $00, $01, $02, $03
	.db $04, $00, $01, $02, $03, $04, $00, $01
	.db $02, $03, $04, $00, $01, $02, $03, $04
	.db $00, $01, $02, $03, $04, $00, $01, $02
	.db $03, $04, $00, $01, $02, $03, $04, $00
	.db $01, $02, $03, $04, $00, $01, $02, $03

I feel like this is going to make you all cringe. :oops:
Current Projects: Aquarium Simulator (MMC1), Astro Birds (VRC6)

User avatar
tokumaru
Posts: 12103
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Drawing Metatiles

Post by tokumaru » Sun May 16, 2021 10:07 am

puppydrum64 wrote:
Sun May 16, 2021 8:28 am
Let's say I'm making a side-scrolling game. Could I save a whole bunch of ROM by only having the screens that you warp to directly be loaded like this? In other words any "screen" that you can only reach by scrolling can only be reached by loading in columns as the player moves rather than drawing every single metatile. Is that a good or bad practice?
I don't understand what you're saying here... Metatiles help you save ROM because each byte references 4 tiles instead of 1, so maps are effectively compressed to 1/4 of the size of a raw name table (not considering the overhead of the metatile definitions, which will be less significant the more you use the same set of metatiles). The way in which you reveal the maps (scrolling, screen switch, etc.) should have no impact on how much space they occupy in the ROM.
In FCEUX the top and bottom 8 pixels are cut off unless your region is set to PAL. I've heard FCEUX isn't very accurate so I don't know if that's how it works on real hardware.
The NES *always* outputs 240 scanlines, no matter the region. Some emulators will hide a few scanlines in NTSC mode because NTSC TVs tend to cover those areas, but the data is definitely still in the video signal and many TVs will show more of it.
If so, should I have my code set up using conditionals like so?
I don't think you should try to do anything different between PAL and NTSC in this regard. Just avoid putting any crucial information too close to the edges, but don't count on a specific number of scanlines being covered. You can configure the emulator to always show all scanlines, which is something I advise developers do.
Thing is I don't know how to tell the assembler if the game is set to PAL or NTSC.
You can define a symbol indicating this, and build two separate ROMs, or detect the console type at runtime. The wiki has examples of how to do this.
Maybe I should draw the tiles anyway and let the user decide.
Yeah, most games fix only the music for the different consoles types.
I feel like this is going to make you all cringe. :oops:
This code does look a bit messy for such a straightforward task, and you're not even handling attributes yet.

puppydrum64
Posts: 158
Joined: Sat Apr 24, 2021 2:18 pm

Re: Drawing Metatiles

Post by puppydrum64 » Sun May 16, 2021 10:30 am

tokumaru wrote:
Sun May 16, 2021 10:07 am
puppydrum64 wrote:
Sun May 16, 2021 8:28 am
I feel like this is going to make you all cringe. :oops:
This code does look a bit messy for such a straightforward task, and you're not even handling attributes yet.
It is a bit messy I agree. The NES's PPU is tricky to work with in this regard since it doesn't have a built-in setting for metatiles but clearly that never stopped anyone :lol:

Loops were never my strong suit when it comes to programming. :wink:
Current Projects: Aquarium Simulator (MMC1), Astro Birds (VRC6)

User avatar
tokumaru
Posts: 12103
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Drawing Metatiles

Post by tokumaru » Sun May 16, 2021 11:25 am

Don't work about it too much. When you're just starting, the most important thing is to come up a solution that works, even if it's not the best one. You learn more from coming up with your own solutions than just copying someone else's.

User avatar
Controllerhead
Posts: 289
Joined: Tue Nov 13, 2018 4:58 am
Location: $4016
Contact:

Re: Drawing Metatiles

Post by Controllerhead » Sun May 16, 2021 11:49 am

puppydrum64 wrote:
Sun May 16, 2021 8:28 am
Let's say I'm making a side-scrolling game. Could I save a whole bunch of ROM by X
Sure! Once you get past making code that works on hardware, you get into the space of creativity. There is no "right" or "wrong" way to do it so long as it works. There are better and more efficient ways to store and paint tiles, sure, but as long as it works as you intended it to, its not "wrong".

Many common solutions are to group metatiles together. Some solutions are horizontal or vertical rows of 16 metatiles. Some solutions involve 4x4 groups of metatiles and attribute data. Some involve groups of "metatile objects" that are painted as you go along. The best solution really depends on what you're trying to do.
puppydrum64 wrote:
Sun May 16, 2021 8:28 am
In FCEUX the top and bottom 8 pixels are cut off unless your region is set to PAL. I've heard FCEUX isn't very accurate so I don't know if that's how it works on real hardware.
This is to mimic older CRT TVs. Most cutoff anywhere between the top / bottom 8-16px of the screen. You should design your game with the expectation that the top/bottom/left/right most 16px will be cutoff and not place any critical game information there. This is known as the "safe area".
Modern TVs and most emulators usually show all of the parts of every scanline, so nor should you assume that these areas will be hidden. You have to account for both the best that you can.
puppydrum64 wrote:
Sun May 16, 2021 8:28 am
I feel like this is going to make you all cringe. :oops:
I doubt there is a single experienced person here who wouldn't cringe at their own early attempts. Don't sweat it :wink:
Last edited by Controllerhead on Sun May 16, 2021 11:55 am, edited 3 times in total.
Image

puppydrum64
Posts: 158
Joined: Sat Apr 24, 2021 2:18 pm

Re: Drawing Metatiles

Post by puppydrum64 » Sun May 16, 2021 11:50 am

tokumaru wrote:
Sun May 16, 2021 11:25 am
Don't work about it too much. When you're just starting, the most important thing is to come up a solution that works, even if it's not the best one. You learn more from coming up with your own solutions than just copying someone else's.
That's true. As much as I learned from Chibiakumas' tutorials and from NESMaker I felt like I wasn't learning as much as I could be since I was just essentially "romhacking" their code
Current Projects: Aquarium Simulator (MMC1), Astro Birds (VRC6)

puppydrum64
Posts: 158
Joined: Sat Apr 24, 2021 2:18 pm

Re: Drawing Metatiles

Post by puppydrum64 » Mon May 17, 2021 7:22 pm

I read elsewhere that most games either associate collision data with metatiles or have that on a separate table in ROM. Let's say I chose the first option, and I had only 5 metatiles to work with. I'll use Super Mario Bros. as an example.

Code: Select all

	.db $45, $45, $47, $47	;$00, brick block
	.db $53, $54, $55, $56	;$01, question block
	.db $b4, $b5, $b6, $b7	;$02, ground tile
	.db $ab, $ad, $ac, $ae	;$03, hard block
	.db $24, $24, $24, $24	;$04, sky
Suppose now that I was creating the game's controls and I have some sort of gravity routine that's applied to all sprites. I imagine it would look something like this and run every frame (again loops aren't my forte so this will probably be very poorly optimized):

Code: Select all

	LDX #$00
GravityLoop:
	LDA $0200,X		;get sprite Y position on screen
	SEC
	SBC fallingSpeed	;an arbitrary value
	STA $0200,X
	CMP #$FE
	BCC keepGoing
	JSR DestroyObject	;some routine that replaces the sprite with blank tiles.
keepGoing:	
	INX
	INX
	INX
	INX
	
	BNE GravityLoop
Now as is, this doesn't check for collision, it just makes everything fall down. I would need some way to know what tile an object is on. I'm having trouble thinking of a way to do that.
Current Projects: Aquarium Simulator (MMC1), Astro Birds (VRC6)

User avatar
tokumaru
Posts: 12103
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Drawing Metatiles

Post by tokumaru » Mon May 17, 2021 9:03 pm

puppydrum64 wrote:
Mon May 17, 2021 7:22 pm
I read elsewhere that most games either associate collision data with metatiles or have that on a separate table in ROM.
Your metatiles currently have 4 bytes each. You need at least one more byte in order to specify which palette they use (2 bits) and their solidity (at least 1 bit, but if you have 1-way platforms, slopes or other kinds of fancy stuff, you'll definitely need more). The problem with this is that multiplying by 5 is not as fast or as straightforward as multiplying by 4. For this reason, many programmers chose to store their metatiles in parallel arrays (the 6502 is better suited for accessing structures of arrays rather than arrays of structures, after all):

Code: Select all

MetatilesTile0:
	.db $45, $53, $b4, $ab, $24
MetatilesTile1:
	.db $45, $54, $b5, $ad, $24
MetatilesTile2:
	.db $47, $55, $b6, $ac, $24
MetatilesTile3:
	.db $47, $56, $b7, $ae, $24
MetatilesAttributes:
	.db $80, $80, $81, $82, $00
This way you can immediaely access any byte of a metatile using only its index, no need to waste time calculating pointers.

Some programmers like to store collision and/or color information in the level map itself, because in some instances that can be easier to handle. That often requires more storage space though, since this information will be repeated for every instance of each metatile, as opposed to only once per metatile like in the example above. I personally prefer to make color and collision properties of the metatiles themselves, because it keeps things compact and consistent.

Code: Select all

	LDX #$00
GravityLoop:
	LDA $0200,X		;get sprite Y position on screen
	SEC
	SBC fallingSpeed	;an arbitrary value
	STA $0200,X
	CMP #$FE
	BCC keepGoing
	JSR DestroyObject	;some routine that replaces the sprite with blank tiles.
keepGoing:	
	INX
	INX
	INX
	INX
	
	BNE GravityLoop
Collision aside, this is not really how you go about applying gravity. NES sprites are just a means of representing the objects that exist in your game, so you don't go around indiscriminately simulating physics on OAM entries like that. You're supposed to have your game objects modeled somewhere in RAM, where they have properties like coordinates, velocities, health, animation timers, and so on, and it's on those objects that you need to apply physics calculations. Then, once all objects have reached their final position for a frame, you generate the OAM entries necessary to draw them on the screen.

Also, each object normally has its own update subroutine, so that they get to decide what happens to them on a case-by-case basis. If you have the game engine control the objects from the outside, it gets much harder to take the necessities of each object into consideration. For example, not all objects are subject to gravity (e.g. floating coins the player can pick up are not supposed to fall), and even objects that *seem* like they're subject to gravity, may not really have any gravity applied to them as a means of saving CPU time (e.g. enemies that just patrol back and forth without ever falling don't actually need gravity).

As for collisions against the level geometry, the typical procedure goes something like this:
-Add up all forces acting on a the same axis to calculate the final displacement in that axis;
-Apply the displacement to the object;
-Depending on the direction of the movement (positive or negative), pick one edge of the object and check if that overlaps anything solid;
-If there was a collision against something solid, calculate how deep the edge of the object is into the solid object and push the object back that many pixels;
-Do the same for the other axis.

unregistered
Posts: 1184
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: Drawing Metatiles

Post by unregistered » Tue May 18, 2021 12:02 am

… listen to tokumaru!

and when running gravity collision, every frame should make your sprite fall at first. After you add the frame’s fall distance to your Yvariable, you check collision between your sprite’s feet/bottom and the part of the metatile that your newly fallen sprite’s bottom is now touching. If a collision should be detected, i.e. if your sprite is now inside a solid metatile part, then simply “eject” your sprite back to its previous location (if your sprite’s gravity pulls it down 8 pixels each frame, then your sprite should be ejected up 8 pixels each time vertical collision is detected). That’s what tokumaru taught me. :)


EDIT: That “subtract 8 from Yvariable ejection” is only valid if your sprite always falls exactly 8 pixels. Cool things can be done with AND, ORA, and rarely EOR to make ejection solve countless problems. :)

FINAL-EDIT: Sry, I replied with a collision ejection post inside your drawing metatiles thread puppydrum64. :( Please forgive me.

puppydrum64
Posts: 158
Joined: Sat Apr 24, 2021 2:18 pm

Re: Drawing Metatiles

Post by puppydrum64 » Thu May 20, 2021 7:03 pm

I'm starting to get to the part of making my game that I dreaded since the beginning: physics. Thankfully it's a spaceship game so there are mostly no physics whatsoever. But I'm having trouble thinking of how to turn object variables into OAM data. If all my game objects were 8 by 8 this would be so much easier since I could just use the OAM buffer $0200-$02FF directly to control their positioning but this is not the case. I've come up with the following so far but I'm not sure how to implement most of it. If some of this looks very familiar, it's because I based my idea a little bit off of NESMaker. (Again the formatting looks weird when pasting from Notepad++)

Code: Select all


TOTAL_MAX_OBJECTS = 16
	.enum $0400
OBJECT_XPOS			.dsb TOTAL_MAX_OBJECTS			
OBJECT_YPOS			.dsb TOTAL_MAX_OBJECTS
OBJECT_STATUS 		.dsb TOTAL_MAX_OBJECTS	
	;#%ABCDEFGH
;A = ACTIVE							E =
;B = FLAGGED FOR DESTRUCTION			F =
;C = 									G =
;D =									H =
OBJECT_ID			.dsb TOTAL_MAX_OBJECTS
OBJECT_PROPERTIES	.dsb TOTAL_MAX_OBJECTS		
;#%ABCDEFGH
;A = OBEY GRAVITY			E = DRAW BEHIND BKGD
;B = BOSS MINION			F = 
;C = INDESTRUCTIBLE		G =
;D = IGNORE COLLISION		H =
							
							
OBJECT_SIZE			.dsb TOTAL_MAX_OBJECTS ;	Total number of sprites needed to display the object.
OBJECT_HEALTH		.dsb TOTAL_MAX_OBJECTS ;
OBJECT_BASE_SPRITE	.dsb TOTAL_MAX_OBJECTS ;	The tile ID for the top left sprite of that object.
OBJECT_PALETTE		.dsb TOTAL_MAX_OBJECTS ; 	pre-buffer for OAM attribute data
	.ende
Current Projects: Aquarium Simulator (MMC1), Astro Birds (VRC6)

User avatar
tokumaru
Posts: 12103
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Drawing Metatiles

Post by tokumaru » Thu May 20, 2021 7:57 pm

puppydrum64 wrote:
Thu May 20, 2021 7:03 pm
But I'm having trouble thinking of how to turn object variables into OAM data.
What you need is a metasprite system. Thankfully you can implement one with just one function. Write a function that takes X and Y coordinates and a pointer to a metasprite definition, and generates OAM entries. A metasprite definition can be as simple as a list of sprites, where the coordinates are relative rather than absolute:

Code: Select all

PlayerStanding:
  .db 4 ;number of sprites
  .db -8, -8, $04, $01 ;x, y, tile, attributes
  .db 0, -8, $05, $01
  .db -8, 0, $06, $01
  .db 0, 0, $07, $01
This is a 16x16-pixel sprite built around a central point. Your metasprite function will loop through these sprites (this is what the sprite count is for), adding the coordinates to the reference point you passed, generating OAM entries on the fly.

Keep in mind that you need to detect coordinates wrapping around the edges of the screen and not output those sprites (unless you DO want sprites to wrap around like in certain old games).
If all my game objects were 8 by 8 this would be so much easier since I could just use the OAM buffer $0200-$02FF directly to control their positioning
Even when using only 8x8 sprites this is not a good idea, because you can't do sprite cycling or dynamically allocate sprites as objects spawn and despawn.

EDIT:

In case you wondering how to detect sprite wraparound, you can simply do different checks depending on the sign of the offset you're adding to the base coordinate:

-If the offset is positive, the result can't be bigger than 255 (i.e. carry set = wraparound);
-If the offset is negative, this is actually a subtraction, and the result can't be smaller than 0 (i.e. carry clear = wraparound);

Here's a quick and dirty (untested!) metasprite drawing subroutine, to help you get the gist of it:

Code: Select all

DrawMetasprite:
  ;BaseX: object's X coordinate;
  ;BaseY: object's Y coordinate;
  ;Metasprite: pointer to metasprite definition;

  ;gets the sprite count
  ldy #$00
  lda (Metasprite), y
  sta SpritesLeft

  ;gets the current OAM index
  ldx OAMIndex

GenerateSprite:

  ;calculates the X coordinate
  iny
  clc
  lda (Metasprite), y
  bpl AddPositiveX
AddNegativeX:
  adc BaseX
  bcs GoodX
  bcc BadX
AddPositiveX:
  adc BaseX
  bcc GoodX
BadX:
  iny
  iny
  iny
  jmp NextSprite
GoodX:
  sta OAMBuffer+3, x

  ;calculates the Y coordinate
  iny
  clc
  lda (Metasprite), y
  bpl AddPositiveY
AddNegativeY:
  adc BaseY
  bcs GoodY
  bcc BadY
AddPositiveY:
  adc BaseY
  bcc GoodY
BadY:
  iny
  iny
  jmp NextSprite
GoodY:
  sta OAMBuffer+0, x

  ;copies the tile index
  iny
  lda (Metasprite), y
  sta OAMBuffer+1, x

  ;copies the attributes
  iny
  lda (Metasprite), y
  sta OAMBuffer+2, x

  ;advances to the next OAM slot
  inx
  inx
  inx
  inx

NextSprite:

  ;decides whether to keep going
  dec SpritesLeft
  bne GenerateSprite

  ;saves OAM index for next time
  stx OAMIndex

  ;returns
  rts

Post Reply