In this post, we’re gonna take a look at the DrawSprite macro and see what it does and how it does it (as a whole and per segment of code).
The DrawSprite macro, as the name suggests, draws a sprite on screen. The macro takes four arguments: the x-position on screen, the y-position on screen, the sprite character to draw, and the attribute data. The macro is used as follows:
;; DrawSprite arg0, arg1, arg2, arg3
;; arg0 = the sprite's x-position on screen
;; arg1 = the sprite's y-position on screen
;; arg2 = the index number in the character table
;; arg3 = the attributes (palette number, x/y mirroring, et cetera)
DrawSprite #$10, #$20, #$71, #%00000000
This example draws the sprite in position $71, 16 pixels from the left, 32 pixels from the top, without mirroring, using sprite palette 0.
The macro’s code, found in GameEngineData\Routines\BASE_4_5\System\Macros\DrawSprite.asm, looks like this:
MACRO DrawSprite arg0, arg1, arg2, arg3
;arg0 = x
;arg1 = y
;arg2 = chr table value
;arg3 = attribute data
TYA
PHA
LDY spriteRamPointer
LDA arg1
STA SpriteRam,y
INY
LDA arg2
STA SpriteRam,y
INY
LDA arg3
STA SpriteRam,y
INY
LDA arg0
STA SpriteRam,y
INY
LDA spriteRamPointer
CLC
ADC #$04
STA spriteRamPointer
PLA
TAY
ENDM
Let’s dissect this code, piece by piece.
MACRO DrawSprite arg0, arg1, arg2, arg3
;arg0 = x
;arg1 = y
;arg2 = chr table value
;arg3 = attribute data
This is where the macro is started. The name of the macro is DrawSprite and it takes four arguments, explained in the comment lines below.
TYA PHA
These two lines transfer the value in the Y-register to the accumulator, and then pushes its value onto the stack. This is done to temporarily store the value, so we can reuse the Y-register for other purposes, and retrieve its original value later when we’re done with the macro.
LDY spriteRamPointer
This takes the value of spriteRamPointer, and loads it into the Y-register. The sprite RAM pointer is a value which holds the current position in sprite RAM. This variable is used so data from existing sprites is not overwritten in RAM. Without this variable, this code may corrupt existing sprites on screen, like the player or monsters.
Writing sprite data to RAM occurs in the following order: the y-position first, then the sprite character, then the attributes, and finally the x-position of the sprite. this is how the NES hardware handles sprites.
LDA arg1 STA SpriteRam,y INY
This snippet loads arg1, which is the y-position of the sprite, into the accumulator, and then stores it in the appropriate byte in sprite RAM. Finally, the value of the Y-register gets increased by one, so the next value will be stored in the next available byte in sprite RAM.
LDA arg2 STA SpriteRam,y INY LDA arg3 STA SpriteRam,y INY LDA arg0 STA SpriteRam,y INY
This process gets repeated three more times, but for the sprite character, the attributes, and the x-position of the sprite.
LDA spriteRamPointer CLC ADC #$04 STA spriteRamPointer
This segment adds four to the spriteRamPointer variable, so when the next sprite needs to be drawn on screen, it uses a new place in sprite RAM. Without this segment, the freshly drawn sprite will be overwritten when the macro gets executed another time, resulting in the original sprite to disappear from screen.
PLA TAY ENDM
Finally, the original value that was stored in the Y-register gets retrieved from the stack, and the compiler gets told the macro ends here.
Putting it all together
MACRO DrawSprite arg0, arg1, arg2, arg3 ;; Define DrawSprite macro, take four arguments ;arg0 = x ;arg1 = y ;arg2 = chr table value ;arg3 = attribute data TYA ;; Transfer the Y-register's value to the accumulator PHA ;; Push the accumulator's value onto the stack LDY spriteRamPointer ;; Load the current sprite RAM pointer value in the Y-register LDA arg1 ;; Load the second argument (y-position) into the accumulator STA SpriteRam,y ;; Store its value in the correct sprite RAM position INY ;; Increase the Y-register's value by one LDA arg2 ;; Load the third argument (sprite character index) into the accumulator STA SpriteRam,y ;; Store its value in the next sprite RAM position INY ;; Increase the Y-register's value by one LDA arg3 ;; Load the fourth argument (attribute data) into the accumulator STA SpriteRam,y ;; Store its value in the next sprite RAM position INY ;; Increase the Y-register's value by one LDA arg0 ;; Load the first argument (x-position) into the accumulator STA SpriteRam,y ;; Store its value in the next sprite RAM position INY ;; Increase the Y-register's value by one (this seems unneccessary) LDA spriteRamPointer ;; Load the sprite RAM pointer value into the accumulator CLC ;; Clear the carry (prevents an erroneous +1 if the carry flag is set) ADC #$04 ;; Add #4 to the accumulator STA spriteRamPointer ;; Store the new value into the sprite RAM pointer variable PLA ;; Pull value from the stack into the accumulator TAY ;; Transfer the accumulator's value into the Y-register ENDM ;; End of macro