[Tutorial] How to add Dynamic Camera in Sonic 1

⚠️ WARNING: This guide only works with Extended Camera added in your Sonic 1 disassembly.

In this guide, you will learn how to replace Sonic CD's linear moving camera with the smooth one.

If you already have added extended camera, this tutorial may be simple for you. Go to your _incobj/01 Sonic.asm file and find the Sonic_PanCamera: label. It has a speed limit of how it will extend.

Replace this entire routine of the label with this
Code:
Sonic_PanCamera:
        move.w    (v_camera_pan).w,d1    ; d1 = Current camera pan value
        move.w    #320/2,d3        ; d3 = Default center position (160)

        move.w    obInertia(a0),d0    ; Get Sonic's ground speed
        btst    #1,obStatus(a0)        ; Is Sonic in the air?
        beq.s    .IsGrounded        ; If not, branch
        move.w    obVelX(a0),d0        ; If in the air, use X velocity

.IsGrounded:
        ; d0 stores the speed (positive/right or negative/left)
        ; Maps the speed to the pan offset
        ext.l    d0            ; Sign-extend d0 to 32-bit for multiplication
        asl.l    #6,d0            ; Multiply speed by 64 (6-bit Logical Shift Left)
        divs.w    #$0600,d0        ; Divide by $0600 (Normal maximum speed)
 
        ; Subtract from center to invert logic (look ahead of Sonic)
        sub.w    d0,d3            ; d3 = Center - Offset

        ; Ensure the pan target does not exceed boundaries (+-64 pixels)
        cmpi.w    #(320/2)-64,d3        ; Left boundary limit
        bge.s    .CheckMaxCap
        move.w    #(320/2)-64,d3

.CheckMaxCap:
        cmpi.w    #(320/2)+64,d3        ; Right boundary limit
        ble.s    .ApplyTweening
        move.w    #(320/2)+64,d3

.ApplyTweening:
        ; d3 = Perfect target position
        ; d1 = Current camera position
        cmp.w    d3,d1            ; Is the camera already at the target position?
        beq.s    .SetPanVal        ; If yes, terminate
        blo.s    .TweenRight        ; If current position < target, move right

.TweenLeft:
        ; Camera needs to move left (decrease d1 towards d3)
        move.w    d1,d2            ; d2 = Current position
        sub.w    d3,d2            ; d2 = Calculate distance to target (current - target)
        lsr.w    #3,d2            ; Divide distance by 8 (adjust smoothness here)
        addq.w    #1,d2            ; Guarantee minimum speed of 1px/frame
 
        sub.w    d2,d1            ; Apply movement to camera (move left)
        cmp.w    d3,d1            ; Did it overshoot the target?
        bge.s    .SetPanVal        ; If not, continue
        move.w    d3,d1            ; If it overshot, snap to exact target
        bra.s    .SetPanVal

.TweenRight:
        ; Camera needs to move right (increase d1 towards d3)
        move.w    d3,d2            ; d2 = Target
        sub.w    d1,d2            ; d2 = Calculate distance to target (target - current)
        lsr.w    #3,d2            ; Divide distance by 8 (adjust smoothness here)
        addq.w    #1,d2            ; Guarantee minimum speed of 1px/frame
 
        add.w    d2,d1            ; Apply movement to camera (move right)
        cmp.w    d3,d1            ; Did it overshoot the target?
        ble.s    .SetPanVal        ; If not, continue
        move.w    d3,d1            ; If it overshot, snap to exact target

.SetPanVal:
        move.w    d1,(v_camera_pan).w    ; Save the new smoothed pan value into RAM
        rts
Looks neat! Also, there's a known issue: it looks off for Sonic if you want a fixed camera (only for Sonic when he goes into a Spindash or Peel-out).

Please replace your previous version of the 'Pan' Camera code with this newer, fresher one.
Code:
Sonic_PanCamera:
        move.w    (v_camera_pan).w,d1    ; d1 = Current camera pan value (Actual position)
        move.w    #320/2,d3        ; d3 = Default target: Screen center (160)
        move.w    #2,d4            ; d4 = DEFAULT SPEED (lsr.w #2 = Divisor 4 for normal running)

        ; --- ACTIVE STATE CHECK (SPINDASH / PEEL-OUT) ---
        tst.b    spindash_flag(a0)    ; Is Sonic charging Spindash or Peel-out?
        bne.s    .ForcedPanDirection    ; If yes, force full pan to the current direction

        ; --- MOVEMENT CHECK ---
        move.w    obInertia(a0),d0    ; Get Sonic's ground speed
        btst    #1,obStatus(a0)        ; Is Sonic in the air?
        beq.s    .CheckGroundVel        ; If not, check ground speed
        move.w    obVelX(a0),d0        ; If in the air, use X velocity

