[SNES developer noob] Help me understand how sprites work

Discussion of hardware and software development for Super NES and Super Famicom. See the SNESdev wiki for more information.

Moderator: Moderators

Forum rules
  • For making cartridges of your Super NES games, see Reproduction.
Xeraster
Posts: 7
Joined: Tue Sep 12, 2017 4:16 pm

[SNES developer noob] Help me understand how sprites work

Post by Xeraster »

So I'm new to snes homebrew but I know how to program somewhat well.

So anyway, I've been sort of crapping around with SNES code in order to build my understanding of how this works and possibly eventually make my own game for lulz (Flappy Bird, anyone?)
There's a lot of helpful stuff on the superfamicom.org wiki to read about which has made it a lot easier for me.
Anyway, one thing I'm finding it especially difficult to grasp an understanding of is how you can display sprites on the screen. For example, how would I add just a stationary sprite to, say, the "walker" demo found in Neviksti’s Snes Starterkit?
So I make a borderless 32x32 pcx image and then run it through PCX2SNES and then get the .clr and .pic files.
I then know I'm supposed to add it to the bottom of my .asm file like so:

Code: Select all

BikerSprite
	.INCBIN ".\\Pictures\\biker.pic"
        .INCBIN ".\\Pictures\\biker.clr"
I understand that I'm then probably supposed to use LoadBlockToVRAM. In the walker example, adding "LoadBlockToVRAM newsprite, $9000, $0800" will look like this:

Code: Select all

        ;Load Tile and Character data to VRAM
	LoadBlockToVRAM	BackgroundMap, $0000, $2000	; 64x64 tiles = 4096 words = 8192 bytes
	LoadBlockToVRAM	BackgroundPics, $2000, $6000	; 384 tiles * (8bit color)= 0x6000 bytes
	LoadBlockToVRAM	ASCIITiles, $5000, $0800	;128 tiles * (2bit color = 2 planes) --> 2048 bytes
	LoadBlockToVRAM	SpriteTiles, $6000, $2800	;20 32x32 tiles * (4bit color = 4 planes) --> 10240 bytes
	LoadBlockToVRAM      BikerSprite, $9000, $0800; new test sprite
After spending a day or so studying the code, looking at other relevant code in other examples and reading stuff on superfamicom wiki, I can't really understand what exactly to do to just display my sprite stationary on the screen somewhere.
I know there are sprite numbers, but how do I assign a number? In the walker example, the main sprite appears to be put onto the screen using the following code:

Code: Select all

	;Set the priority bit of all the BG2 tiles
	LDA #$80
	STA $2115		;set up the VRAM so we can write just to the high byte
	LDX #$5800
	STX $2116
	LDX #$0400		;32x32 tiles = 1024
	LDA #$20
Next_tile:
	STA $2119
	DEX
	BNE Next_tile
	LDA #01
	STA $1E
	LDA #00
	STA $20
	
	JSR SpriteInit	;setup the sprite buffer
	JSR JoyInit		;setup joypads and enable NMI

	;         ---12345678901234567890123456789012---

	;setup our walking sprite
	;put him in the center of the screen
	lda #($80-16)
	sta SpriteBuf1+sx
	lda #(224/2-16)
	sta SpriteBuf1+sy

	;put sprite #0 on screen
	lda #$54
	sta SpriteBuf2

	;set the sprite to the highest priority
	lda #$30			
	sta SpriteBuf1+spriority

	;setup the video modes and such, then turn on the screen
	JSR SetupVideo	
It looks like the sprite number is #$54. If I change it to #$53 or 55, the walking guy sprite just disappears while I expected it to show a different sprite instead. Obviously there's more to it that I haven't been able to figure out by studying or trial and error. Can anyone help me with this?

My goal is to just get any sprite to appear in the walker example just so I can understand what exactly has to be done to do this again in a game of my own.

Thanks in advance
User avatar
HihiDanni
Posts: 186
Joined: Tue Apr 05, 2016 5:25 pm

Re: [SNES developer noob] Help me understand how sprites wor

Post by HihiDanni »

I suggest studying the OAM layout - you can find it here: https://wiki.superfamicom.org/snes/show/Sprites

In short, you should have a section of RAM that represents what you want the contents of OAM to be, and then during NMI/Vblank (or force blank, where the display is turned off) you upload that RAM copy into the actual OAM.

