Deep Dive: DrawSprite

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