Fire Button Shoots
Read the fire button and launch a bullet from the ship — then make the shot spawn correctly wherever the ship is, using the ninth X bit from the last unit.
The ship flies anywhere on screen now and stops cleanly at every edge — but it's unarmed. A shooter needs a shot. This unit reads the fire button and launches a bullet that streaks up the screen, then fixes the one thing that breaks the moment you fire from the right: the ninth X bit you added last unit.
Where we start
We have last unit's ship: full-width movement, clamped to the screen, sprite 0 in white. Nothing happens when you press fire — the button isn't read yet, and there's no second sprite to launch. We'll give the ship a bullet on sprite 1.
Milestone 1 — fire a bullet
The fire button is one more bit on the same joystick byte you read for movement. Port 2 sits at $dc00; bits 0–3 are the four directions, and bit 4 is fire — active-low like the rest, so a pressed button reads as a 0.
The bullet rides on sprite 1. We point it at a tall, thin shape, colour it yellow, and leave it disabled until the moment of firing. A single zero-page flag, bullet_active, tracks whether a shot is in flight — and that flag is also the guard: we only spawn a new bullet when no bullet is already flying, so holding fire down gives one shot at a time, not a solid beam.
Here's the whole step — reading fire, the spawn guard, and moving the bullet up the screen each frame:
| 1 | 1 | ; Starfield - Unit 4: Fire Button Shoots | |
| 2 | 2 | ; Cumulative steps: step-00 (ship + clamping) -> step-01 (+ a bullet) -> step-02 (+ 9th-bit-aware bullet) | |
| 3 | 3 | ; Assemble: acme -f cbm -o <step>.prg <step>.asm | |
| 4 | + | | |
| 5 | + | ; ------------------------------------------------ | |
| 6 | + | ; Zero-page variables | |
| 7 | + | ; ------------------------------------------------ | |
| 8 | + | bullet_active = $02 ; 0 = no bullet, 1 = active | |
| 9 | + | bullet_y = $03 ; Bullet Y position | |
| 4 | 10 | | |
| 5 | 11 | ; ------------------------------------------------ | |
| 6 | 12 | ; BASIC stub | |
| ... | |||
| 40 | 46 | sta $d015 ; Enable sprite 0 | |
| 41 | 47 | lda #$00 | |
| 42 | 48 | sta $d010 ; sprite high-X bits clear (ship starts under X=256) | |
| 49 | + | | |
| 50 | + | ; Sprite 1 setup (bullet) | |
| 51 | + | lda #129 | |
| 52 | + | sta $07f9 ; Data pointer (block 129 = $2040) | |
| 53 | + | lda #$07 | |
| 54 | + | sta $d028 ; Colour (yellow) | |
| 55 | + | | |
| 56 | + | ; Bullet starts inactive | |
| 57 | + | lda #$00 | |
| 58 | + | sta bullet_active | |
| 43 | 59 | | |
| 44 | 60 | ; ------------------------------------------------ | |
| 45 | 61 | ; Game loop — runs once per frame | |
| ... | |||
| 125 | 141 | sta $d010 | |
| 126 | 142 | + | |
| 127 | 143 | not_right: | |
| 144 | + | | |
| 145 | + | ; --- Fire button (bit 4) --- | |
| 146 | + | lda $dc00 | |
| 147 | + | and #%00010000 | |
| 148 | + | bne no_fire ; Bit is 1 = NOT pressed | |
| 149 | + | | |
| 150 | + | ; Only spawn if no bullet is already flying | |
| 151 | + | lda bullet_active | |
| 152 | + | bne no_fire | |
| 153 | + | | |
| 154 | + | ; Spawn the bullet at the ship's position | |
| 155 | + | lda $d000 ; Ship X (low byte) -> bullet X | |
| 156 | + | sta $d002 | |
| 157 | + | lda $d001 ; Ship Y -> bullet Y | |
| 158 | + | sta bullet_y | |
| 159 | + | | |
| 160 | + | ; Enable sprite 1 (keep sprite 0 enabled) | |
| 161 | + | lda $d015 | |
| 162 | + | ora #%00000010 | |
| 163 | + | sta $d015 | |
| 164 | + | | |
| 165 | + | lda #$01 | |
| 166 | + | sta bullet_active | |
| 167 | + | | |
| 168 | + | no_fire: | |
| 169 | + | | |
| 170 | + | ; --- Update the bullet --- | |
| 171 | + | lda bullet_active | |
| 172 | + | beq no_bullet | |
| 173 | + | | |
| 174 | + | ; Move it up 4 pixels a frame | |
| 175 | + | lda bullet_y | |
| 176 | + | sec | |
| 177 | + | sbc #$04 | |
| 178 | + | sta bullet_y | |
| 179 | + | sta $d003 ; sprite 1 Y | |
| 180 | + | | |
| 181 | + | ; Gone off the top? (Y < 30) -> remove it | |
| 182 | + | cmp #$1e | |
| 183 | + | bcs no_bullet | |
| 184 | + | | |
| 185 | + | lda #$00 | |
| 186 | + | sta bullet_active | |
| 187 | + | lda $d015 | |
| 188 | + | and #%11111101 ; disable sprite 1, keep sprite 0 | |
| 189 | + | sta $d015 | |
| 190 | + | | |
| 191 | + | no_bullet: | |
| 128 | 192 | | |
| 129 | 193 | jmp game_loop | |
| 130 | 194 | | |
| ... | |||
| 153 | 217 | !byte $00,$66,$00 ; ##..## | |
| 154 | 218 | !byte $00,$24,$00 ; #..# | |
| 155 | 219 | !byte $00,$00,$00 ; | |
| 220 | + | | |
| 221 | + | ; ------------------------------------------------ | |
| 222 | + | ; Sprite data at $2040 (block 129) — bullet | |
| 223 | + | ; ------------------------------------------------ | |
| 224 | + | *= $2040 | |
| 225 | + | !byte $00,$00,$00 | |
| 226 | + | !byte $00,$00,$00 | |
| 227 | + | !byte $00,$00,$00 | |
| 228 | + | !byte $00,$00,$00 | |
| 229 | + | !byte $00,$00,$00 | |
| 230 | + | !byte $00,$00,$00 | |
| 231 | + | !byte $00,$00,$00 | |
| 232 | + | !byte $00,$18,$00 ; ## | |
| 233 | + | !byte $00,$18,$00 ; ## | |
| 234 | + | !byte $00,$18,$00 ; ## | |
| 235 | + | !byte $00,$18,$00 ; ## | |
| 236 | + | !byte $00,$18,$00 ; ## | |
| 237 | + | !byte $00,$18,$00 ; ## | |
| 238 | + | !byte $00,$00,$00 | |
| 239 | + | !byte $00,$00,$00 | |
| 240 | + | !byte $00,$00,$00 | |
| 241 | + | !byte $00,$00,$00 | |
| 242 | + | !byte $00,$00,$00 | |
| 243 | + | !byte $00,$00,$00 | |
| 244 | + | !byte $00,$00,$00 | |
| 245 | + | !byte $00,$00,$00 | |
| 156 | 246 | |
The complete step 1 program
; Starfield - Unit 4: Fire Button Shoots
; Cumulative steps: step-00 (ship + clamping) -> step-01 (+ a bullet) -> step-02 (+ 9th-bit-aware bullet)
; Assemble: acme -f cbm -o <step>.prg <step>.asm
; ------------------------------------------------
; Zero-page variables
; ------------------------------------------------
bullet_active = $02 ; 0 = no bullet, 1 = active
bullet_y = $03 ; Bullet Y position
; ------------------------------------------------
; BASIC stub
; ------------------------------------------------
*= $0801
!byte $0c,$08,$0a,$00,$9e,$32,$30,$36,$31,$00,$00,$00
; ------------------------------------------------
; Initialisation
; ------------------------------------------------
*= $080d
; Black screen
lda #$00
sta $d020 ; Border colour
sta $d021 ; Background colour
; Clear the screen
ldx #$00
- lda #$20
sta $0400,x
sta $0500,x
sta $0600,x
sta $0700,x
inx
bne -
; Sprite 0 setup (ship)
lda #128
sta $07f8 ; Data pointer (block 128 = $2000)
lda #172
sta $d000 ; X position
lda #220
sta $d001 ; Y position
lda #$01
sta $d027 ; Colour (white)
lda #%00000001
sta $d015 ; Enable sprite 0
lda #$00
sta $d010 ; sprite high-X bits clear (ship starts under X=256)
; Sprite 1 setup (bullet)
lda #129
sta $07f9 ; Data pointer (block 129 = $2040)
lda #$07
sta $d028 ; Colour (yellow)
; Bullet starts inactive
lda #$00
sta bullet_active
; ------------------------------------------------
; Game loop — runs once per frame
; ------------------------------------------------
game_loop:
; Wait for the raster beam to reach line 255
; This syncs our code to the display (~50Hz PAL)
- lda $d012
cmp #$ff
bne -
; --- Read joystick and move ship ---
; UP (bit 0) — clamp to Y >= 50
lda $dc00 ; Read joystick port 2
and #%00000001 ; Isolate bit 0
bne not_up ; Bit is 1 = NOT pressed (active low)
lda $d001
cmp #52 ; 50 + room for a 2-pixel move
bcc not_up ; already at the top — don't move
dec $d001 ; Move ship up (decrease Y)
dec $d001 ; 2 pixels per frame
not_up:
; DOWN (bit 1) — clamp to Y <= 234
lda $dc00
and #%00000010
bne not_down
lda $d001
cmp #233 ; 234 - room for a 2-pixel move
bcs not_down ; already at the bottom — don't move
inc $d001 ; Move ship down (increase Y)
inc $d001
not_down:
; LEFT (bit 2) — 9-bit X, clamp to X >= 24
lda $dc00
and #%00000100
bne not_left
lda $d010
and #$01
bne left_ok ; high bit set: X >= 256, always safe to go left
lda $d000
cmp #26 ; 24 + room for a 2-pixel move
bcc not_left ; already at the left edge — don't move
left_ok:
; before each step, flip the 9th bit when X is about to wrap $00 -> $ff
lda $d000
bne +
lda $d010
eor #$01 ; the eor bit-flip from the Primer, on sprite 0's high X bit
sta $d010
+ dec $d000
lda $d000
bne +
lda $d010
eor #$01
sta $d010
+ dec $d000
not_left:
; RIGHT (bit 3) — 9-bit X, clamp to X <= 320
lda $dc00
and #%00001000
bne not_right
lda $d010
and #$01
beq right_ok ; high bit clear: X < 256, always safe to go right
lda $d000
cmp #63 ; (320 - 256) - room for a 2-pixel move
bcs not_right ; already at the right edge — don't move
right_ok:
; after each step, flip the 9th bit when X wraps $ff -> $00
inc $d000
bne +
lda $d010
eor #$01
sta $d010
+ inc $d000
bne +
lda $d010
eor #$01
sta $d010
+
not_right:
; --- Fire button (bit 4) ---
lda $dc00
and #%00010000
bne no_fire ; Bit is 1 = NOT pressed
; Only spawn if no bullet is already flying
lda bullet_active
bne no_fire
; Spawn the bullet at the ship's position
lda $d000 ; Ship X (low byte) -> bullet X
sta $d002
lda $d001 ; Ship Y -> bullet Y
sta bullet_y
; Enable sprite 1 (keep sprite 0 enabled)
lda $d015
ora #%00000010
sta $d015
lda #$01
sta bullet_active
no_fire:
; --- Update the bullet ---
lda bullet_active
beq no_bullet
; Move it up 4 pixels a frame
lda bullet_y
sec
sbc #$04
sta bullet_y
sta $d003 ; sprite 1 Y
; Gone off the top? (Y < 30) -> remove it
cmp #$1e
bcs no_bullet
lda #$00
sta bullet_active
lda $d015
and #%11111101 ; disable sprite 1, keep sprite 0
sta $d015
no_bullet:
jmp game_loop
; ------------------------------------------------
; Sprite data at $2000 (block 128)
; ------------------------------------------------
*= $2000
!byte $00,$18,$00 ; ##
!byte $00,$3c,$00 ; ####
!byte $00,$3c,$00 ; ####
!byte $00,$7e,$00 ; ######
!byte $00,$7e,$00 ; ######
!byte $00,$ff,$00 ; ########
!byte $00,$ff,$00 ; ########
!byte $01,$ff,$80 ; ##########
!byte $03,$ff,$c0 ; ############
!byte $07,$ff,$e0 ; ##############
!byte $07,$ff,$e0 ; ##############
!byte $07,$e7,$e0 ; ###..####..###
!byte $03,$c3,$c0 ; ##....##....##
!byte $01,$ff,$80 ; ##########
!byte $00,$ff,$00 ; ########
!byte $00,$ff,$00 ; ########
!byte $00,$db,$00 ; ##.##.##
!byte $00,$db,$00 ; ##.##.##
!byte $00,$66,$00 ; ##..##
!byte $00,$24,$00 ; #..#
!byte $00,$00,$00 ;
; ------------------------------------------------
; Sprite data at $2040 (block 129) — bullet
; ------------------------------------------------
*= $2040
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$18,$00 ; ##
!byte $00,$18,$00 ; ##
!byte $00,$18,$00 ; ##
!byte $00,$18,$00 ; ##
!byte $00,$18,$00 ; ##
!byte $00,$18,$00 ; ##
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
Three pieces fit together here:
- Read fire. Load
$dc00, mask bit 4 withand #%00010000, andbnepast the spawn when it's set (not pressed) — the same isolate-and-branch you used for each direction in Unit 2. - Spawn, once. If
bullet_activeis already non-zero a bullet is flying, so we skip. Otherwise we copy the ship's X and Y to the bullet, enable sprite 1 alongside sprite 0 (ora #%00000010into$d015), and set the flag. - Fly it up. Each frame, while the flag is set, we pull the bullet's Y down by four with
sec/sbc #$04— the subtraction from the Primer — and write it to sprite 1's Y register. When it passes the top of the screen we clear the flag and switch sprite 1 off again.
Press fire and the shot launches:
Milestone 2 — fire from anywhere
There's a bug hiding in that spawn, and last unit is exactly what exposes it. Fly the ship over to the right half of the screen — past X=255, where the ninth bit is set — and fire. The bullet appears way over on the left:
We copied $d000, the ship's low X byte, into the bullet's — but not the ninth bit. When the ship is past 255 its real position is "low byte + 256", and dropping the +256 lands the bullet a screen-width to the left. Sprite 1's ninth bit lives in the same $d010 register as the ship's, just one bit over: bit 0 is sprite 0 (the ship), bit 1 is sprite 1 (the bullet). So at spawn we copy the ship's bit across to the bullet's:
| 156 | 156 | sta $d002 | |
| 157 | 157 | lda $d001 ; Ship Y -> bullet Y | |
| 158 | 158 | sta bullet_y | |
| 159 | + | | |
| 160 | + | ; Copy the ship's 9th X bit (bit 0) to the bullet's (bit 1), | |
| 161 | + | ; so a shot fired from the right half spawns under the ship | |
| 162 | + | lda $d010 | |
| 163 | + | and #%11111101 ; clear the bullet's 9th bit first | |
| 164 | + | sta $d010 | |
| 165 | + | lda $d010 | |
| 166 | + | and #$01 ; the ship's 9th bit | |
| 167 | + | asl ; shift it into the bullet's position (bit 1) | |
| 168 | + | ora $d010 | |
| 169 | + | sta $d010 | |
| 159 | 170 | | |
| 160 | 171 | ; Enable sprite 1 (keep sprite 0 enabled) | |
| 161 | 172 | lda $d015 | |
| ... | |||
| 187 | 198 | lda $d015 | |
| 188 | 199 | and #%11111101 ; disable sprite 1, keep sprite 0 | |
| 189 | 200 | sta $d015 | |
| 201 | + | lda $d010 | |
| 202 | + | and #%11111101 ; clear the bullet's 9th bit | |
| 203 | + | sta $d010 | |
| 190 | 204 | | |
| 191 | 205 | no_bullet: | |
| 192 | 206 | |
The complete program
; Starfield - Unit 4: Fire Button Shoots
; Cumulative steps: step-00 (ship + clamping) -> step-01 (+ a bullet) -> step-02 (+ 9th-bit-aware bullet)
; Assemble: acme -f cbm -o <step>.prg <step>.asm
; ------------------------------------------------
; Zero-page variables
; ------------------------------------------------
bullet_active = $02 ; 0 = no bullet, 1 = active
bullet_y = $03 ; Bullet Y position
; ------------------------------------------------
; BASIC stub
; ------------------------------------------------
*= $0801
!byte $0c,$08,$0a,$00,$9e,$32,$30,$36,$31,$00,$00,$00
; ------------------------------------------------
; Initialisation
; ------------------------------------------------
*= $080d
; Black screen
lda #$00
sta $d020 ; Border colour
sta $d021 ; Background colour
; Clear the screen
ldx #$00
- lda #$20
sta $0400,x
sta $0500,x
sta $0600,x
sta $0700,x
inx
bne -
; Sprite 0 setup (ship)
lda #128
sta $07f8 ; Data pointer (block 128 = $2000)
lda #172
sta $d000 ; X position
lda #220
sta $d001 ; Y position
lda #$01
sta $d027 ; Colour (white)
lda #%00000001
sta $d015 ; Enable sprite 0
lda #$00
sta $d010 ; sprite high-X bits clear (ship starts under X=256)
; Sprite 1 setup (bullet)
lda #129
sta $07f9 ; Data pointer (block 129 = $2040)
lda #$07
sta $d028 ; Colour (yellow)
; Bullet starts inactive
lda #$00
sta bullet_active
; ------------------------------------------------
; Game loop — runs once per frame
; ------------------------------------------------
game_loop:
; Wait for the raster beam to reach line 255
; This syncs our code to the display (~50Hz PAL)
- lda $d012
cmp #$ff
bne -
; --- Read joystick and move ship ---
; UP (bit 0) — clamp to Y >= 50
lda $dc00 ; Read joystick port 2
and #%00000001 ; Isolate bit 0
bne not_up ; Bit is 1 = NOT pressed (active low)
lda $d001
cmp #52 ; 50 + room for a 2-pixel move
bcc not_up ; already at the top — don't move
dec $d001 ; Move ship up (decrease Y)
dec $d001 ; 2 pixels per frame
not_up:
; DOWN (bit 1) — clamp to Y <= 234
lda $dc00
and #%00000010
bne not_down
lda $d001
cmp #233 ; 234 - room for a 2-pixel move
bcs not_down ; already at the bottom — don't move
inc $d001 ; Move ship down (increase Y)
inc $d001
not_down:
; LEFT (bit 2) — 9-bit X, clamp to X >= 24
lda $dc00
and #%00000100
bne not_left
lda $d010
and #$01
bne left_ok ; high bit set: X >= 256, always safe to go left
lda $d000
cmp #26 ; 24 + room for a 2-pixel move
bcc not_left ; already at the left edge — don't move
left_ok:
; before each step, flip the 9th bit when X is about to wrap $00 -> $ff
lda $d000
bne +
lda $d010
eor #$01 ; the eor bit-flip from the Primer, on sprite 0's high X bit
sta $d010
+ dec $d000
lda $d000
bne +
lda $d010
eor #$01
sta $d010
+ dec $d000
not_left:
; RIGHT (bit 3) — 9-bit X, clamp to X <= 320
lda $dc00
and #%00001000
bne not_right
lda $d010
and #$01
beq right_ok ; high bit clear: X < 256, always safe to go right
lda $d000
cmp #63 ; (320 - 256) - room for a 2-pixel move
bcs not_right ; already at the right edge — don't move
right_ok:
; after each step, flip the 9th bit when X wraps $ff -> $00
inc $d000
bne +
lda $d010
eor #$01
sta $d010
+ inc $d000
bne +
lda $d010
eor #$01
sta $d010
+
not_right:
; --- Fire button (bit 4) ---
lda $dc00
and #%00010000
bne no_fire ; Bit is 1 = NOT pressed
; Only spawn if no bullet is already flying
lda bullet_active
bne no_fire
; Spawn the bullet at the ship's position
lda $d000 ; Ship X (low byte) -> bullet X
sta $d002
lda $d001 ; Ship Y -> bullet Y
sta bullet_y
; Copy the ship's 9th X bit (bit 0) to the bullet's (bit 1),
; so a shot fired from the right half spawns under the ship
lda $d010
and #%11111101 ; clear the bullet's 9th bit first
sta $d010
lda $d010
and #$01 ; the ship's 9th bit
asl ; shift it into the bullet's position (bit 1)
ora $d010
sta $d010
; Enable sprite 1 (keep sprite 0 enabled)
lda $d015
ora #%00000010
sta $d015
lda #$01
sta bullet_active
no_fire:
; --- Update the bullet ---
lda bullet_active
beq no_bullet
; Move it up 4 pixels a frame
lda bullet_y
sec
sbc #$04
sta bullet_y
sta $d003 ; sprite 1 Y
; Gone off the top? (Y < 30) -> remove it
cmp #$1e
bcs no_bullet
lda #$00
sta bullet_active
lda $d015
and #%11111101 ; disable sprite 1, keep sprite 0
sta $d015
lda $d010
and #%11111101 ; clear the bullet's 9th bit
sta $d010
no_bullet:
jmp game_loop
; ------------------------------------------------
; Sprite data at $2000 (block 128)
; ------------------------------------------------
*= $2000
!byte $00,$18,$00 ; ##
!byte $00,$3c,$00 ; ####
!byte $00,$3c,$00 ; ####
!byte $00,$7e,$00 ; ######
!byte $00,$7e,$00 ; ######
!byte $00,$ff,$00 ; ########
!byte $00,$ff,$00 ; ########
!byte $01,$ff,$80 ; ##########
!byte $03,$ff,$c0 ; ############
!byte $07,$ff,$e0 ; ##############
!byte $07,$ff,$e0 ; ##############
!byte $07,$e7,$e0 ; ###..####..###
!byte $03,$c3,$c0 ; ##....##....##
!byte $01,$ff,$80 ; ##########
!byte $00,$ff,$00 ; ########
!byte $00,$ff,$00 ; ########
!byte $00,$db,$00 ; ##.##.##
!byte $00,$db,$00 ; ##.##.##
!byte $00,$66,$00 ; ##..##
!byte $00,$24,$00 ; #..#
!byte $00,$00,$00 ;
; ------------------------------------------------
; Sprite data at $2040 (block 129) — bullet
; ------------------------------------------------
*= $2040
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$18,$00 ; ##
!byte $00,$18,$00 ; ##
!byte $00,$18,$00 ; ##
!byte $00,$18,$00 ; ##
!byte $00,$18,$00 ; ##
!byte $00,$18,$00 ; ##
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
!byte $00,$00,$00
We clear the bullet's ninth bit first (and #%11111101), then read the ship's bit (and #$01), shift it one place left with asl so it lines up over the bullet's position, and ora it back in. When the bullet despawns we clear its ninth bit again, so the next shot starts clean. Now the same shot from the right launches from under the ship:
When it's wrong, see why
The bullet's behaviour points straight at which piece is off — check these values:
- Nothing fires. The button read or the guard is the suspect. Watch
bullet_active: if it never reaches 1 when you press fire, the bit-4 test is wrong — an inverted branch, or the wrong mask. If it sticks at 1 forever, the despawn never runs and the guard blocks every later shot, so confirm the bullet's Y is being compared against the top-of-screen limit. - A solid beam instead of single shots. The guard isn't guarding.
bullet_activemust be checked before spawning and only cleared when the bullet leaves — spawn regardless and every frame fire is held launches a fresh bullet. - Bullet spawns in the wrong place from the right. The ninth bit. Read
$d010: bit 0 is the ship, bit 1 is the bullet. After firing from the right half they should match; if bit 1 stays clear while bit 0 is set, the copy isn't happening and the bullet is a screen-width off.
Before and after
We started with an unarmed ship and finished with one that fires a single bullet on demand — and fires it from the right place wherever the ship is sitting, because the shot now carries the same ninth X bit the ship does.
Try this
- A faster shot. The bullet climbs four pixels a frame (
sbc #$04). Make it six or eight and feel how a quicker bullet changes the timing of a shot — how far ahead of a target you have to aim. - Rapid fire. Right now the guard allows one bullet at a time. Leave it as is and tap fire repeatedly; then picture what a second bullet sprite would buy you. (We'll come back to multiple shots later — for now, notice how much the single-shot limit shapes the feel.)
What you've learnt
- Reading the fire button — bit 4 of
$dc00, isolated and branched the same way as the direction bits. - A spawn guard — one zero-page flag that both tracks the bullet and stops a held button firing a continuous stream.
- A second sprite in motion — enabling sprite 1, moving it each frame with
sbc, and switching it off when it leaves the screen. - Carrying the ninth bit between sprites — copying sprite 0's
$d010bit to sprite 1's so a shot fired past X=255 spawns in the right place.
What's next
The shot works, but it's silent. Next you'll give it a voice — program the SID chip to fire a laser sound the moment a bullet launches.