There's actually two tables. The first is 512 bytes and each sprite gets four bytes. This is where you select the sprite tile to use, X and Y position, and most attributes. The second is 32 bytes (called the OAM high table) and represents four sprites per byte. You'll need this to set the sprite size and allow it to go partway off the left side of the screen.

By the way, the register at $2101 is how you tell the PPU where in VRAM your sprite tiles are stored.

Edit: By sprite numbers do you mean the OAM index, or the sprite tile? The sprite tile can be considered as the Nth tile into a 128x128 graphic region of sprite graphics. As for the OAM index, that is up to you.
SNES NTSC 2/1/3 1CHIP | serial number UN318588627
User avatar
dougeff
Posts: 3079
Joined: Fri May 08, 2015 7:17 pm

Re: [SNES developer noob] Help me understand how sprites wor

Post by dougeff »

I'm probably going to quote this wrong, but my memory says.

The OAM low table isn't updated until the OAM high table is written to.

So basically, you need to write to both tables every frame, low and high.
Last edited by dougeff on Wed Sep 13, 2017 3:38 pm, edited 1 time in total.
nesdoug.com -- blog/tutorial on programming for the NES
93143
Posts: 1717
Joined: Fri Jul 04, 2014 9:31 pm

Re: [SNES developer noob] Help me understand how sprites wor

Post by 93143 »

Pretty sure that's wrong. The low table is behind a word buffer, which can have funky results if you're mixing reads and writes. The high table is byte access. They can be written independently.

What you describe would require a half-kilobyte write buffer...
User avatar
dougeff
Posts: 3079
Joined: Fri May 08, 2015 7:17 pm

Re: [SNES developer noob] Help me understand how sprites wor

Post by dougeff »

How am I to interpret this?

"Writes to the low table go into a word-sized buffer, which is written to the appropriate word of OAM when the high byte of the word is written" (from the link above at wiki.superfamicom.org)
nesdoug.com -- blog/tutorial on programming for the NES
adam_smasher
Posts: 271
Joined: Sun Mar 27, 2011 10:49 am
Location: Victoria, BC

Re: [SNES developer noob] Help me understand how sprites wor

Post by adam_smasher »

