Search code examples
assemblysprite65816

How to modify sprite position using 65c816 Assembly for the SNES?


I am trying to modify my sprite's position, but I can't figure out how to

I have spent hours searching for answers, but none of which work with the assembler I am using: WLA-DX. I am extremely new to 6502 assembly, so forgive me if my logic is really skewed. I was following the tutorial on georgjz's tutorial on GitHub. My sprite is just a single colored square and the pallet is the default pallet that comes with yy-chr

Header.inc

;==LoRom==      ; We'll get to HiRom some other time.

.MEMORYMAP                      ; Begin describing the system architecture.
  SLOTSIZE $8000                ; The slot is $8000 bytes in size. More details on slots later.
  DEFAULTSLOT 0
  SLOT 0 $8000                  ; Defines Slot 0's starting address.
.ENDME          ; End MemoryMap definition

.ROMBANKSIZE $8000              ; Every ROM bank is 32 KBytes in size
.ROMBANKS 8                     ; 2 Mbits - Tell WLA we want to use 8 ROM Banks

.SNESHEADER
  ID "SNES"                     ; 1-4 letter string, just leave it as "SNES"

  NAME "SNES Testing         "  ; Program Title - can't be over 21 bytes,
  ;    "123456789012345678901"  ; use spaces for unused bytes of the name.

  SLOWROM
  LOROM

  CARTRIDGETYPE $00             ; $00 = ROM only, see WLA documentation for others
  ROMSIZE $08                   ; $08 = 2 Mbits,  see WLA doc for more..
  SRAMSIZE $00                  ; No SRAM         see WLA doc for more..
  COUNTRY $01                   ; $01 = U.S.  $00 = Japan  $02 = Australia, Europe, Oceania and Asia  $03 = Sweden  $04 = Finland  $05 = Denmark  $06 = France  $07 = Holland  $08 = Spain  $09 = Germany, Austria and Switzerland  $0A = Italy  $0B = Hong Kong and China  $0C = Indonesia  $0D = Korea
  LICENSEECODE $00              ; Just use $00
  VERSION $00                   ; $00 = 1.00, $01 = 1.01, etc.
.ENDSNES

.SNESNATIVEVECTOR               ; Define Native Mode interrupt vector table
  COP EmptyHandler
  BRK EmptyHandler
  ABORT EmptyHandler
  NMI VBlank
  IRQ EmptyHandler
.ENDNATIVEVECTOR

.SNESEMUVECTOR                  ; Define Emulation Mode interrupt vector table
  COP EmptyHandler
  ABORT EmptyHandler
  NMI EmptyHandler
  RESET Start                   ; where execution starts
  IRQBRK EmptyHandler
.ENDEMUVECTOR

.BANK 0 SLOT 0                  ; Defines the ROM bank and the slot it is inserted in memory.
.ORG 0                          ; .ORG 0 is really $8000, because the slot starts at $8000
.SECTION "EmptyVectors" SEMIFREE

EmptyHandler:
       rti

.ENDS

.EMPTYFILL $00                  ; fill unused areas with $00, opcode for BRK.  
                                ; BRK will crash the snes if executed.

Snes_Init.asm

