I'm trying to create a simple background looping system which can dynamically load backgrounds into the PPU. I just have a single background at the moment, and have the game scrolling horizontally, loading in each column from the background data one by one as the horizontal scroll reaches it. The dynamic loading of columns works fine, after much work, but I still need to load in the initial background when the game starts. I tried to build a simple procedure which reuses my column loading procedure, simply looping through it 32 times (for 32 columns) before the PPU is then enabled.
When I implement this though, it seems to break everything, even the aforementioned column loading loop contained in my NMI handler. All I can seem to work out based on the FCEUX debugger is that the palette data seemingly gets overwritten, implying that my writes to the PPU are hitting addresses that they're not supposed to. I can't work out why though, as the initial background loading is using mostly the same code as the subsequent writes. Plus it seems to even break the column loop itself during NMI handling. I feel that it must be something simple, like I'm not setting the PPU up properly during the initial load, or misunderstanding what's actually happening under the hood. If anyone could take a look at my code and see if they notice what's wrong about the 'LoadBackground' procedure which would be breaking everything else, it would really help me out, as I've been at this for days now with no progress.
I've attached what I believe to be the relevant pieces of code (not every procedure). I've also left a comment in the code against a particular line in the 'LoadBackground' procedure which I've identified as the culprit that actually breaks everything; if I comment that line the background still doesn't load, but it doesn't break the rest of my game loop.
(I've also attached the full project, if needed)
Code: Select all
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Procedure to Load Background Data into PPU Nametable
;; - Loop through and draw 32 columns of tiles
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.proc LoadBackground
lda #<BackgroundData
sta SourceAddr ; lo-byte of source address to background data lo-byte
lda #>BackgroundData
sta SourceAddr+1 ; hi-byte of source address to background data hi-byte
bit PPU_STATUS
lda #$20
sta NewColAddr+1 ; hi-byte of PPU Address to $20 (first nametable)
lda #$00
sta NewColAddr ; lo-byte of PPU Address to $00
LoopBackgroundColumn:
jsr DrawNewColumn ; draw a new column
;inc NewColAddr ; update PPU Address lo-byte to next column
; <- CURRENTLY BREAKS GAME PALETTE AND SUBSEQUENT COLUMN LOADING WHEN ENABLED
lda Column
cmp #0 ; if current column is 0 (wrapped around from 32)
bne LoopBackgroundColumn ; Else: continue looping
rts
.endproc
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Reset Handler
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Reset:
INIT_NES
InitVariables:
lda #0
sta Frame
sta Clock60
ldx #0
lda SpriteData,x ; get Sprite Data at index 0 (y-position)
sta YPos+1 ; store Sprite Data y-position in YPos pixels
ldx #3
lda SpriteData,x ; get Sprite Data at index 3 (x-position)
sta XPos+1 ; store Sprite Data x-position in XPos pixels
lda #0
sta XScroll+1
sta XScroll
sta Row
sta Column
lda #1
sta NewColumn ; setup to draw new column immediately
Main:
jsr LoadPalette ; load Palette Data
jsr LoadSprites ; load Sprites
lda #%00010100 ; disable NMI, Set Background to second Pattern Table
sta PPU_CTRL
jsr LoadBackground ; load initial Background Data
EnablePPURendering:
bit PPU_STATUS
lda #%10010100 ; Enable NMI, Set Background to second Pattern Table
sta PPU_CTRL
lda #0
sta PPU_SCROLL
sta PPU_SCROLL
lda #%00011110
sta PPU_MASK ; Set PPU_MASK bits to show background
LoopForever:
jmp LoopForever
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; NMI Handler
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
NMI:
lda #$00 ; disable rendering
sta PPU_MASK
NewColumnCheck:
lda NewColumn
cmp #1 ; if new column flag set
bne FinishColumn
jsr DrawNewColumn ; If it is, proceed to draw a new column of tiles
FinishColumn:
jsr ScrollBackground
jsr RefreshRendering
jsr CheckColumnAddresses
rti
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Routine to check if a new column of tiles needs to be drawn
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.proc CheckColumnAddresses
NewColumnCheck:
lda XScroll+1
and #%00000111 ; Check if the scroll a multiple of 8
bne NoNewColumn ; Else: we still don't need to draw a new column
lda XScroll
cmp #0 ; Check if scroll fraction is 0
bne NoNewColumn ; Else: we still don't need to draw a new column
jsr SetColumnAddresses
jmp FinishColumn
NoNewColumn:
lda #0
sta NewColumn
FinishColumn:
rts
.endproc
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Routine to set a new column of tiles
;; - Calculate lo-byte & hi-byte of PPU destination address
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.proc SetColumnAddresses
lda #1
sta NewColumn
lda XScroll+1
lsr
lsr
lsr ; divide by 8 to get column (pixel position 8 -> column 1)
sta NewColAddr ; lo-byte of destination address
lda CurNam
eor #1 ; invert current nametable to 0 or 1, into accumulator
asl
asl ; multiply by 4, get either $00 -> $00 or $01 -> $04
clc
adc #$20 ; add to $20 to get either $20 or $24
sta NewColAddr+1 ; hi-byte of destination address ($20XX or $24XX)
lda #<BackgroundData
sta SourceAddr ; lo-byte of source address to background data lo-byte
lda #>BackgroundData
sta SourceAddr+1 ; hi-byte of source address to background data hi-byte
rts
.endproc
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Routine to draw a new column of tiles off-screen as we scroll horizontally
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.proc DrawNewColumn
PPU_SETADDR_VALUE NewColAddr
ldx #$04
ldy Column
ColumnLoop:
PPU_SETDATA_OFFSET (SourceAddr),y
inc Row ; increment current row
lda #30
cmp Row ; is current row last row
beq FinishColumn ; Then: finish loop
tya
clc
adc #32 ; add 32 to y-offset, to next row
tay
bcc ColumnLoop ; if y not exceed 256 and set Carry, continue loop
NextBatch:
inc SourceAddr+1 ; increment hi-byte of source, to next batch
ldy Column ; reset y offset
jmp ColumnLoop ; restart draw loop
FinishColumn:
lda Column
clc
adc #1 ; increment current column
and #%00011111 ; drop left-most bit to clamp value to 32, wrap around to 0
sta Column
rts
.endproc