IIRC (haven't touched the SNES in a bit):

All writes go through a byte-sized port ($2104). When writing a word to the low table:

1. You write the low byte. It gets stored in a buffer.
2. You write the high byte. Only then is it written into the table.

The low byte/high byte thing going on there refers to a word for the low table; it has nothing to do with the high table.
User avatar
dougeff
Posts: 3079
Joined: Fri May 08, 2015 7:17 pm

Re: [SNES developer noob] Help me understand how sprites wor

Post by dougeff »

Got it, thanks.
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
HihiDanni
Posts: 186
Joined: Tue Apr 05, 2016 5:25 pm

Re: [SNES developer noob] Help me understand how sprites wor

Post by HihiDanni »

For a second I thought I could gain 512 bytes of DMA headroom by just writing to the OAM transfer registers mid-frame. Heh, guess that's not the case.
SNES NTSC 2/1/3 1CHIP | serial number UN318588627
psycopathicteen
Posts: 3140
Joined: Wed May 19, 2010 6:12 pm

Re: [SNES developer noob] Help me understand how sprites wor

Post by psycopathicteen »

If you're not using all 8-channels for HDMA, you can set up a DMA channel for OAM before vblank.
User avatar
HihiDanni
Posts: 186
Joined: Tue Apr 05, 2016 5:25 pm

Re: [SNES developer noob] Help me understand how sprites wor

Post by HihiDanni »

By writing to the registers mid-frame, I didn't mean that I wanted sprite changes to take effect mid-frame. dougeff's post implied that you could upload OAM whenever, but be able to control when the newly uploaded OAM table is applied, so you'd only upload the 32-byte high table in the NMI routine and it'd work the same but you'd be doing it faster.
SNES NTSC 2/1/3 1CHIP | serial number UN318588627
Xeraster
Posts: 7
Joined: Tue Sep 12, 2017 4:16 pm

Re: [SNES developer noob] Help me understand how sprites wor

Post by Xeraster »

HihiDanni wrote:I suggest studying the OAM layout - you can find it here: https://wiki.superfamicom.org/snes/show/Sprites

In short, you should have a section of RAM that represents what you want the contents of OAM to be, and then during NMI/Vblank (or force blank, where the display is turned off) you upload that RAM copy into the actual OAM.
I understand that much. There's a low byte and a high byte. These contain data for the table having to do with telling the system how to use my sprite. As seen on the given link, it shows the record format for the low table and the high table and what the values are. But I can't find where they did this in the walker example.

By random guessing it looks like in the walker example, this is how they assign the high table, using the address you said:

Code: Select all

      lda #$A3		;Sprites 32x32 or 64x64, character data at $6000 (word address)
      sta $2101  
So A3 converted from hex to binary is 10100011 or 163 in decimal. It's supposed to contain bits from the first byte of the low table? Right? According to this link this means that the sprite size is 101 which is 32x32 or 64x64 which makes sense. Not sure how to interpret the 011 at the end. Its "base selection bits" which is 3. Hmm. The sprite in question starts at $6000 and is #$2800 in size so that can't be it. How does register 2101 tell the PPU where the sprites are stores again? I don't understand.

So in the current code, the OAM address for the sprite I presume is being added is 0, So I would redo that whole bit of code to add a second sprite except set 2102 and 2103 to 1 instead of 0 for sprite ID 1? But where is the part that says where the VRAM address and range is?
Here is all the code I have at vblank and below:

Code: Select all

.DEFINE FrameNum $12		

VBlank:
	rep #$30		;A/Mem=16bits, X/Y=16bits
	phb
	pha
	phx
	phy
	phd

	sep #$20		; mem/A = 8 bit, X/Y = 16 bit

	;*********transfer sprite data

	stz $2102		; set OAM address to 0
	stz $2103

	LDY #$0400
	STY $4300		; CPU -> PPU, auto increment, write 1 reg, $2104 (OAM data write)
	LDY #$0400
	STY $4302		; source offset
	LDY #$0220
	STY $4305		; number of bytes to transfer
	LDA #$7E
	STA $4304		; bank address = $7E  (work RAM)
	LDA #$01
	STA $420B		;start DMA transfer

	;*********transfer BG2 data
	LDA #$00
	STA $2115		;set up VRAM write to write only the lower byte

	LDX #$5800
	STX $2116		;set VRAM address to BG3 tile map

	LDY #$1800
	STY $4300		; CPU -> PPU, auto increment, write 1 reg, $2118 (Lowbyte of VRAM write)
	LDY #$0000
	STY $4302		; source offset
	LDY #$0400
	STY $4305		; number of bytes to transfer
	LDA #$7F
	STA $4304		; bank address = $7F  (work RAM)
	LDA #$01
	STA $420B		;start DMA transfer

	;update the map co-ordinates
	lda MapX
	sta $210D
	lda MapX+1
	sta $210D

	lda MapY
	sta $210E
	lda MapY+1
	sta $210E

	;update the joypad data
	JSR GetInput

	lda $4210		;clear NMI Flag

	REP #$30		;A/Mem=16bits, X/Y=16bits
	
	inc FrameNum
	PLD 
	PLY 
	PLX 
	PLA 
	PLB 
      RTI
	  

;End of demo Main code

;============================================================================
; SetupVideo -- Set the video mode for the demo
;----------------------------------------------------------------------------
; In: None
;----------------------------------------------------------------------------
; Out: None
;----------------------------------------------------------------------------
SetupVideo:
	php

	rep #$10		;A/mem = 8bit, X/Y=16bit
	sep #$20
      
	lda #$A3		;Sprites 32x32 or 64x64, character data at $6000 (word address)
      sta $2101         

	lda #$04		;Set video mode 4, 8x8 tiles (256 color BG1, 4 color BG2)
      sta $2105         

	lda #$03		;Set BG1's Tile Map VRAM offset to $0000 (word address)
      sta $2107		;   and the Tile Map size to 64 tiles x 64 tiles

	lda #$52		;Set BG1's Character VRAM offset to $2000 (word address)
      sta $210B		;Set BG2's Character VRAM offset to $5000 (word address)

	lda #$58		;Set BG2's Tile Map VRAM offset to $5800 (word address)
      sta $2108		;   and the Tile Map size to 32 tiles x 32 tiles

	lda #$13		;Turn on BG1 and BG2 and Sprites
      sta $212C

      lda #$0F		;Turn on screen, full brightness
      sta $2100		

	lda #$FF		;Scroll BG2 down 1 pixel
	sta $2110
	sta $2110         

	plp
	rts

.ENDS
Where on here is there any evidence of setting up the low table or high tables like in the link you provided? Is there an example somewhere where someone has programmed 2 independent sprites that I could look at and contrast that vs this? I'm sure I would be able to figure it out on my own if I had one of those but until then I'm trying to decode how it's done from here.

But thanks everyone for the help so far I hope I'm eventually able to figure this out.
User avatar
HihiDanni
Posts: 186
Joined: Tue Apr 05, 2016 5:25 pm

Re: [SNES developer noob] Help me understand how sprites wor

Post by HihiDanni »

Xeraster wrote:I understand that much. There's a low byte and a high byte. These contain data for the table having to do with telling the system how to use my sprite. As seen on the given link, it shows the record format for the low table and the high table and what the values are. But I can't find where they did this in the walker example.
Be careful not to confuse low byte and high byte with low table and high table. Those are two different concepts.
Xeraster wrote:By random guessing it looks like in the walker example, this is how they assign the high table, using the address you said:

Code: Select all

      lda #$A3		;Sprites 32x32 or 64x64, character data at $6000 (word address)
      sta $2101  
This doesn't affect the OAM table, it's just telling the PPU where the sprite graphics are stored in VRAM, which is a separate section of memory from OAM.
Its "base selection bits" which is 3. Hmm. The sprite in question starts at $6000
So you uploaded the sprite graphic to word address 0x6000? Be careful, as the VRAM address registers go by word address, not byte address. Be careful not to upload to the wrong region. But in this case if it's at word address 0x6000 then you have the base selection bits set correctly.

Now here's the thing: In the OAM data format, the third byte of form 'cccccccc' is a tile offset, not an address offset. So to display the sprite graphic you uploaded to word address 0x6000 you should use 0 for this byte.
So in the current code, the OAM address for the sprite I presume is being added is 0, So I would redo that whole bit of code to add a second sprite except set 2102 and 2103 to 1 instead of 0 for sprite ID 1?
You don't need to do this by hand. The OAM DMA transfer will send all the sprites for you in one go.

From the snippet:

Code: Select all

	stz $2102		; set OAM address to 0
	stz $2103

	LDY #$0400
	STY $4300		; CPU -> PPU, auto increment, write 1 reg, $2104 (OAM data write)
	LDY #$0400
	STY $4302		; source offset
	LDY #$0220
	STY $4305		; number of bytes to transfer
	LDA #$7E
	STA $4304		; bank address = $7E  (work RAM)
	LDA #$01
	STA $420B		;start DMA transfer
This does the actual DMA transfer to OAM. See the value that's getting stored in $4302? That's the address in main RAM of what gets sent to OAM. In this example it's hardcoded to 0x400. I'm not sure why. It's probably an error in the example. It should probably refer to the SpriteBuf variable label mentioned in previous code snippets. So the SpriteBuf is where you want to make your changes.
SNES NTSC 2/1/3 1CHIP | serial number UN318588627
Xeraster
Posts: 7
Joined: Tue Sep 12, 2017 4:16 pm

Re: [SNES developer noob] Help me understand how sprites wor

Post by Xeraster »

HihiDanni wrote:
Xeraster wrote: This does the actual DMA transfer to OAM. See the value that's getting stored in $4302? That's the address in main RAM of what gets sent to OAM. In this example it's hardcoded to 0x400. I'm not sure why. It's probably an error in the example. It should probably refer to the SpriteBuf variable label mentioned in previous code snippets. So the SpriteBuf is where you want to make your changes.
OOOOH that $0400 thing makes sense now, because in the SpriteInit section, SpriteBuf1 is defined as $0400, so it's the same. So in order to code a second sprite, I could use write the 4 bytes of data starting with $0404. But then I have to use the high table to write the next parts and it's 2 bits per sprite, just like you said in the very first thing you said when you first replied to this thread.
Now the first sprite writes #$54 to $0600 aka SpriteBuf2. For a second sprite, would I want to use #$55? Because in binary that's already 01010100. There's already 2 BG layers which logically I would expect to use up the first 2 bits. Then the 1 working sprite I already have which should be occupying bits 5 and 6 (0 for x coord 9th bit, and 1 for large sprite size), so #$55 would make it 01010101, giving my desired 2nd sprite 0 for bit 7 and 1 for bit 1. Right?

Also, once I do that, I should be able to go the part where it does the DMA transfers. But then instead of writing $0400 to registers $4300 and $4302, it would be $0404 and I would set the OAM address to 1 on registers $2102 and $2103. Is this a correct assumption?

I know I'm a noob but thanks for taking the time to help me so far.

EDIT: I'm trying to do that idea I just wrote about. It almost works except I still don't know how to use the sprite I put in the VRAM at $9000 AND it messes up the background into glitchy weirdness. BUT HEY, I got a second sprite to display. NOW I'm starting to get somewhere! (almost)

You did mention that the DMA OAM transfer is supposed to send all the sprites in one go. I couldn't get that to work (because I'm probably doing something wrong). So I just have 2 of those DMA transfer sprite data blocks. Here's what it looks like:

