如何使用 65c816 程序集为 SNES 修改精灵位置?
How to modify sprite position using 65c816 Assembly for the SNES?
我正在尝试修改精灵的位置,但我不知道该怎么做
我花了几个小时寻找答案,但 none 其中与我正在使用的汇编程序一起工作: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 00 ; The slot is 00 bytes in size. More details on slots later.
DEFAULTSLOT 0
SLOT 0 00 ; Defines Slot 0's starting address.
.ENDME ; End MemoryMap definition
.ROMBANKSIZE 00 ; 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 [=10=] ; [=10=] = ROM only, see WLA documentation for others
ROMSIZE ; = 2 Mbits, see WLA doc for more..
SRAMSIZE [=10=] ; No SRAM see WLA doc for more..
COUNTRY ; = U.S. [=10=] = Japan = Australia, Europe, Oceania and Asia = Sweden = Finland = Denmark = France = Holland = Spain = Germany, Austria and Switzerland [=10=]A = Italy [=10=]B = Hong Kong and China [=10=]C = Indonesia [=10=]D = Korea
LICENSEECODE [=10=] ; Just use [=10=]
VERSION [=10=] ; [=10=] = 1.00, = 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 00, because the slot starts at 00
.SECTION "EmptyVectors" SEMIFREE
EmptyHandler:
rti
.ENDS
.EMPTYFILL [=10=] ; fill unused areas with [=10=], 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 # ; Binary mode (decimal mode off), X/Y 16 bit
ldx #FFF ; set stack to FFF
txs
jsr Init
.ENDM
.bank 0
.section "Snes_Init" SEMIFREE
Init:
sep # ; X,Y,A are 8 bit numbers
lda #F ; screen off, full brightness
sta 00 ; brightness + screen enable register
stz 01 ; Sprite register (size + address in VRAM)
stz 02 ; Sprite registers (address of sprite memory [OAM])
stz 03 ; "" ""
stz 05 ; Mode 0, = Graphic mode register
stz 06 ; noplanes, no mosaic, = Mosaic register
stz 07 ; Plane 0 map VRAM location
stz 08 ; Plane 1 map VRAM location
stz 09 ; Plane 2 map VRAM location
stz 0A ; Plane 3 map VRAM location
stz 0B ; Plane 0+1 Tile data location
stz 0C ; Plane 2+3 Tile data location
stz 0D ; Plane 0 scroll x (first 8 bits)
stz 0D ; Plane 0 scroll x (last 3 bits) #[=11=] - #ff
lda #$FF ; The top pixel drawn on the screen isn't the top one in the tilemap, it's the one above that.
sta 0E ; Plane 0 scroll y (first 8 bits)
sta 10 ; Plane 1 scroll y (first 8 bits)
sta 12 ; Plane 2 scroll y (first 8 bits)
sta 14 ; Plane 3 scroll y (first 8 bits)
lda # ; Since this could get quite annoying, it's better to edit the scrolling registers to fix this.
sta 0E ; Plane 0 scroll y (last 3 bits) #[=11=] - #ff
sta 10 ; Plane 1 scroll y (last 3 bits) #[=11=] - #ff
sta 12 ; Plane 2 scroll y (last 3 bits) #[=11=] - #ff
sta 14 ; Plane 3 scroll y (last 3 bits) #[=11=] - #ff
stz 0F ; Plane 1 scroll x (first 8 bits)
stz 0F ; Plane 1 scroll x (last 3 bits) #[=11=] - #ff
stz 11 ; Plane 2 scroll x (first 8 bits)
stz 11 ; Plane 2 scroll x (last 3 bits) #[=11=] - #ff
stz 13 ; Plane 3 scroll x (first 8 bits)
stz 13 ; Plane 3 scroll x (last 3 bits) #[=11=] - #ff
lda # ; increase VRAM address after writing to 19
sta 15 ; VRAM address increment register
stz 16 ; VRAM address low
stz 17 ; VRAM address high
stz 1A ; Initial Mode 7 setting register
stz 1B ; Mode 7 matrix parameter A register (low)
lda #
sta 1B ; Mode 7 matrix parameter A register (high)
stz 1C ; Mode 7 matrix parameter B register (low)
stz 1C ; Mode 7 matrix parameter B register (high)
stz 1D ; Mode 7 matrix parameter C register (low)
stz 1D ; Mode 7 matrix parameter C register (high)
stz 1E ; Mode 7 matrix parameter D register (low)
sta 1E ; Mode 7 matrix parameter D register (high)
stz 1F ; Mode 7 center position X register (low)
stz 1F ; Mode 7 center position X register (high)
stz 20 ; Mode 7 center position Y register (low)
stz 20 ; Mode 7 center position Y register (high)
stz 21 ; Color number register ([=11=]-ff)
stz 23 ; BG1 & BG2 Window mask setting register
stz 24 ; BG3 & BG4 Window mask setting register
stz 25 ; OBJ & Color Window mask setting register
stz 26 ; Window 1 left position register
stz 27 ; Window 2 left position register
stz 28 ; Window 3 left position register
stz 29 ; Window 4 left position register
stz 2A ; BG1, BG2, BG3, BG4 Window Logic register
stz 2B ; OBJ, Color Window Logic Register (or,and,xor,xnor)
sta 2C ; Main Screen designation (planes, sprites enable)
stz 2D ; Sub Screen designation
stz 2E ; Window mask for Main Screen
stz 2F ; Window mask for Sub Screen
lda #
sta 30 ; Color addition & screen addition init setting
stz 31 ; Add/Sub sub designation for screen, sprite, color
lda #$E0
sta 32 ; color data for addition/subtraction
stz 33 ; Screen setting (interlace x,y/enable SFX data)
stz 00 ; Enable V-blank, interrupt, Joypad register
lda #$FF
sta 01 ; Programmable I/O port
stz 02 ; Multiplicand A
stz 03 ; Multiplier B
stz 04 ; Multiplier C
stz 05 ; Multiplicand C
stz 06 ; Divisor B
stz 07 ; Horizontal Count Timer
stz 08 ; Horizontal Count Timer MSB (most significant bit)
stz 09 ; Vertical Count Timer
stz 0A ; Vertical Count Timer MSB
stz 0B ; General DMA enable (bits 0-7)
stz 0C ; Horizontal DMA (HDMA) enable (bits 0-7)
stz 0D ; 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 #f ; force v-blanking
sta 00
stz 00 ; disable NMI
; transfer VRAM data
stz 16 ; set the VRAM address to [=12=]00
stz 17
lda #
sta 15 ; increment VRAM address by 1 when writing to 19
ldx #[=12=] ; 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 18 ; write the byte in A to VRAM
sta [=12=]00 ; write the byte in A to VRAM
inx ; increment counter/offset
lda SpriteData, X ; get bitplane 1/3 byte from the sprite data
sta 19 ; write the byte in A to VRAM
sta [=12=]00 ; write the byte in A to VRAM
inx ; increment counter/offset
cpx # ; check whether we have written * : bytes to VRAM (four sprites)
bcc VRAMLoop ; if X is smaller than , continue the loop
; transfer CGRAM data
lda #
sta 21 ; set CGRAM address to
ldx #[=12=] ; set X to zero, use it as loop counter and offset
CGRAMLoop:
lda ColorData, X ; get the color low byte
sta 22 ; store it in CGRAM
inx ; increase counter/offset
lda ColorData, X ; get the color high byte
sta 22 ; store it in CGRAM
inx ; increase counter/offset
cpx # ; check whether 32/ bytes were transfered
bcc CGRAMLoop ; if not, continue loop
stz 02 ; set the OAM address to ...
stz 03 ; ...at [=12=]00
; OAM data for first sprite
lda # ; horizontal position of first sprite
sta 04
lda # ; vertical position of first sprite
sta 04
lda #[=12=] ; name of first sprite
sta 04
lda #[=12=] ; no flip, prio 0, palette 0
sta 04
; make Objects visible
lda #
sta 2C
; release forced blanking, set screen to full brightness
lda #[=12=]f
sta 00
jmp GameLoop ; all initialization is done
GameLoop:
wai ; wait for NMI / V-Blank
jmp GameLoop
我试图至少让精灵每帧向右移动 1 个像素,但精灵根本没有移动
============================================= ==============================
编辑:我添加了@Michael 评论中的代码,没有任何改变。这是我更新的代码:
Snes_Init.asm
.define SpriteX [=13=]A0
Init:
sep # ; X,Y,A are 8 bit numbers
lda #
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 04
lda # ; vertical position of first sprite
sta 04
lda #[=14=] ; name of first sprite
sta 04
lda #[=14=] ; no flip, prio 0, palette 0
sta 04
;Even more code
并且我在 CGRAMLoop 之后添加了 MoveSprite 子程序
我没有看到任何实际会改变精灵位置的代码。
但是假设您有一个用于存储当前 X 位置的 zeropage 变量:
.define spriteX [=10=]A0 ; I used address $A0 as an example. Just pick some address
; that you aren't already using for something else
在初始化期间的某处你给它一些初始值:
sep #
lda #
sta spriteX
然后你可以编写一个子程序来递增该值并将其写入 OAM:
MoveSprite:
php
sep #
stz 02
stz 03
inc spriteX
lda spriteX
sta 04
lda #
sta 04 ; 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
然后您可以从 VBlank
中断处理程序中调用该子例程。
请注意,您可能需要一个额外的计数器来仅每隔一帧(或您喜欢的任何间隔)增加位置;否则精灵会很快滚动。
您还需要启用 VBlank NMI 才能真正触发中断:
lda #
sta 00 ; Enable VBlank NMI
jmp GameLoop ; all initialization is done
我正在尝试修改精灵的位置,但我不知道该怎么做
我花了几个小时寻找答案,但 none 其中与我正在使用的汇编程序一起工作: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 00 ; The slot is 00 bytes in size. More details on slots later.
DEFAULTSLOT 0
SLOT 0 00 ; Defines Slot 0's starting address.
.ENDME ; End MemoryMap definition
.ROMBANKSIZE 00 ; 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 [=10=] ; [=10=] = ROM only, see WLA documentation for others
ROMSIZE ; = 2 Mbits, see WLA doc for more..
SRAMSIZE [=10=] ; No SRAM see WLA doc for more..
COUNTRY ; = U.S. [=10=] = Japan = Australia, Europe, Oceania and Asia = Sweden = Finland = Denmark = France = Holland = Spain = Germany, Austria and Switzerland [=10=]A = Italy [=10=]B = Hong Kong and China [=10=]C = Indonesia [=10=]D = Korea
LICENSEECODE [=10=] ; Just use [=10=]
VERSION [=10=] ; [=10=] = 1.00, = 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 00, because the slot starts at 00
.SECTION "EmptyVectors" SEMIFREE
EmptyHandler:
rti
.ENDS
.EMPTYFILL [=10=] ; fill unused areas with [=10=], 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 # ; Binary mode (decimal mode off), X/Y 16 bit
ldx #FFF ; set stack to FFF
txs
jsr Init
.ENDM
.bank 0
.section "Snes_Init" SEMIFREE
Init:
sep # ; X,Y,A are 8 bit numbers
lda #F ; screen off, full brightness
sta 00 ; brightness + screen enable register
stz 01 ; Sprite register (size + address in VRAM)
stz 02 ; Sprite registers (address of sprite memory [OAM])
stz 03 ; "" ""
stz 05 ; Mode 0, = Graphic mode register
stz 06 ; noplanes, no mosaic, = Mosaic register
stz 07 ; Plane 0 map VRAM location
stz 08 ; Plane 1 map VRAM location
stz 09 ; Plane 2 map VRAM location
stz 0A ; Plane 3 map VRAM location
stz 0B ; Plane 0+1 Tile data location
stz 0C ; Plane 2+3 Tile data location
stz 0D ; Plane 0 scroll x (first 8 bits)
stz 0D ; Plane 0 scroll x (last 3 bits) #[=11=] - #ff
lda #$FF ; The top pixel drawn on the screen isn't the top one in the tilemap, it's the one above that.
sta 0E ; Plane 0 scroll y (first 8 bits)
sta 10 ; Plane 1 scroll y (first 8 bits)
sta 12 ; Plane 2 scroll y (first 8 bits)
sta 14 ; Plane 3 scroll y (first 8 bits)
lda # ; Since this could get quite annoying, it's better to edit the scrolling registers to fix this.
sta 0E ; Plane 0 scroll y (last 3 bits) #[=11=] - #ff
sta 10 ; Plane 1 scroll y (last 3 bits) #[=11=] - #ff
sta 12 ; Plane 2 scroll y (last 3 bits) #[=11=] - #ff
sta 14 ; Plane 3 scroll y (last 3 bits) #[=11=] - #ff
stz 0F ; Plane 1 scroll x (first 8 bits)
stz 0F ; Plane 1 scroll x (last 3 bits) #[=11=] - #ff
stz 11 ; Plane 2 scroll x (first 8 bits)
stz 11 ; Plane 2 scroll x (last 3 bits) #[=11=] - #ff
stz 13 ; Plane 3 scroll x (first 8 bits)
stz 13 ; Plane 3 scroll x (last 3 bits) #[=11=] - #ff
lda # ; increase VRAM address after writing to 19
sta 15 ; VRAM address increment register
stz 16 ; VRAM address low
stz 17 ; VRAM address high
stz 1A ; Initial Mode 7 setting register
stz 1B ; Mode 7 matrix parameter A register (low)
lda #
sta 1B ; Mode 7 matrix parameter A register (high)
stz 1C ; Mode 7 matrix parameter B register (low)
stz 1C ; Mode 7 matrix parameter B register (high)
stz 1D ; Mode 7 matrix parameter C register (low)
stz 1D ; Mode 7 matrix parameter C register (high)
stz 1E ; Mode 7 matrix parameter D register (low)
sta 1E ; Mode 7 matrix parameter D register (high)
stz 1F ; Mode 7 center position X register (low)
stz 1F ; Mode 7 center position X register (high)
stz 20 ; Mode 7 center position Y register (low)
stz 20 ; Mode 7 center position Y register (high)
stz 21 ; Color number register ([=11=]-ff)
stz 23 ; BG1 & BG2 Window mask setting register
stz 24 ; BG3 & BG4 Window mask setting register
stz 25 ; OBJ & Color Window mask setting register
stz 26 ; Window 1 left position register
stz 27 ; Window 2 left position register
stz 28 ; Window 3 left position register
stz 29 ; Window 4 left position register
stz 2A ; BG1, BG2, BG3, BG4 Window Logic register
stz 2B ; OBJ, Color Window Logic Register (or,and,xor,xnor)
sta 2C ; Main Screen designation (planes, sprites enable)
stz 2D ; Sub Screen designation
stz 2E ; Window mask for Main Screen
stz 2F ; Window mask for Sub Screen
lda #
sta 30 ; Color addition & screen addition init setting
stz 31 ; Add/Sub sub designation for screen, sprite, color
lda #$E0
sta 32 ; color data for addition/subtraction
stz 33 ; Screen setting (interlace x,y/enable SFX data)
stz 00 ; Enable V-blank, interrupt, Joypad register
lda #$FF
sta 01 ; Programmable I/O port
stz 02 ; Multiplicand A
stz 03 ; Multiplier B
stz 04 ; Multiplier C
stz 05 ; Multiplicand C
stz 06 ; Divisor B
stz 07 ; Horizontal Count Timer
stz 08 ; Horizontal Count Timer MSB (most significant bit)
stz 09 ; Vertical Count Timer
stz 0A ; Vertical Count Timer MSB
stz 0B ; General DMA enable (bits 0-7)
stz 0C ; Horizontal DMA (HDMA) enable (bits 0-7)
stz 0D ; 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 #f ; force v-blanking
sta 00
stz 00 ; disable NMI
; transfer VRAM data
stz 16 ; set the VRAM address to [=12=]00
stz 17
lda #
sta 15 ; increment VRAM address by 1 when writing to 19
ldx #[=12=] ; 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 18 ; write the byte in A to VRAM
sta [=12=]00 ; write the byte in A to VRAM
inx ; increment counter/offset
lda SpriteData, X ; get bitplane 1/3 byte from the sprite data
sta 19 ; write the byte in A to VRAM
sta [=12=]00 ; write the byte in A to VRAM
inx ; increment counter/offset
cpx # ; check whether we have written * : bytes to VRAM (four sprites)
bcc VRAMLoop ; if X is smaller than , continue the loop
; transfer CGRAM data
lda #
sta 21 ; set CGRAM address to
ldx #[=12=] ; set X to zero, use it as loop counter and offset
CGRAMLoop:
lda ColorData, X ; get the color low byte
sta 22 ; store it in CGRAM
inx ; increase counter/offset
lda ColorData, X ; get the color high byte
sta 22 ; store it in CGRAM
inx ; increase counter/offset
cpx # ; check whether 32/ bytes were transfered
bcc CGRAMLoop ; if not, continue loop
stz 02 ; set the OAM address to ...
stz 03 ; ...at [=12=]00
; OAM data for first sprite
lda # ; horizontal position of first sprite
sta 04
lda # ; vertical position of first sprite
sta 04
lda #[=12=] ; name of first sprite
sta 04
lda #[=12=] ; no flip, prio 0, palette 0
sta 04
; make Objects visible
lda #
sta 2C
; release forced blanking, set screen to full brightness
lda #[=12=]f
sta 00
jmp GameLoop ; all initialization is done
GameLoop:
wai ; wait for NMI / V-Blank
jmp GameLoop
我试图至少让精灵每帧向右移动 1 个像素,但精灵根本没有移动
============================================= ==============================
编辑:我添加了@Michael 评论中的代码,没有任何改变。这是我更新的代码:
Snes_Init.asm
.define SpriteX [=13=]A0
Init:
sep # ; X,Y,A are 8 bit numbers
lda #
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 04
lda # ; vertical position of first sprite
sta 04
lda #[=14=] ; name of first sprite
sta 04
lda #[=14=] ; no flip, prio 0, palette 0
sta 04
;Even more code
并且我在 CGRAMLoop 之后添加了 MoveSprite 子程序
我没有看到任何实际会改变精灵位置的代码。
但是假设您有一个用于存储当前 X 位置的 zeropage 变量:
.define spriteX [=10=]A0 ; I used address $A0 as an example. Just pick some address
; that you aren't already using for something else
在初始化期间的某处你给它一些初始值:
sep #
lda #
sta spriteX
然后你可以编写一个子程序来递增该值并将其写入 OAM:
MoveSprite:
php
sep #
stz 02
stz 03
inc spriteX
lda spriteX
sta 04
lda #
sta 04 ; 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
然后您可以从 VBlank
中断处理程序中调用该子例程。
请注意,您可能需要一个额外的计数器来仅每隔一帧(或您喜欢的任何间隔)增加位置;否则精灵会很快滚动。
您还需要启用 VBlank NMI 才能真正触发中断:
lda #
sta 00 ; Enable VBlank NMI
jmp GameLoop ; all initialization is done