SNES programming tutorial. Example 7.
I made a simple Pong demo to show sprite collisions.
Well… I was trying to keep it simple, but I decided to use some of the more complicated code I have previously written. Copied to the library.asm file from some of the EasySNES files. oam_spr (copies one sprite to the buffer), oam_meta_spr (copies multiple sprites to the buffer), oam_clear (clears the buffer), map_offset (gets an address from a specific x/y coordinate in a map). I did change the return from these functions from RTL to RTS, because all of our code is in the same bank.
Link if you are interested.
NOTE: I will probably change the metasprite code. It is very slow.
check_collision is new. I will discuss that a bit later.
Let’s talk about the process of making this. I made a circle gradient in GIMP for the background, and converted to indexed 4 color (with dithering).
We could have used Mode 0 for that, huh? But I stuck with Mode 1 anyway. Saved as uncompressed PNG. Converted to .chr .map .pal with superfamiconv. Loaded the background in M1TE (my tile/map editor).
Then I drew some numbers for BG3, and filled a little on the top and bottom.
Clicked the priority checkbox for this map.
Saved all the maps and tiles and palette.
Now I opened SPEZ (my sprite editor) and drew some simple box shapes for the ball and paddle. Saved them as metasprites.asm and saved their tiles (chr) and palette.
Everything is .incbin -ed in the main.asm file. We are loading everything just like the previous examples, with DMAs to the VRAM. One difference is that I’m turning everything on for the main screen.
lda #ALL_ON_SCREEN ; $1f
sta main_screen ; $212c
We didn’t really need BG2, since it’s blank. Oh well.
You might notice that I wrote a macro for DMAs to the VRAM. This made the code a little easier to read and write. Let’s look at an example…
DMA_VRAM $700, Map1, $6000
This is the DMA_VRAM macro definition…
.macro DMA_VRAM length, src_addr, dst_addr
;dst is address in the VRAM
;a should be 8 bit, xy should be 16 bit
sta $4300 ; transfer mode, 2 registers 1 write
; $2118 and $2119 are a pair Low/High
lda #$18 ; $2118
sta $4301 ; destination, vram data
stx $4302 ; source
sta $4304 ; bank
stx $4305 ; length
sta $420b ; start dma, channel 0
So where it says length, the macro will insert the $700 bytes (not $800, because the screen is only 224 pixels high, so I’m not filling the entire 256 pixel high map). Where it says src_addr, it replaces it with Map1. Where it says dst_addr, it replaces it with VRAM address $6000. Doesn’t this look nicer though?
DMA_VRAM $700, Map1, $6000
Simple. Elegant. Easy to read. Macros are your friends.
Everything between InfiniteLoop and line 426 jmp InfiniteLoop is the game loop. Every frame we wait till v-blank. Copy the OAM_BUFFER to the OAM. Print the score to the top of the screen. Read the controllers. Move the paddles if up or down are pressed.
lda pad1 and #KEY_UP beq @not_up @up: A8 lda paddle1_y cmp #$20 ;max up beq @not_up ;too far up bcc @not_up dec paddle1_y dec paddle1_y dec paddle2_y dec paddle2_y @not_up:
This code is moving both paddles, because this is just example code. You could modify it, so that controller2 moves the paddle on the right. Copy this whole thing, and replace pad1 with pad2, and only move paddle2. Also change the label names, so you don’t have duplicates.
We are only moving the ball while it is “active”. Press START to make it active, and choose a random direction to go (based on a frame counter).
ball_x_speed and ball_y_speed are the directions of the ball. Either 1 or -1 ($ff).
If the ball is active, it moves up/down until it reaches the ceiling or floor.
;bounce off ceilings
;bounce off floor
lda #$ff ; -1
It moves left/right until it reaches the end of the room. But we want it to bounce off the paddles, so we need to check collisions with hitboxes. I wrote this a long time ago (modified slightly). It’s the check_collision function in the library.asm file.
So we need the dimensions and location of 2 boxes. That’s 8 numbers, that I copy to these variables…
obj1x, obj1w, obj1y, obj1h
obj2x, obj2w, obj2y, obj2h
x = left side of sprite object
w = width (minus 1)
y = top side of the sprite object
h = height (minus 1)
I defined some of these with constants at the top of main.asm
BALL_SIZE = 7
PADDLE_W = 7
PADDLE_H = 47
Of course, the x and y values are changing. Those are defined as variables in the zero page (direct page).
I copy these to the obj1 obj2 stuff, and then call check_collision, which sets the “collision” variable to 0 or 1. If collision is true, we bounce the ball.
I’m also drawing the sprites each frame. That uses the oam_meta_spr function to read from a metasprite data set in metasprites.asm.
I’m using a similar approach as the collision code. I’m copying a bunch of values to the spr_h*, spr_x, spr_y, and then loading A and X with the address of the metasprite data…
lda #.loword(Meta_00) ;left paddle
And this will automatically put all the data in the OAM_BUFFER at the correct x and y positions. It also adjusts the high table bit shifting and keeps track of exactly how many sprites have been added (sprid).
*spr_h is just that extra 9th X bit. It should be 0 or 1. We are keeping it 0 for all these examples, for simplicity. This function is a bit complex, but don’t worry about it right now.
Writing to the background
The print_score function always runs during v-blank. It has to, because it is writing to the VRAM. I’m using this map_offset function (in library.asm). It wants you to load X with the tile’s x position 0-31 and load Y with the tile’s y position 0-31. If you only have pixel X and Y, just shift right (lsr a) 3 times to get the 0-255 value to 0-31 (tile) for 8×8 tiles.
map_offset does some bit shifting to convert that to a VRAM address. It returns A16 = the offset. You add that to the base address (our BG3 map is at $7000).
jsr map_offset ; returns a16 = vram address offset
adc #$7000 ;layer 3 map
and then copying 2 values per number on screen. We are writing with the VRAM increment set to +32. That means that the second write will go below the first one.
sta vram_inc ;$2115
Some of these values might be hard to understand, like, why are we adding $10 to the points_L? Our tiles for numbers begins at $10. 0 is at $10. 1 is at $11, etc. Anyway, here’s our finished program. Press START to get it going.
Try to make this into a game by having controller 2 to move the right paddle.
The ball is a bit slow, though. Moving 2 pixels per frame might be too fast. It would be best to use “fixed point” math, that’s a 16-bit variable for ball speed and position, where the upper byte refers to a pixel position, and the lower byte is a sub-pixel position (and speed). Then we could have 1 1/2 pixel per frame movement.
I wish we had some sound effects too. Maybe a little later for that.