For my first demonstration of sprites I decided to go with your typical bouncing sprite. Instead of using a ball, the text message "Hello Sprites!" Is used. Due to the NES limitation of only allowing 8 sprites on a line, the message is broken into two lines of text. This version manually manipulates sprite memory instead of using the more common DMA method that will be covered next. To my surprise, this actually lead to issues with the emulator if ran with the default fast PPU emulation though worked fine when ran with the slower PPU emulator. This is a good reminder that emulators are not always accurate, though developing with them is much faster than developing on real hardware.
The complete source code for this project is at http://blazinggames.com/books/NES/. Only the most relevant sections of code are included.
The program starts out with pretty standard initialization code. The screen is then filled with a checkerboard pattern instead of blank spaces so it will be clear that the sprites are appearing above the screen image (background). Next we have the task of setting up the sprites. In our case we are just copying this info from a table in ROM into the PPU Sprite Memory. Variables for holding the top corner of the block of sprites and the movement directions are then set up. The main loop simply waits for the VBlank flag to be set and then calls the moveSprite function which has all the movement logic.
 ; initialize the sprites
 LDA #0
 TAY
 STA $2003
 ; fill sprite memory with data from ROM
InitSprites_dataLoop:
 LDA hello_sprite_data,Y
 STA $2004
 INY
 CPY #52
 BNE InitSprites_dataLoop
 
 ; fill rest of sprite memory with 255 so that it will not be visible
 LDA #255 
InitSprites_fillLoop:
 STA $2004
 INY
 BNE InitSprites_fillLoop
  
 ; set sprite variables 
 LDA #0
 STA SPRITE_X
 STA SPRITE_Y
 LDA #1
 STA H_ADJUST
 STA V_ADJUST
The MoveSprites function is fairly large but also simple. The first chunk of code shows how the coordinates are adjusted. Each frame the block's top corner is adjusted by the current direction variables. Hits against the screen bounds are then performed with hits resulting in the direction being changed. What may be confusing, and something I will definitely be writing about in the future, 255 is used for -1.
MoveSprites:
 ; First adjust top corner of sprite block
 LDA SPRITE_X
 CLC
 ADC H_ADJUST
 STA SPRITE_X
 ; Has hit left edge?
 BNE MoveSprite_checkRightEdge
 ; if so set horizontal adjust to +1
 LDA #1
 STA H_ADJUST
MoveSprite_checkRightEdge:
 ; if has hit right edge?
 CMP #192
 BNE MoveSprite_vertical
 ; if hit right edge, set horizontal adjust to -1
 LDA #$FF ; -1
 STA H_ADJUST
; vertical tests similar to above and not included here, see source file for full code.
Once we know the coordinates for the top of the block of sprites, we can calculate the position of the sprites. As the bottom calculations are the more complex ones, here is the code for setting the bottom line of sprites.
 ; we reach here when top row done, so prepare bottom row
 LDA SPRITE_Y
 CLC
 ADC #8
 STA TEMP_Y ; Y coordinate to use for bottom row of sprites
 LDA SPRITE_X
 STA TEMP_X ; X coordinate starts at block x coordinate
  ; Y register already correct so no need to set
 LDX #8  ; 8 sprites in bottom row need to be counted
MoveSprite_bottomRowAdjust:
 STY $2003
 LDA TEMP_Y
 STA $2004 ; write adjusted sprite Y coordinate
 INY  ; Adjust index to point to sprite X data
 INY
 INY
 STY $2003 ; tell PPU we want to write sprite X
 LDA TEMP_X
 STA $2004 ; Write sprite X coordinate
 CLC
 ADC #8  ; add 8 to this coordinate for next sprite
 STA TEMP_X
 INY  ; set up index for next sprite
 DEX  ; and adjust countdown
 BNE MoveSprite_bottomRowAdjust
As you can see it is fairly simple. Most NES programs, however, do not manipulate sprites directly as we are doing so we will rewrite this program to take advantage of interrupts and DMA,as well as explain what interrupts and DMA shortly but next week will be a postmortem possibly followed by something else.
Subscribe to:
Post Comments (Atom)

No comments:
Post a Comment