Code: Select all

	lda #($80-16)
	sta SpriteBuf1+sx
	lda #(224/2-16)
	sta SpriteBuf1+sy ;these 4 lines were already here
	
	lda #($80-10)
	sta $404
	lda #(224/2-16)
	sta $405

	;put sprite #0 on screen.
	lda #$54
	sta SpriteBuf2

	;set the sprite to the highest priority
	lda #$30			
	sta SpriteBuf1+spriority
	lda #$00		
	sta $408
	lda #$30;
	sta $407
My second DMA transfer block that I just added under the first one.

Code: Select all

;*********transfer sprite2 data

	lda #$01
	sta $2102		; set OAM address to 0
	lda #$01
	sta $2103

	LDY #$404
	STY $4300		; CPU -> PPU, auto increment, write 1 reg, $2104 (OAM data write)
	LDY #$404
	STY $4302		; source offset
	LDY #$0220
	STY $4305		; number of bytes to transfer
	LDA #$7E
	STA $4304		; bank address = $7E  (work RAM)
	LDA #$01
	STA $420B		;start DMA transfer
I'm getting closer, but what am I doing wrong now?
User avatar
HihiDanni
Posts: 186
Joined: Tue Apr 05, 2016 5:25 pm

Re: [SNES developer noob] Help me understand how sprites wor

