AllDarnDavey
Active member
Just posting some Metroidvania scrolling improvements we've been working on in the help forum, where people could find it more easily. Along with my change to speed up the camera when the character is past the "scroll pad" so the camera will start to recenter itself until it reaches the scroll pad for that direction.
First you'll need a new byte added to ZeroPage RAM called camSpeedup.
Then you'll want to backup and replace your doUpdateCamera.asm with this one:
Code:
doUpdateCamera:
LDX camObject
LDA Object_h_speed_lo,x
STA tempA
LDA Object_h_speed_hi,x
STA tempB
LDA camX
AND #%11110000
STA tempz
LDA scrollByte
AND #%10100000
BNE +scrollEngaged
JMP skipScrollUpdate
+scrollEngaged:
LDA camX_hi
STA tempC ;; will hold. If this is changed by the end of the routine
;; then we need to update anything that should change when our camera
;; has crossed the screen boundary, such as screenFlags.
LDA scrollByte
AND #%10000000
BNE doHorizontalCameraUpdate
JMP noHorizontalCameraUpdate
doHorizontalCameraUpdate:
LDA scrollByte
AND #%01000000
BNE doRightCameraUpdate
;; is left camera update
LDA camX_lo
SEC
SBC tempA
SBC camSpeedup ;;add camera speed boost to recenter player
STA camX_lo
LDA camX
SBC tempB
STA temp
BCS +skipCheckForScrollScreenEdge
LDA ScreenFlags00
AND #%00100000
BEQ +skipCheckForScrollScreenEdge
JMP noHorizontalCameraUpdate
+skipCheckForScrollScreenEdge
LDA temp
STA camX
LDA camX_hi
SBC #$00
STA camX_hi
JSR getCamSeam
JMP noHorizontalCameraUpdate
doRightCameraUpdate
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Here, we have to check the right side of the screen
;;; to see if we're lined up with the edge.
LDA camX
ADC tempB
ADC #$FF
BCC +skipCheckForScrollScreenEdge
LDA ScreenFlags00
AND #%00010000
BEQ +skipCheckForScrollScreenEdge
JMP skipAllScrollSeamUpdate
+skipCheckForScrollScreenEdge
LDA camX_lo
clc
adc tempA
ADC camSpeedup ;;add camera speed boost to recenter player
STA camX_lo
LDA camX
adc tempB
STA camX
LDA camX_hi
adc #$00
STA camX_hi
JSR getCamSeam
noHorizontalCameraUpdate:
LDA scrollByte
AND #%00000001
BEQ +canUpdateScrollColumn
JMP skipScrollUpdate
+canUpdateScrollColumn
LDA scrollByte
AND #%00000010
BNE forceScrollColumnUpdate
LDA camX
AND #%11110000
CMP tempz
BNE +canUpdateScrollColumn2
JMP skipScrollUpdate
forceScrollColumnUpdate:
LDA scrollByte
AND #%11111101
STA scrollByte
+canUpdateScrollColumn2
LDA scrollByte
ORA #%00000101
STA scrollByte
;;;;;;;;;; DO SCROLL UPDATE.
SwitchBank #$16
LDY scrollUpdateScreen
LDA warpMap
BEQ +loadFromMap1
;; load from map 2
LDA NameTablePointers_Map2_lo,y
STA temp16
LDA NameTablePointers_Map2_hi,y
STA temp16+1
LDA AttributeTables_Map2_Lo,y
STA pointer
LDA AttributeTables_Map2_Hi,y
STA pointer+1
LDA CollisionTables_Map2_Lo,y
STA pointer6
LDA CollisionTables_Map2_Hi,y
STA pointer6+1
LDY camScreen
LDA CollisionTables_Map2_Lo,y
STA pointer2
LDA CollisionTables_Map2_Hi,y
STA pointer2+1
JMP +doneWithGettingOffset
+loadFromMap1
LDA NameTablePointers_Map1_lo,y
STA temp16
LDA NameTablePointers_Map1_hi,y
STA temp16+1
LDA AttributeTables_Map1_Lo,y
STA pointer
LDA AttributeTables_Map1_Hi,y
STA pointer+1
LDA CollisionTables_Map1_Lo,y
STA pointer6
LDA CollisionTables_Map1_Hi,y
STA pointer6+1
LDY camScreen
LDA CollisionTables_Map1_Lo,y
STA pointer2
LDA CollisionTables_Map1_Hi,y
STA pointer2+1
+doneWithGettingOffset:
ReturnBank
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; now we have pointers for the fetch.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; We can read from the pointers to get metatile data.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; jump to the bank
LDA scrollUpdateScreen
LSR
LSR
LSR
LSR
LSR
STA temp ; bank
LDA warpMap
BEQ +gotWarpMap
LDA temp
;; it was underground, add 8 to warp map
CLC
ADC #$08
sta temp
+gotWarpMap
SwitchBank temp
; LDX screenState
; LDY Mon1SpawnLocation,x
; LDA (pointer6),y
; STA scrollUpdateObjectLocation
; LDY Mon2SpawnLocation,x
; LDA (pointer6),y
; STA scrollUpdateObjectLocation+1
; LDY Mon3SpawnLocation,x
; LDA (pointer6),y
; STA scrollUpdateObjectLocation+2
; LDY Mon4SpawnLocation,x
; LDA (pointer6),y
; STA scrollUpdateObjectLocation+3
LDX screenState
LDY Monster1ID,x
LDA (pointer6),y
STA tempD
LDY Mon1SpawnLocation,x
JSR checkSeamForMonsterPosition
LDX screenState
LDY Monster2ID,x
LDA (pointer6),y
STA tempD
LDY Mon2SpawnLocation,x
JSR checkSeamForMonsterPosition
LDX screenState
LDy Monster3ID,x
LDA (pointer6),y
STA tempD
LDY Mon3SpawnLocation,x
JSR checkSeamForMonsterPosition
LDX screenState
LDy Monster4ID,x
LDA (pointer6),y
STA tempD
LDY Mon4SpawnLocation,x
JSR checkSeamForMonsterPosition
LDA scrollUpdateScreen
AND #%00000001
ASL
ASL
ORA #%00100000
STA temp1 ;; temp 1 now represents the high byte of the address to place
LDA scrollUpdateColumn
LSR
LSR
LSR
AND #%00011111
STA temp2 ;; temp 2 now represents the low byte of the pushes.
LDA scrollUpdateColumn
LSR
LSR
LSR
LSR
STA temp3
LDA #$00
STA scrollOffsetCounter
LDX #$00 ;; will keep track of scroll update ram.
LDA #$0F
STA tempA ;; will keep the track of how many tiles to draw.
;; #$0f is an entire column.
loop_LoadNametableMeta_column:
LDY temp3
LDA (temp16),y
STA temp
JSR doGetSingleMetaTileValues
LDA temp1
STA scrollUpdateRam,x
INX
LDA temp2
STA scrollUpdateRam,x
INX
LDA updateTile_00
STA scrollUpdateRam,x
INX
LDA temp1
STA scrollUpdateRam,x
INX
LDA temp2
CLC
ADC #$01
STA scrollUpdateRam,x
INX
LDA updateTile_01
STA scrollUpdateRam,x
INX
LDA temp1
STA scrollUpdateRam,x
INX
LDA temp2
CLC
ADC #$20
STA scrollUpdateRam,x
INX
LDA updateTile_02
STA scrollUpdateRam,x
INX
LDA temp1
STA scrollUpdateRam,x
INX
LDA temp2
CLC
ADC #$21
STA scrollUpdateRam,x
INX
LDA updateTile_03
STA scrollUpdateRam,x
INX
DEC tempA
LDA tempA
BEQ +doneWithNtLoad
;; not done with NT load. Need more tiles.
;;;;;;;;;;;;;;;;;;;;;;;;;;
;; update the 16 bit position of the new place to push tiles to.
LDA temp2
CLC
ADC #$40
STA temp2
LDA temp1
ADC #$00
STA temp1
;; update the tile read location.
LDA temp3
CLC
ADC #$10
STA temp3
JMP loop_LoadNametableMeta_column
+doneWithNtLoad
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;; add attributes to the update list.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; 23 = 00100011
;;;; 27 = 00100111
;;;; so the last bit of scrollUpdateScreen and shift it twice to the left
;;;; then or it with 00100000 to get the high byte of the attribute update table.
LDA scrollUpdateScreen
AND #%00000001
ASL
ASL
;ASL
ORA #%00100011
STA temp1 ;; this is now the high byte of the attribute table update
LDA scrollUpdateColumn
LSR
LSR
LSR
LSR
LSR
STA temp2 ;; temp 2 now represents the low byte of the pushes.
;; don't need a temp3 to keep track of pull position, because it will be 1 to 1.
LDA #$08
STA tempA ;; will keep the track of how many tiles to draw.
;; #$0f is an entire column.
loop_LoadAttribute_column:
LDY temp2
LDA (pointer),y
STA temp
LDA temp1
STA scrollUpdateRam,x
INX
LDA temp2
CLC
ADC #$c0
STA scrollUpdateRam,x
INX
LDA temp
STA scrollUpdateRam,x
INX
DEC tempA
LDA tempA
BEQ +doneWithAtLoad
LDA temp2
CLC
ADC #$08
STA temp2
JMP loop_LoadAttribute_column
+doneWithAtLoad
TXA
STA maxScrollOffsetCounter
LDA updateScreenData
ORA #%00000100
STA updateScreenData
LDA scrollUpdateColumn
LSR
LSR
LSR
LSR
STA temp1 ;; keeps track of where to place on the collision table.
LSR
STA temp2 ;; keeps track of the nubble being pulled from.
LDX #$0F ;; keep track of how many values to load are left.
LDA scrollUpdateScreen
AND #%00000001
BNE +doUpdateOddCt
;; do update even CT
;; to ct table 1
doUpdateCtLoop
LDA temp1
AND #%00000001
BNE +oddCol
LDY temp2
LDA (pointer6),y
LSR
LSR
LSR
LSR
JMP +pushToCol
+oddCol
LDY temp2
LDA (pointer6),y
AND #%00001111
+pushToCol:
LDY temp1
STA collisionTable,y
LDA temp1
CLC
ADC #$10
STA temp1
LDA temp2
CLC
ADC #$08
STA temp2
DEX
BNE doUpdateCtLoop
JMP +doneWithCtLoad
+doUpdateOddCt
;; do update odd ct
;; to ct table 2
doUpdateCtLoop2
LDA temp1
AND #%00000001
BNE +oddCol
LDY temp2
LDA (pointer6),y
LSR
LSR
LSR
LSR
JMP +pushToCol
+oddCol
LDY temp2
LDA (pointer6),y
AND #%00001111
+pushToCol:
LDY temp1
STA collisionTable2,y
LDA temp1
CLC
ADC #$10
STA temp1
LDA temp2
CLC
ADC #$08
STA temp2
DEX
BNE doUpdateCtLoop2
JMP +doneWithCtLoad
+doneWithCtLoad
LDA tempC
CMP camX_hi
BEQ +skipUpdatingScreenFlags
;; update screen flags.
LDY #124
LDA (pointer2),y
STA ScreenFlags00
LDY #182
LDA (pointer2),y
STA ScreenFlags01
+skipUpdatingScreenFlags
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ReturnBank
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; here check camX_hi against tempD
;;; if they are the same, that means we haven't changed screen boundaries.
;;; if they have changed, it means we have changed screen boundaries, so
;;; we should load in the new screenFlags data.
skipScrollUpdate
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Make sure we always update camScreen.
LDA camY_hi
ASL
ASL
ASL
ASL
CLC
ADC camX_hi
STA camScreen
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
JSR checkForMonsterCameraClipping
skipAllScrollSeamUpdate:
RTS
getCamSeam:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Since we use camScreen in this subroutine, we'll have to make sure it's properly updated
;;; before get our column and screen.
LDA camY_hi
ASL
ASL
ASL
ASL
CLC
ADC camX_hi
STA camScreen
LDA scrollByte
AND #%01000000
BNE +getRightScrollUpdate
;; get left scroll update
LDA camX
AND #%11110000
sec
sbc #$70
STA scrollUpdateColumn
LDA camScreen
sbc #$00
STA scrollUpdateScreen
RTS
+getRightScrollUpdate
LDA camX
AND #%11110000
CLC
ADC #$80
STA scrollUpdateColumn
LDA camScreen
ADC #$01
STA scrollUpdateScreen
RTS
checkForMonsterCameraClipping:
;;; use temp16 to check for cam clips
LDA camX_hi
AND #%00001111
BNE notOnZeroScreen
LDA #$00
STA temp16
LDA camX_hi
STA temp16+1
JMP +gotIt
notOnZeroScreen:
LDA camX
SEC
SBC #$80
STA temp16 ;; low left cam clip
LDA camX_hi
;AND #%00001111
SBC #$00
AND #%00001111
STA temp16+1 ;; high left cam clip
+gotIt:
LDA camX
CLC
ADC #$80
STA pointer ;; low right cam clip
LDA camX_hi
ADC #$01
AND #%00001111
STA pointer+1 ;; high right cam clip
LDX #$00
skipCheckingThisObject_forEraseColumnLoop
cpx player1_object
BEQ doEraseNonPlayerObjectsInThisColumnLoop
LDA Object_status,x
AND #%10000000
BEQ doEraseNonPlayerObjectsInThisColumnLoop
;; check this monster's position
LDA Object_x_hi,x
STA temp
LDA Object_screen,x
AND #%00001111
STA temp1
Compare16 temp16+1, temp16, temp1, temp
+
DestroyObject
JMP doEraseNonPlayerObjectsInThisColumnLoop
++
skipCheckingThisObject_forEraseColumnLoop2:
Compare16 pointer+1, pointer, temp1, temp
+
JMP doEraseNonPlayerObjectsInThisColumnLoop
++
DestroyObject
doEraseNonPlayerObjectsInThisColumnLoop
INX
CPX #TOTAL_MAX_OBJECTS
BNE skipCheckingThisObject_forEraseColumnLoop
RTS
checkSeamForMonsterPosition:
;; y is loaded before subroutine.
LDA (pointer6),y
STA temp
ASL
ASL
ASL
ASL
STA temp2
LDA scrollUpdateColumn
AND #%11110000
CMP temp2
BNE +noMonsterToLoadInThisColumn
LDA temp
AND #%11110000
STA temp1
CMP #%11110000
BEQ +noMonsterToLoadInThisColumn
CreateObjectOnScreen temp2, temp1, tempD, #$00, scrollUpdateScreen
+noMonsterToLoadInThisColumn:
RTS
You'll also want to backup and replace the normal do_simpleScrollRight.asm input script with this one:
Code:
RIGHT_SCROLL_PAD = #$30 ;; screenspace x position scroll starts and recenters too
LDX player1_object
LDA Object_x_hi,x
SEC
SBC camX
STA temp
CMP #RIGHT_SCROLL_PAD
BCC +noScroll
SBC #$08 ;;if player is within this ammount of pixels of scroll pad, normal scroll speed
CMP #RIGHT_SCROLL_PAD
BCS +speedUpScroll
JMP +normalScroll
+noScroll
LDA #$00
STA camSpeedup
LDA scrollByte
AND #%00111111
STA scrollByte
RTS
+speedUpScroll
;PlaySound #sfx_whip ;;I like to use sounds as code simple debugs for code execution
LDA #$FF ;;add to camera speed
STA camSpeedup
LDA scrollByte
AND #%01000000
BNE +
LDA scrollByte
ORA #%00000010
+
AND #%00111111
ORA #%11000000 ;; bit one forces an update.
STA scrollByte
RTS
+normalScroll
;PlaySound #sfx_cursor ;;I like to use sounds as code simple debugs for code execution
LDA #$00 ;;reset camera speed
STA camSpeedup
LDA scrollByte
AND #%01000000
BNE +
LDA scrollByte
ORA #%00000010
+
AND #%00111111
ORA #%11000000 ;; bit one forces an update.
STA scrollByte
RTS
And backup and replace your do_simpleScrollLeft.asm with this version:
Code:
LEFT_SCROLL_PAD = #$B0 ;; screenspace x position scroll starts and recenters too
LDX player1_object
LDA Object_x_hi,x
SEC
SBC camX
STA temp
CMP #LEFT_SCROLL_PAD
BCS +noScroll
ADC #$08 ;;if player is within this ammount of pixels of scroll pad, normal scroll speed
CMP #LEFT_SCROLL_PAD
BCC +speedUpScroll
JMP +normalScroll
+noScroll
LDA #$00
STA camSpeedup
LDA scrollByte
AND #%00111111
STA scrollByte
RTS
+speedUpScroll
;PlaySound #sfx_whip ;;I like to use sounds as code simple debugs for code execution
LDA #$FF ;;add to camera speed
STA camSpeedup
LDA scrollByte
AND #%01000000
BEQ +
LDA scrollByte
ORA #%00000010
+
AND #%00111111
ORA #%10000000 ;; bit one forces an update.
STA scrollByte
RTS
+normalScroll
;PlaySound #sfx_cursor ;;I like to use sounds as code simple debugs for code execution
LDA #$00 ;;reset camera speed
STA camSpeedup
LDA scrollByte
AND #%01000000
BEQ +
LDA scrollByte
ORA #%00000010
+
AND #%00111111
ORA #%10000000 ;; bit one forces an update.
STA scrollByte
RTS
TWEAKABLES:
#LEFT_SCROLL_PAD
#RIGHT_SCROLL_PAD
In the scroll input scripts, these set what screen relative x collumn to start scrolling and center the player on. Adjust these for how much space in front of the player you want when scrolling either direction. I gave mine plenty of room for incoming reaction time, but you may want it set differently.
LDA #$FF ;;add to camera speed
This line in each scroll input script sets how fast the camera recenters, mine's is set to max speed, adjust this to your liking.
SBC #$08 ;;if player is within this ammount of pixels of scroll pad, normal scroll speed
This ine in each scroll input (it's ADC #$08 in scroll left and SBC #$08 in scroll right) gives the exact scroll pad a buffer. I was getting slightly jerky scrolling once the camera had recentered at first because it couldn't hit a single pixel target, it would overshoot, stop scrolling for a single frame, start scrolling fast again and overshoot again, over and over. This adds an 8 pixel buffer to the scroll pad target to avoid overshooting it and causing jerky scrolling. Depending on the speed of your character you may need to increase this buffer ammount if your scrolling isn't smooth after the camera recenters on the scroll pad.
POSSIBLE FUTURE IMPROVEMENTS:
There's probably some optimizations that could be made. For instance, because we only need a scroll fast or scroll normal bool check, we could skip needing a new Zero Page RAM byte if we could find an unsed bit on an existing byte to set as our new "scroll fast bit". A single bit check on the "scrollByte" seems like an obvious candidate (if it's not using every bit for something already), but so far I have been unable to decipher what every scrollByte bit is for, and if there's an unused bit we could claim.