.MACRO Snes_Init
    sei         ; Disabled interrupts
    clc         ; clear carry to switch to native mode
    xce         ; Xchange carry & emulation bit. native mode
    rep     #$18    ; Binary mode (decimal mode off), X/Y 16 bit
         ldx    #$1FFF  ; set stack to $1FFF
         txs

         jsr Init
 .ENDM

 .bank 0
 .section "Snes_Init" SEMIFREE
 Init:
    sep     #$20    ; X,Y,A are 8 bit numbers
    lda     #$8F    ; screen off, full brightness
    sta     $2100   ; brightness + screen enable register 
    stz     $2101   ; Sprite register (size + address in VRAM) 
    stz     $2102   ; Sprite registers (address of sprite memory [OAM])
    stz     $2103   ;    ""                       ""
    stz     $2105   ; Mode 0, = Graphic mode register
    stz     $2106   ; noplanes, no mosaic, = Mosaic register
    stz     $2107   ; Plane 0 map VRAM location
    stz     $2108   ; Plane 1 map VRAM location
    stz     $2109   ; Plane 2 map VRAM location
    stz     $210A   ; Plane 3 map VRAM location
    stz     $210B   ; Plane 0+1 Tile data location
    stz     $210C   ; Plane 2+3 Tile data location
    stz     $210D   ; Plane 0 scroll x (first 8 bits)
    stz     $210D   ; Plane 0 scroll x (last 3 bits) #$0 - #$07ff
    lda     #$FF    ; The top pixel drawn on the screen isn't the top one in the tilemap, it's the one above that.
    sta     $210E   ; Plane 0 scroll y (first 8 bits)
    sta     $2110   ; Plane 1 scroll y (first 8 bits)
    sta     $2112   ; Plane 2 scroll y (first 8 bits)
    sta     $2114   ; Plane 3 scroll y (first 8 bits)
    lda     #$07    ; Since this could get quite annoying, it's better to edit the scrolling registers to fix this.
    sta     $210E   ; Plane 0 scroll y (last 3 bits) #$0 - #$07ff
    sta     $2110   ; Plane 1 scroll y (last 3 bits) #$0 - #$07ff
    sta     $2112   ; Plane 2 scroll y (last 3 bits) #$0 - #$07ff
    sta     $2114   ; Plane 3 scroll y (last 3 bits) #$0 - #$07ff
    stz     $210F   ; Plane 1 scroll x (first 8 bits)
    stz     $210F   ; Plane 1 scroll x (last 3 bits) #$0 - #$07ff
    stz     $2111   ; Plane 2 scroll x (first 8 bits)
    stz     $2111   ; Plane 2 scroll x (last 3 bits) #$0 - #$07ff
    stz     $2113   ; Plane 3 scroll x (first 8 bits)
    stz     $2113   ; Plane 3 scroll x (last 3 bits) #$0 - #$07ff
    lda     #$80    ; increase VRAM address after writing to $2119
    sta     $2115   ; VRAM address increment register
    stz     $2116   ; VRAM address low
    stz     $2117   ; VRAM address high
    stz     $211A   ; Initial Mode 7 setting register
    stz     $211B   ; Mode 7 matrix parameter A register (low)
    lda     #$01
    sta     $211B   ; Mode 7 matrix parameter A register (high)
    stz     $211C   ; Mode 7 matrix parameter B register (low)
    stz     $211C   ; Mode 7 matrix parameter B register (high)
    stz     $211D   ; Mode 7 matrix parameter C register (low)
    stz     $211D   ; Mode 7 matrix parameter C register (high)
    stz     $211E   ; Mode 7 matrix parameter D register (low)
    sta     $211E   ; Mode 7 matrix parameter D register (high)
    stz     $211F   ; Mode 7 center position X register (low)
    stz     $211F   ; Mode 7 center position X register (high)
    stz     $2120   ; Mode 7 center position Y register (low)
    stz     $2120   ; Mode 7 center position Y register (high)
    stz     $2121   ; Color number register ($0-ff)
    stz     $2123   ; BG1 & BG2 Window mask setting register
    stz     $2124   ; BG3 & BG4 Window mask setting register
    stz     $2125   ; OBJ & Color Window mask setting register
    stz     $2126   ; Window 1 left position register
    stz     $2127   ; Window 2 left position register
    stz     $2128   ; Window 3 left position register
    stz     $2129   ; Window 4 left position register
    stz     $212A   ; BG1, BG2, BG3, BG4 Window Logic register
    stz     $212B   ; OBJ, Color Window Logic Register (or,and,xor,xnor)
    sta     $212C   ; Main Screen designation (planes, sprites enable)
    stz     $212D   ; Sub Screen designation
    stz     $212E   ; Window mask for Main Screen
    stz     $212F   ; Window mask for Sub Screen
    lda     #$30
    sta     $2130   ; Color addition & screen addition init setting
    stz     $2131   ; Add/Sub sub designation for screen, sprite, color
    lda     #$E0
    sta     $2132   ; color data for addition/subtraction
    stz     $2133   ; Screen setting (interlace x,y/enable SFX data)
    stz     $4200   ; Enable V-blank, interrupt, Joypad register
    lda     #$FF
    sta     $4201   ; Programmable I/O port
    stz     $4202   ; Multiplicand A
    stz     $4203   ; Multiplier B
    stz     $4204   ; Multiplier C
    stz     $4205   ; Multiplicand C
    stz     $4206   ; Divisor B
    stz     $4207   ; Horizontal Count Timer
    stz     $4208   ; Horizontal Count Timer MSB (most significant bit)
    stz     $4209   ; Vertical Count Timer
    stz     $420A   ; Vertical Count Timer MSB
    stz     $420B   ; General DMA enable (bits 0-7)
    stz     $420C   ; Horizontal DMA (HDMA) enable (bits 0-7)
    stz     $420D   ; Access cycle designation (slow/fast rom)
    cli         ; Enable interrupts
    rts
 .ends

Testing.asm

.include "header.inc"
.include "Snes_Init.asm"

SpriteData: .incbin "sprite.sprite"
ColorData: .incbin "sprite.pal"