Post by HihiDanni »

That DMA to OAM code is already there in the VBlank handler, so you don't need to write it again elsewhere. You'll notice in the comments that the number of bytes it transfers is 0x220, which is the full size of OAM, hence it transfers all sprites.

Since you mentioned that SpriteBuf1 is at 0x400 and SpriteBuf2 is at 0x600, then SpriteBuf1 must correspond to the low table and SpriteBuf2 the high table. While you're still getting the hang of the OAM format, I would suggest just playing with the low table for now and then learning the high table later. But for sprite number 0, you'd change the low table at the first four bytes in SpriteBuf1, and the first two bits (first byte) of SpriteBuf2. For sprite number 1, you'll change the low table at SpriteBuf1 + 4, and for the high table it's still the first byte of SpriteBuf2.

Now might be a good time to learn about how index addressing works. The idea is you set the X index register to, say, 4 (a four byte offset, or sprite number 1), and then use code like the following to do the store:

Code: Select all

ldx #4 ; Byte offset of 4 into the OAM low table
lda #$80
sta SpriteBuf1, x ; Set sprite 1's X coordinate to 128
As for the screen glitching, you probably wrote to the region of VRAM currently being used for either the BG tile graphics ("char") or the BG tilemaps.
SNES NTSC 2/1/3 1CHIP | serial number UN318588627
93143
Posts: 1717
Joined: Fri Jul 04, 2014 9:31 pm

Re: [SNES developer noob] Help me understand how sprites wor

Post by 93143 »

Xeraster wrote:There's already 2 BG layers which logically I would expect to use up the first 2 bits.
No, the BG layers have nothing to do with OAM. It's all sprites. Remember, the S-PPU is fixed-function; you can't trade sprites for BG layers. You get 128 sprites no matter what. You can't even turn off sprites, except by moving them offscreen.

Count the entries. There are exactly 512 bytes in low OAM, four bytes for each sprite. There are exactly 32 bytes in high OAM, with four sprites to a byte. That's 128 sprites.

EDIT: Sorry if that sounded condescending. I'm feeling a bit harried right now for unrelated reasons, and I was trying to make the point as clear as possible.
Post Reply