.CheckGroundVel:
        and.w    d0,d0            ; Is Sonic completely stopped?
        beq.s    .ApplyTweening        ; IF STOPPED: Do not modify d3 (Keep default center = 160)

        ; --- DYNAMIC CALCULATION BY SPEED (Moving) ---
        ext.l    d0            ; Sign-extend to 32-bit
        asl.l    #6,d0            ; Multiply speed by 64
        divs.w    #$0600,d0        ; Divide by the game's maximum speed
        sub.w    d0,d3            ; d3 = Center (160) - Dynamic offset
        bra.s    .ClampCameraLimits

.ForcedPanDirection:
        move.w    #3,d4            ; OMIT ADDITIONAL CHANGES: PEEL-OUT SPEED CHANGE HERE
                        ; (lsr.w #3 = Divisor 8. Makes Peel-Out panning slower and smoother)

        ; If charging Spindash/Peel-out while stationary, shift the target completely
        btst    #0,obStatus(a0)        ; Check Sonic's direction (Bit 0: 0=Right, 1=Left)
        beq.s    .ForcedRight

        ; Looking left: push camera ahead of Sonic (Add to target d3)
        add.w    #64,d3            ; d3 = 160 + 64 = 224
        bra.s    .ApplyTweening

.ForcedRight:
        ; Looking right: push camera ahead of Sonic (Subtract from target d3)
        sub.w    #64,d3            ; d3 = 160 - 64 = 96
        bra.s    .ApplyTweening

.ClampCameraLimits:
        ; Ensures calculation by speed does not break safe boundaries of +-64 pixels
        cmpi.w    #(320/2)-64,d3        ; Left limit (96)
        bge.s    .CheckMaxCap
        move.w    #(320/2)-64,d3

.CheckMaxCap:
        cmpi.w    #(320/2)+64,d3        ; Right limit (224)
        ble.s    .ApplyTweening
        move.w    #(320/2)+64,d3

.ApplyTweening:
        ; d3 = Final validated target
        ; d1 = Current camera position
        cmp.w    d3,d1            ; Is the camera already at the target position?
        beq.s    .SaveAndExit        ; If yes, exit without wasting processing time
        bgt.s    .TweenLeft        ; If current (d1) > target (d3), move left

.TweenRight:
        ; --- IMPROVED TWEENING TO THE RIGHT ---
        move.w    d3,d2            ; d2 = Target
        sub.w    d1,d2            ; d2 = Remaining distance (Target - Current)
        lsr.w    d4,d2            ; Apply the speed stored in d4
        addq.w    #1,d2            ; Adjusted minimum speed to 1px for maximum smoothness
 
        add.w    d2,d1            ; Apply movement to the right
        cmp.w    d3,d1            ; Did it overshoot the target?
        blt.s    .SaveAndExit        ; If not, save
        move.w    d3,d1            ; If it overshot, snap to exact target
        bra.s    .SaveAndExit

.TweenLeft:
        ; --- IMPROVED TWEENING TO THE LEFT ---
        move.w    d1,d2            ; d2 = Current
        sub.w    d3,d2            ; d2 = Remaining distance (Current - Target)
        lsr.w    d4,d2            ; Apply the speed stored in d4
        addq.w    #1,d2            ; Adjusted minimum speed to 1px for maximum smoothness
 
        sub.w    d2,d1            ; Apply movement to the left
        cmp.w    d3,d1            ; Did it overshoot the target?
        bgt.s    .SaveAndExit        ; If not, save
        move.w    d3,d1            ; If it overshot, snap to exact target

.SaveAndExit:
        move.w    d1,(v_camera_pan).w    ; Save the new corrected pan value into RAM
        rts
And... That's it! I hope you enjoyed this tutorial, enjoy your vastly dynamic camera which is better for player's vision and no comparison to the original game.

Happy hacking! 😃
 
Last edited:
Back
Top