VBlank:    ; Needed to satisfy interrupt definition in "Header.inc"
    RTI

Start:
    Snes_Init
    sei                     ; disable interrupts
    clc                     ; clear the carry flag
    xce                     ; switch the 65816 to native (16-bit mode)
    lda #$8f                ; force v-blanking
    sta $2100
    stz $4200            ; disable NMI

    ; transfer VRAM data
    stz $2116              ; set the VRAM address to $0000
    stz $2117
    lda #$80
    sta $2115              ; increment VRAM address by 1 when writing to $2119
    ldx #$00                ; set register X to zero, we will use X as a loop counter and offset

VRAMLoop:
    .16BIT
    lda SpriteData, X       ; get bitplane 0/2 byte from the sprite data
    sta $2118             ; write the byte in A to VRAM
    sta $0000             ; write the byte in A to VRAM
    inx                     ; increment counter/offset
    lda SpriteData, X       ; get bitplane 1/3 byte from the sprite data
    sta $2119             ; write the byte in A to VRAM
    sta $0000             ; write the byte in A to VRAM
    inx                     ; increment counter/offset
    cpx #$20                ; check whether we have written $04 * $20 : $80 bytes to VRAM (four sprites)
    bcc VRAMLoop            ; if X is smaller than $80, continue the loop

    ; transfer CGRAM data
    lda #$80
    sta $2121               ; set CGRAM address to $80
    ldx #$00                ; set X to zero, use it as loop counter and offset

CGRAMLoop:
    lda ColorData, X        ; get the color low byte
    sta $2122              ; store it in CGRAM
    inx                     ; increase counter/offset
    lda ColorData, X        ; get the color high byte
    sta $2122              ; store it in CGRAM
    inx                     ; increase counter/offset
    cpx #$20                ; check whether 32/$20 bytes were transfered
    bcc CGRAMLoop           ; if not, continue loop

    stz $2102             ; set the OAM address to ...
    stz $2103             ; ...at $0000

    ; OAM data for first sprite
    lda #$78      ; horizontal position of first sprite
    sta $2104
    lda #$68       ; vertical position of first sprite
    sta $2104
    lda #$00                ; name of first sprite
    sta $2104
    lda #$00                ; no flip, prio 0, palette 0
    sta $2104

    ; make Objects visible
    lda #$10
    sta $212C
    ; release forced blanking, set screen to full brightness
    lda #$0f
    sta $2100
    jmp GameLoop            ; all initialization is done

GameLoop:
    wai                     ; wait for NMI / V-Blank
    jmp GameLoop

I am trying to at least get the sprite to move to the right 1 pixel per frame, but the sprite isn't moving at all

===========================================================================

Edit: I added the code from the comment by @Michael, and nothing has changed. Here is the code that I updated:

Snes_Init.asm

 .define SpriteX $00A0
 Init:
    sep     #$20    ; X,Y,A are 8 bit numbers
    lda     #$78
    sta     SpriteX
    ;Initialization code
    rts
.ends

Testing.asm

VBlank:    ; Needed to satisfy interrupt definition in "Header.inc"
    jsr MoveSprite
    RTI

; Code

CGRAMLoop:
    ;More code

    ; OAM data for first sprite
    lda SpriteX      ; horizontal position of first sprite
    sta $2104
    lda #$68       ; vertical position of first sprite
    sta $2104
    lda #$00                ; name of first sprite
    sta $2104
    lda #$00                ; no flip, prio 0, palette 0
    sta $2104

    ;Even more code

and I added the MoveSprite subroutine after CGRAMLoop


Solution

  • I don't see any code that actually will change the position of your sprite.

    But let's say that you have a zeropage variable where you store the current X position:

    .define spriteX  $00A0  ; I used address $A0 as an example. Just pick some address
                            ; that you aren't already using for something else
    

    And somewhere during initialization you give it some initial value:

    sep #$20
    lda #$78
    sta spriteX
    

    Then you could write a subroutine that increments the value and writes it to OAM:

    MoveSprite:
        php
        sep #$20
        stz $2102
        stz $2103 
        inc spriteX
        lda spriteX
        sta $2104
        lda #$68                            
        sta $2104  ; This write is necessary even if you're not changing the Y position,
                   ; because there's some internal latching going on in the PPU.
        plp
        rts
    

    And then you could call that subroutine from your VBlank interrupt handler.
    Note that you may want an additional counter to only increment the position every other frame (or whatever interval you like); otherwise the sprite will scroll by fairly quickly.


    You also need to enable VBlank NMI for the interrupt to actually be triggered:

    lda #$80
    sta $4200        ; Enable VBlank NMI
    
    jmp GameLoop     ; all initialization is done