Skip to content
Game 1 Unit 8 of 64 1 hr learning time

Collision Detection

Hit a car, die. Flash screen. Respawn. Consequences.

13% of Signal

What You’re Building

Consequences.

Traffic that you can walk through isn’t traffic—it’s decoration. This unit adds collision detection: touch a car and the screen flashes red, then you respawn at the start. Now the game has stakes.

By the end of this unit:

  • Frog-car collisions detected
  • Screen flashes red on death
  • Brief pause (0.6 seconds)
  • Frog respawns at starting position

Unit 8 Screenshot

Bounding Box Collision

The simplest collision detection: treat both objects as rectangles and check for overlap.

Two rectangles overlap if ALL of these are true:

  • Frog’s right edge > Car’s left edge
  • Car’s right edge > Frog’s left edge
  • Frog’s bottom edge > Car’s top edge
  • Car’s bottom edge > Frog’s top edge

Since our frog and cars are on a grid, we can simplify: only check cars in the same row as the frog.

First, the constants that define collision boundaries:

; Collision Detection Constants
; Frog and car dimensions for bounding box checks

; Frog dimensions
FROG_HEIGHT     equ 16
FROG_WIDTH      equ 16

; Car dimensions
CAR_WIDTH_PX    equ 32          ; Pixels (not words!)
CAR_HEIGHT      equ 12

; Death animation
DEATH_FRAMES    equ 30          ; 0.6 seconds at 50fps
FLASH_COLOUR    equ $0f00       ; Red flash

; Road boundaries (grid rows)
ROAD_ROW_FIRST  equ 7
ROAD_ROW_LAST   equ 11

; Frog states
STATE_IDLE      equ 0
STATE_HOPPING   equ 1
STATE_DYING     equ 2           ; New state for death animation

The Collision Test

The full collision check filters by row first, then tests X overlap using AABB (Axis-Aligned Bounding Box) logic:

; Bounding Box Collision Test
; AABB overlap: two boxes collide if all four edges overlap

check_collision:
            ; Only check if frog is on a road row
            move.w  frog_grid_y,d0
            cmp.w   #ROAD_ROW_FIRST,d0
            blt     .no_collision
            cmp.w   #ROAD_ROW_LAST,d0
            bgt     .no_collision

            ; Frog is on road - check against all cars
            lea     car_data,a2
            moveq   #NUM_CARS-1,d7

.loop:
            ; Check if car is in same row
            move.w  2(a2),d1            ; Car row
            cmp.w   d0,d1
            bne.s   .next_car

            ; Same row - check X overlap using AABB test
            move.w  frog_pixel_x,d2     ; Frog X
            move.w  (a2),d3             ; Car X

            ; Test 1: frog_right > car_left
            move.w  d2,d4
            add.w   #FROG_WIDTH,d4      ; d4 = frog right edge
            cmp.w   d3,d4
            ble.s   .next_car           ; Frog right <= car left: no collision

            ; Test 2: car_right > frog_left
            move.w  d3,d4
            add.w   #CAR_WIDTH_PX,d4    ; d4 = car right edge
            cmp.w   d2,d4
            ble.s   .next_car           ; Car right <= frog left: no collision

            ; COLLISION! Both tests passed
            bsr     trigger_death
            bra.s   .no_collision       ; Exit early

.next_car:
            lea     CAR_STRUCT_SIZE(a2),a2
            dbf     d7,.loop

.no_collision:
            rts

The checks use ble (branch if less or equal) rather than blt because we want to skip if edges are exactly touching—that’s not a collision.

The Death State

We add a third state to our frog state machine. When dying, the frog ignores input and counts down a timer while flashing the screen:

; Death State and Flash Effect
; Screen flashes red by modifying Copper colour register

trigger_death:
            move.w  #STATE_DYING,frog_state
            move.w  #DEATH_FRAMES,death_timer
            move.w  #FLASH_COLOUR,flash_colour
            rts

; In update_frog, handle dying state:
.dying:
            ; Count down death timer
            subq.w  #1,death_timer
            bgt.s   .still_dying

            ; Death complete - respawn
            bsr     respawn_frog
            bra     .done

.still_dying:
            ; Flash effect: alternate colour every 4 frames
            move.w  death_timer,d0
            and.w   #4,d0               ; Bit 2 toggles every 4 frames
            beq.s   .flash_off
            move.w  #FLASH_COLOUR,flash_colour
            bra     .done
.flash_off:
            move.w  #COLOUR_BLACK,flash_colour
            bra     .done

; At end of update_frog, copy flash colour to Copper list:
.done:
            move.w  flash_colour,flash_copper
            rts

The flash effect modifies the Copper list’s background colour. We alternate the colour every 4 frames, creating a flashing effect at about 6Hz—noticeable but not seizure-inducing.

Respawn Logic

When death is complete, we reset the frog:

respawn_frog:
    move.w  #START_GRID_X,frog_grid_x
    move.w  #START_GRID_Y,frog_grid_y
    move.w  #STATE_IDLE,frog_state
    clr.w   frog_anim_frame
    clr.w   joy_prev

    ; Clear flash
    move.w  #COLOUR_BLACK,flash_colour

    bsr     grid_to_pixels
    rts

Note that we clear joy_prev—this prevents a held joystick direction from immediately moving the respawned frog.

When to Check Collisions

We only check collisions when the frog is alive:

mainloop:
    bsr     wait_vblank
    bsr     update_frog
    bsr     update_sprite

    bsr     erase_all_cars
    bsr     move_all_cars
    bsr     draw_all_cars

    ; Only check if not already dying
    cmp.w   #STATE_DYING,frog_state
    beq.s   .skip_collision
    bsr     check_collision
.skip_collision:

    bra     mainloop

Checking during death would immediately trigger another death on respawn if a car happened to be at the start position.

Collision Timing

An important subtlety: we check collision after updating cars and after the frog has finished moving. This means:

  1. Frog starts hop
  2. Frog animates through hop (8 frames)
  3. Frog lands on new cell
  4. Collision check happens
  5. If car overlaps, death triggers

The frog can hop “through” a fast car if it passes during the hop animation. This is actually how classic Frogger works—you’re only vulnerable when stationary.

Key Takeaways

  • Bounding box collision checks rectangle overlap
  • Row filtering reduces unnecessary checks
  • Death state prevents input during animation
  • Copper modification creates screen flash effect
  • State cleanup on respawn prevents input glitches
  • Collision timing affects gameplay feel

The Code

;══════════════════════════════════════════════════════════════════════════════
; SIGNAL - A Frogger-style game for the Commodore Amiga
; Unit 8: Collision Detection
;
; Traffic is meaningless if you can walk through it. This unit adds collision
; detection: hit a car, the screen flashes red, and you respawn at the start.
; Now it's actually a game with consequences!
;══════════════════════════════════════════════════════════════════════════════

;══════════════════════════════════════════════════════════════════════════════
; CONSTANTS
;══════════════════════════════════════════════════════════════════════════════

SCREEN_W        equ 40
SCREEN_H        equ 256
PLANE_SIZE      equ SCREEN_W*SCREEN_H

GRID_COLS       equ 20
GRID_ROWS       equ 13
CELL_SIZE       equ 16
GRID_ORIGIN_X   equ 48
GRID_ORIGIN_Y   equ 44
START_GRID_X    equ 9
START_GRID_Y    equ 12

HOP_FRAMES      equ 8
PIXELS_PER_FRAME equ 2
STATE_IDLE      equ 0
STATE_HOPPING   equ 1
STATE_DYING     equ 2           ; NEW: Death state
DIR_UP          equ 0
DIR_DOWN        equ 1
DIR_LEFT        equ 2
DIR_RIGHT       equ 3
FROG_HEIGHT     equ 16
FROG_WIDTH      equ 16

; Death animation
DEATH_FRAMES    equ 30          ; How long death lasts (0.6 seconds at 50fps)
FLASH_COLOUR    equ $0f00       ; Red flash

NUM_CARS        equ 10
CAR_WIDTH       equ 2
CAR_WIDTH_PX    equ 32          ; Pixels
CAR_HEIGHT      equ 12
CAR_STRUCT_SIZE equ 8

; Collision threshold (pixels of overlap required)
COLLISION_THRESHOLD equ 8

; Road rows (grid rows 7-11, which are rows 8-12 in 1-based counting)
ROAD_ROW_FIRST  equ 7
ROAD_ROW_LAST   equ 11

; Zone colours
COLOUR_BLACK    equ $0000
COLOUR_HOME     equ $0282
COLOUR_HOME_LIT equ $03a3
COLOUR_WATER1   equ $0148
COLOUR_WATER2   equ $026a
COLOUR_MEDIAN   equ $0383
COLOUR_ROAD1    equ $0333
COLOUR_ROAD2    equ $0444
COLOUR_START    equ $0262
COLOUR_START_LIT equ $0373
COLOUR_FROG     equ $00f0
COLOUR_EYES     equ $0ff0
COLOUR_OUTLINE  equ $0000
COLOUR_CAR      equ $0f00

;══════════════════════════════════════════════════════════════════════════════
; HARDWARE REGISTERS
;══════════════════════════════════════════════════════════════════════════════

CUSTOM      equ $dff000

DMACONR     equ $002
VPOSR       equ $004
JOY1DAT     equ $00c

BLTCON0     equ $040
BLTCON1     equ $042
BLTAFWM     equ $044
BLTALWM     equ $046
BLTCPTH     equ $048
BLTBPTH     equ $04c
BLTAPTH     equ $050
BLTDPTH     equ $054
BLTSIZE     equ $058
BLTCMOD     equ $060
BLTBMOD     equ $062
BLTAMOD     equ $064
BLTDMOD     equ $066

COP1LC      equ $080
COPJMP1     equ $088
DMACON      equ $096
INTENA      equ $09a
INTREQ      equ $09c
COLOR00     equ $180
SPR0PTH     equ $120
SPR0PTL     equ $122

;══════════════════════════════════════════════════════════════════════════════
; CODE SECTION
;══════════════════════════════════════════════════════════════════════════════

            section code,code_c

start:
            lea     CUSTOM,a5

            move.w  #$7fff,INTENA(a5)
            move.w  #$7fff,INTREQ(a5)
            move.w  #$7fff,DMACON(a5)

            bsr     clear_screen

            ; Set up bitplane pointer
            lea     screen_plane,a0
            move.l  a0,d0
            swap    d0
            lea     bplpth_val,a1
            move.w  d0,(a1)
            swap    d0
            lea     bplptl_val,a1
            move.w  d0,(a1)

            ; Initialise frog
            bsr     respawn_frog

            ; Set sprite pointer
            lea     frog_data,a0
            move.l  a0,d0
            swap    d0
            lea     sprpth_val,a1
            move.w  d0,(a1)
            swap    d0
            lea     sprptl_val,a1
            move.w  d0,(a1)
            bsr     update_sprite

            ; Install copper list
            lea     copperlist,a0
            move.l  a0,COP1LC(a5)
            move.w  d0,COPJMP1(a5)

            ; Enable DMA
            move.w  #$87e0,DMACON(a5)

;══════════════════════════════════════════════════════════════════════════════
; MAIN LOOP
;══════════════════════════════════════════════════════════════════════════════

mainloop:
            bsr     wait_vblank

            ; Update frog (handles all states including death)
            bsr     update_frog
            bsr     update_sprite

            ; Update cars
            bsr     erase_all_cars
            bsr     move_all_cars
            bsr     draw_all_cars

            ; Check collisions (only if frog is alive)
            cmp.w   #STATE_DYING,frog_state
            beq.s   .skip_collision
            bsr     check_collision
.skip_collision:

            bra     mainloop

;══════════════════════════════════════════════════════════════════════════════
; COLLISION DETECTION
;══════════════════════════════════════════════════════════════════════════════

;------------------------------------------------------------------------------
; CHECK_COLLISION - Test frog against all cars
;------------------------------------------------------------------------------
check_collision:
            ; Only check if frog is on a road row
            move.w  frog_grid_y,d0
            cmp.w   #ROAD_ROW_FIRST,d0
            blt     .no_collision
            cmp.w   #ROAD_ROW_LAST,d0
            bgt     .no_collision

            ; Frog is on road - check against all cars in this row
            lea     car_data,a2
            moveq   #NUM_CARS-1,d7

.loop:
            ; Check if car is in same row
            move.w  2(a2),d1            ; Car row
            cmp.w   d0,d1               ; Compare with frog row
            bne.s   .next_car

            ; Same row - check X overlap
            move.w  frog_pixel_x,d2     ; Frog X
            move.w  (a2),d3             ; Car X

            ; Check: frog_x + frog_width > car_x
            move.w  d2,d4
            add.w   #FROG_WIDTH,d4
            cmp.w   d3,d4
            ble.s   .next_car           ; Frog right edge <= car left edge

            ; Check: car_x + car_width > frog_x
            move.w  d3,d4
            add.w   #CAR_WIDTH_PX,d4
            cmp.w   d2,d4
            ble.s   .next_car           ; Car right edge <= frog left edge

            ; COLLISION! Trigger death
            bsr     trigger_death
            bra.s   .no_collision       ; Exit early

.next_car:
            lea     CAR_STRUCT_SIZE(a2),a2
            dbf     d7,.loop

.no_collision:
            rts

;------------------------------------------------------------------------------
; TRIGGER_DEATH - Start death sequence
;------------------------------------------------------------------------------
trigger_death:
            move.w  #STATE_DYING,frog_state
            move.w  #DEATH_FRAMES,death_timer

            ; Flash screen red by changing Copper colour
            move.w  #FLASH_COLOUR,flash_colour
            rts

;------------------------------------------------------------------------------
; RESPAWN_FROG - Reset frog to starting position
;------------------------------------------------------------------------------
respawn_frog:
            move.w  #START_GRID_X,frog_grid_x
            move.w  #START_GRID_Y,frog_grid_y
            move.w  #STATE_IDLE,frog_state
            clr.w   frog_anim_frame
            clr.w   joy_prev

            ; Reset flash colour to black
            move.w  #COLOUR_BLACK,flash_colour

            bsr     grid_to_pixels
            rts

;══════════════════════════════════════════════════════════════════════════════
; CAR MANAGEMENT
;══════════════════════════════════════════════════════════════════════════════

erase_all_cars:
            lea     car_data,a2
            moveq   #NUM_CARS-1,d7

.loop:
            move.w  (a2),d0
            move.w  2(a2),d1
            bsr     calc_screen_addr
            bsr     wait_blit

            move.l  a0,BLTDPTH(a5)
            move.w  #SCREEN_W-CAR_WIDTH*2,BLTDMOD(a5)
            move.w  #$0100,BLTCON0(a5)
            move.w  #0,BLTCON1(a5)
            move.w  #(CAR_HEIGHT<<6)|CAR_WIDTH,BLTSIZE(a5)

            lea     CAR_STRUCT_SIZE(a2),a2
            dbf     d7,.loop
            rts

move_all_cars:
            lea     car_data,a2
            moveq   #NUM_CARS-1,d7

.loop:
            move.w  (a2),d0
            move.w  4(a2),d1
            add.w   d1,d0

            tst.w   d1
            bmi.s   .check_left

            cmp.w   #320,d0
            blt.s   .store
            sub.w   #320+32,d0
            bra.s   .store

.check_left:
            cmp.w   #-32,d0
            bgt.s   .store
            add.w   #320+32,d0

.store:
            move.w  d0,(a2)
            lea     CAR_STRUCT_SIZE(a2),a2
            dbf     d7,.loop
            rts

draw_all_cars:
            lea     car_data,a2
            moveq   #NUM_CARS-1,d7

.loop:
            move.w  (a2),d0
            move.w  2(a2),d1

            cmp.w   #-32,d0
            blt.s   .next
            cmp.w   #320,d0
            bge.s   .next

            bsr     calc_screen_addr
            bsr     wait_blit

            move.l  #car_gfx,BLTAPTH(a5)
            move.l  a0,BLTDPTH(a5)
            move.w  #$ffff,BLTAFWM(a5)
            move.w  #$ffff,BLTALWM(a5)
            move.w  #0,BLTAMOD(a5)
            move.w  #SCREEN_W-CAR_WIDTH*2,BLTDMOD(a5)
            move.w  #$09f0,BLTCON0(a5)
            move.w  #0,BLTCON1(a5)
            move.w  #(CAR_HEIGHT<<6)|CAR_WIDTH,BLTSIZE(a5)

.next:
            lea     CAR_STRUCT_SIZE(a2),a2
            dbf     d7,.loop
            rts

;══════════════════════════════════════════════════════════════════════════════
; FROG ROUTINES
;══════════════════════════════════════════════════════════════════════════════

update_frog:
            move.w  frog_state,d0

            cmp.w   #STATE_DYING,d0
            beq     .dying

            tst.w   d0
            beq     .idle
            bra     .hopping

.dying:
            ; Count down death timer
            subq.w  #1,death_timer
            bgt.s   .still_dying

            ; Death complete - respawn
            bsr     respawn_frog
            bra     .done

.still_dying:
            ; Flash effect: alternate colour
            move.w  death_timer,d0
            and.w   #4,d0               ; Flash every 4 frames
            beq.s   .flash_off
            move.w  #FLASH_COLOUR,flash_colour
            bra     .done
.flash_off:
            move.w  #COLOUR_BLACK,flash_colour
            bra     .done

.idle:
            bsr     read_joystick_edge
            tst.w   d0
            beq     .done

            btst    #8,d0
            beq.s   .not_up
            move.w  frog_grid_y,d1
            tst.w   d1
            beq.s   .not_up
            move.w  #DIR_UP,frog_dir
            bra.s   .start_hop
.not_up:
            btst    #0,d0
            beq.s   .not_down
            move.w  frog_grid_y,d1
            cmp.w   #GRID_ROWS-1,d1
            beq.s   .not_down
            move.w  #DIR_DOWN,frog_dir
            bra.s   .start_hop
.not_down:
            btst    #9,d0
            beq.s   .not_left
            move.w  frog_grid_x,d1
            tst.w   d1
            beq.s   .not_left
            move.w  #DIR_LEFT,frog_dir
            bra.s   .start_hop
.not_left:
            btst    #1,d0
            beq     .done
            move.w  frog_grid_x,d1
            cmp.w   #GRID_COLS-1,d1
            beq     .done
            move.w  #DIR_RIGHT,frog_dir

.start_hop:
            move.w  #STATE_HOPPING,frog_state
            clr.w   frog_anim_frame
            bra.s   .done

.hopping:
            addq.w  #1,frog_anim_frame
            move.w  frog_dir,d0

            cmp.w   #DIR_UP,d0
            bne.s   .hop_not_up
            sub.w   #PIXELS_PER_FRAME,frog_pixel_y
            bra.s   .check_done
.hop_not_up:
            cmp.w   #DIR_DOWN,d0
            bne.s   .hop_not_down
            add.w   #PIXELS_PER_FRAME,frog_pixel_y
            bra.s   .check_done
.hop_not_down:
            cmp.w   #DIR_LEFT,d0
            bne.s   .hop_not_left
            sub.w   #PIXELS_PER_FRAME,frog_pixel_x
            bra.s   .check_done
.hop_not_left:
            add.w   #PIXELS_PER_FRAME,frog_pixel_x

.check_done:
            cmp.w   #HOP_FRAMES,frog_anim_frame
            blt.s   .done

            move.w  frog_dir,d0
            cmp.w   #DIR_UP,d0
            bne.s   .end_not_up
            subq.w  #1,frog_grid_y
            bra.s   .snap
.end_not_up:
            cmp.w   #DIR_DOWN,d0
            bne.s   .end_not_down
            addq.w  #1,frog_grid_y
            bra.s   .snap
.end_not_down:
            cmp.w   #DIR_LEFT,d0
            bne.s   .end_not_left
            subq.w  #1,frog_grid_x
            bra.s   .snap
.end_not_left:
            addq.w  #1,frog_grid_x

.snap:
            bsr     grid_to_pixels
            move.w  #STATE_IDLE,frog_state

.done:
            ; Update flash colour in Copper list
            move.w  flash_colour,flash_copper
            rts

;══════════════════════════════════════════════════════════════════════════════
; UTILITY ROUTINES
;══════════════════════════════════════════════════════════════════════════════

grid_to_pixels:
            move.w  frog_grid_x,d0
            mulu    #CELL_SIZE,d0
            add.w   #GRID_ORIGIN_X,d0
            move.w  d0,frog_pixel_x

            move.w  frog_grid_y,d0
            mulu    #CELL_SIZE,d0
            add.w   #GRID_ORIGIN_Y,d0
            move.w  d0,frog_pixel_y
            rts

read_joystick_edge:
            move.w  JOY1DAT(a5),d0
            move.w  d0,d1
            lsr.w   #1,d1
            eor.w   d1,d0

            move.w  joy_prev,d1
            not.w   d1
            and.w   d0,d1
            move.w  d0,joy_prev

            move.w  d1,d0
            rts

wait_vblank:
            move.l  #$1ff00,d1
.wait:
            move.l  VPOSR(a5),d0
            and.l   d1,d0
            bne.s   .wait
            rts

wait_blit:
            btst    #6,DMACONR(a5)
            bne.s   wait_blit
            rts

clear_screen:
            bsr.s   wait_blit
            move.l  #screen_plane,BLTDPTH(a5)
            move.w  #0,BLTDMOD(a5)
            move.w  #$0100,BLTCON0(a5)
            move.w  #0,BLTCON1(a5)
            move.w  #(SCREEN_H<<6)|SCREEN_W/2,BLTSIZE(a5)
            rts

calc_screen_addr:
            lea     screen_plane,a0
            move.w  d1,d2
            mulu    #CELL_SIZE,d2
            add.w   #GRID_ORIGIN_Y,d2
            mulu    #SCREEN_W,d2
            add.l   d2,a0
            move.w  d0,d2
            lsr.w   #3,d2
            ext.l   d2
            add.l   d2,a0
            rts

update_sprite:
            lea     frog_data,a0
            move.w  frog_pixel_y,d0
            move.w  frog_pixel_x,d1

            move.w  d0,d2
            lsl.w   #8,d2
            lsr.w   #1,d1
            or.b    d1,d2
            move.w  d2,(a0)

            add.w   #FROG_HEIGHT,d0
            lsl.w   #8,d0
            move.w  d0,2(a0)
            rts

;══════════════════════════════════════════════════════════════════════════════
; VARIABLES
;══════════════════════════════════════════════════════════════════════════════

frog_grid_x:    dc.w    9
frog_grid_y:    dc.w    12
frog_pixel_x:   dc.w    0
frog_pixel_y:   dc.w    0
frog_state:     dc.w    0
frog_dir:       dc.w    0
frog_anim_frame: dc.w   0
joy_prev:       dc.w    0

death_timer:    dc.w    0
flash_colour:   dc.w    0

car_data:
            dc.w    0,7,1,0
            dc.w    160,7,1,0
            dc.w    100,8,-2,0
            dc.w    250,8,-2,0
            dc.w    50,9,2,0
            dc.w    200,9,2,0
            dc.w    80,10,-1,0
            dc.w    220,10,-1,0
            dc.w    30,11,3,0
            dc.w    180,11,3,0

;══════════════════════════════════════════════════════════════════════════════
; COPPER LIST
;══════════════════════════════════════════════════════════════════════════════

copperlist:
            dc.w    COLOR00
flash_copper:
            dc.w    COLOUR_BLACK        ; This gets modified for flash effect

            dc.w    $0100,$1200
            dc.w    $0102,$0000
            dc.w    $0104,$0000
            dc.w    $0108,$0000
            dc.w    $010a,$0000

            dc.w    $00e0
bplpth_val: dc.w    $0000
            dc.w    $00e2
bplptl_val: dc.w    $0000

            dc.w    $0180,$0000
            dc.w    $0182,COLOUR_CAR

            dc.w    $01a2,COLOUR_FROG
            dc.w    $01a4,COLOUR_EYES
            dc.w    $01a6,COLOUR_OUTLINE

            dc.w    SPR0PTH
sprpth_val: dc.w    $0000
            dc.w    SPR0PTL
sprptl_val: dc.w    $0000

            dc.w    $2c07,$fffe
            dc.w    COLOR00,COLOUR_HOME
            dc.w    $3407,$fffe
            dc.w    COLOR00,COLOUR_HOME_LIT
            dc.w    $3807,$fffe
            dc.w    COLOR00,COLOUR_HOME

            dc.w    $3c07,$fffe
            dc.w    COLOR00,COLOUR_WATER1
            dc.w    $4c07,$fffe
            dc.w    COLOR00,COLOUR_WATER2
            dc.w    $5c07,$fffe
            dc.w    COLOR00,COLOUR_WATER1
            dc.w    $6c07,$fffe
            dc.w    COLOR00,COLOUR_WATER2
            dc.w    $7c07,$fffe
            dc.w    COLOR00,COLOUR_WATER1

            dc.w    $8c07,$fffe
            dc.w    COLOR00,COLOUR_MEDIAN

            dc.w    $9c07,$fffe
            dc.w    COLOR00,COLOUR_ROAD1
            dc.w    $ac07,$fffe
            dc.w    COLOR00,COLOUR_ROAD2
            dc.w    $bc07,$fffe
            dc.w    COLOR00,COLOUR_ROAD1
            dc.w    $cc07,$fffe
            dc.w    COLOR00,COLOUR_ROAD2
            dc.w    $dc07,$fffe
            dc.w    COLOR00,COLOUR_ROAD1

            dc.w    $ec07,$fffe
            dc.w    COLOR00,COLOUR_START
            dc.w    $f407,$fffe
            dc.w    COLOR00,COLOUR_START_LIT
            dc.w    $f807,$fffe
            dc.w    COLOR00,COLOUR_START

            dc.w    $fc07,$fffe
            dc.w    COLOR00,COLOUR_BLACK

            dc.w    $ffff,$fffe

;══════════════════════════════════════════════════════════════════════════════
; GRAPHICS DATA
;══════════════════════════════════════════════════════════════════════════════

            even
car_gfx:
            dc.w    $0ff0,$0ff0
            dc.w    $3ffc,$3ffc
            dc.w    $7ffe,$7ffe
            dc.w    $ffff,$ffff
            dc.w    $ffff,$ffff
            dc.w    $ffff,$ffff
            dc.w    $ffff,$ffff
            dc.w    $ffff,$ffff
            dc.w    $ffff,$ffff
            dc.w    $7ffe,$7ffe
            dc.w    $3c3c,$3c3c
            dc.w    $0000,$0000

            even
frog_data:
            dc.w    $ec50,$fc00

            dc.w    $0000,$0000
            dc.w    $07e0,$0000
            dc.w    $1ff8,$0420
            dc.w    $3ffc,$0a50
            dc.w    $7ffe,$1248
            dc.w    $7ffe,$1008
            dc.w    $ffff,$2004
            dc.w    $ffff,$0000
            dc.w    $ffff,$0000
            dc.w    $7ffe,$2004
            dc.w    $7ffe,$1008
            dc.w    $3ffc,$0810
            dc.w    $1ff8,$0420
            dc.w    $07e0,$0000
            dc.w    $0000,$0000
            dc.w    $0000,$0000

            dc.w    $0000,$0000

;══════════════════════════════════════════════════════════════════════════════
; SCREEN BUFFER
;══════════════════════════════════════════════════════════════════════════════

            section chipbss,bss_c

            even
screen_plane:
            ds.b    PLANE_SIZE

Build It

vasmm68k_mot -Fhunkexe -kick1hunks -o signal signal.asm

What’s Next

Death happens, but there’s no limit. In Unit 9, we’ll add a lives system—three lives before game over.

What Changed

Unit 7 → Unit 8
+188-103
11 ;══════════════════════════════════════════════════════════════════════════════
22 ; SIGNAL - A Frogger-style game for the Commodore Amiga
3-; Unit 7: Cars on the Road
3+; Unit 8: Collision Detection
44 ;
5-; One car isn't traffic. This unit adds multiple cars across all five road
6-; lanes, each moving at different speeds. Some go left, some go right.
7-; The Blitter draws them all efficiently using a loop.
5+; Traffic is meaningless if you can walk through it. This unit adds collision
6+; detection: hit a car, the screen flashes red, and you respawn at the start.
7+; Now it's actually a game with consequences!
88 ;══════════════════════════════════════════════════════════════════════════════
99
1010 ;══════════════════════════════════════════════════════════════════════════════
1111 ; CONSTANTS
1212 ;══════════════════════════════════════════════════════════════════════════════
1313
14-; Screen setup
1514 SCREEN_W equ 40
1615 SCREEN_H equ 256
1716 PLANE_SIZE equ SCREEN_W*SCREEN_H
1817
19-; Grid constants
2018 GRID_COLS equ 20
2119 GRID_ROWS equ 13
2220 CELL_SIZE equ 16
...
2523 START_GRID_X equ 9
2624 START_GRID_Y equ 12
2725
28-; Frog animation
2926 HOP_FRAMES equ 8
3027 PIXELS_PER_FRAME equ 2
3128 STATE_IDLE equ 0
3229 STATE_HOPPING equ 1
30+STATE_DYING equ 2 ; NEW: Death state
3331 DIR_UP equ 0
3432 DIR_DOWN equ 1
3533 DIR_LEFT equ 2
3634 DIR_RIGHT equ 3
3735 FROG_HEIGHT equ 16
36+FROG_WIDTH equ 16
3837
39-; Car constants
40-NUM_CARS equ 10 ; Total cars
41-CAR_WIDTH equ 2 ; 32 pixels = 2 words
38+; Death animation
39+DEATH_FRAMES equ 30 ; How long death lasts (0.6 seconds at 50fps)
40+FLASH_COLOUR equ $0f00 ; Red flash
41+
42+NUM_CARS equ 10
43+CAR_WIDTH equ 2
44+CAR_WIDTH_PX equ 32 ; Pixels
4245 CAR_HEIGHT equ 12
43-CAR_STRUCT_SIZE equ 8 ; Bytes per car: x(2), row(2), speed(2), pad(2)
46+CAR_STRUCT_SIZE equ 8
47+
48+; Collision threshold (pixels of overlap required)
49+COLLISION_THRESHOLD equ 8
50+
51+; Road rows (grid rows 7-11, which are rows 8-12 in 1-based counting)
52+ROAD_ROW_FIRST equ 7
53+ROAD_ROW_LAST equ 11
4454
4555 ; Zone colours
4656 COLOUR_BLACK equ $0000
...
5363 COLOUR_ROAD2 equ $0444
5464 COLOUR_START equ $0262
5565 COLOUR_START_LIT equ $0373
56-
57-; Sprite and car colours
5866 COLOUR_FROG equ $00f0
5967 COLOUR_EYES equ $0ff0
6068 COLOUR_OUTLINE equ $0000
...
102110 start:
103111 lea CUSTOM,a5
104112
105- ; --- System takeover ---
106113 move.w #$7fff,INTENA(a5)
107114 move.w #$7fff,INTREQ(a5)
108115 move.w #$7fff,DMACON(a5)
109116
110- ; --- Clear screen ---
111117 bsr clear_screen
112118
113- ; --- Set up bitplane pointer ---
119+ ; Set up bitplane pointer
114120 lea screen_plane,a0
115121 move.l a0,d0
116122 swap d0
...
120126 lea bplptl_val,a1
121127 move.w d0,(a1)
122128
123- ; --- Initialise frog ---
124- move.w #START_GRID_X,frog_grid_x
125- move.w #START_GRID_Y,frog_grid_y
126- move.w #STATE_IDLE,frog_state
127- clr.w joy_prev
128- bsr grid_to_pixels
129+ ; Initialise frog
130+ bsr respawn_frog
129131
130- ; --- Set sprite pointer ---
132+ ; Set sprite pointer
131133 lea frog_data,a0
132134 move.l a0,d0
133135 swap d0
...
138140 move.w d0,(a1)
139141 bsr update_sprite
140142
141- ; --- Install copper list ---
143+ ; Install copper list
142144 lea copperlist,a0
143145 move.l a0,COP1LC(a5)
144146 move.w d0,COPJMP1(a5)
145147
146- ; --- Enable DMA ---
148+ ; Enable DMA
147149 move.w #$87e0,DMACON(a5)
148150
149151 ;══════════════════════════════════════════════════════════════════════════════
...
153155 mainloop:
154156 bsr wait_vblank
155157
156- ; --- Update frog ---
158+ ; Update frog (handles all states including death)
157159 bsr update_frog
158160 bsr update_sprite
159161
160- ; --- Update cars ---
162+ ; Update cars
161163 bsr erase_all_cars
162164 bsr move_all_cars
163165 bsr draw_all_cars
166+
167+ ; Check collisions (only if frog is alive)
168+ cmp.w #STATE_DYING,frog_state
169+ beq.s .skip_collision
170+ bsr check_collision
171+.skip_collision:
164172
165173 bra mainloop
166174
167175 ;══════════════════════════════════════════════════════════════════════════════
168-; CAR MANAGEMENT
176+; COLLISION DETECTION
169177 ;══════════════════════════════════════════════════════════════════════════════
170178
171179 ;------------------------------------------------------------------------------
172-; ERASE_ALL_CARS - Clear all cars from screen
180+; CHECK_COLLISION - Test frog against all cars
173181 ;------------------------------------------------------------------------------
174-erase_all_cars:
182+check_collision:
183+ ; Only check if frog is on a road row
184+ move.w frog_grid_y,d0
185+ cmp.w #ROAD_ROW_FIRST,d0
186+ blt .no_collision
187+ cmp.w #ROAD_ROW_LAST,d0
188+ bgt .no_collision
189+
190+ ; Frog is on road - check against all cars in this row
175191 lea car_data,a2
176192 moveq #NUM_CARS-1,d7
177193
178194 .loop:
179- move.w (a2),d0 ; X position
180- move.w 2(a2),d1 ; Row
195+ ; Check if car is in same row
196+ move.w 2(a2),d1 ; Car row
197+ cmp.w d0,d1 ; Compare with frog row
198+ bne.s .next_car
199+
200+ ; Same row - check X overlap
201+ move.w frog_pixel_x,d2 ; Frog X
202+ move.w (a2),d3 ; Car X
203+
204+ ; Check: frog_x + frog_width > car_x
205+ move.w d2,d4
206+ add.w #FROG_WIDTH,d4
207+ cmp.w d3,d4
208+ ble.s .next_car ; Frog right edge <= car left edge
209+
210+ ; Check: car_x + car_width > frog_x
211+ move.w d3,d4
212+ add.w #CAR_WIDTH_PX,d4
213+ cmp.w d2,d4
214+ ble.s .next_car ; Car right edge <= frog left edge
215+
216+ ; COLLISION! Trigger death
217+ bsr trigger_death
218+ bra.s .no_collision ; Exit early
219+
220+.next_car:
221+ lea CAR_STRUCT_SIZE(a2),a2
222+ dbf d7,.loop
223+
224+.no_collision:
225+ rts
226+
227+;------------------------------------------------------------------------------
228+; TRIGGER_DEATH - Start death sequence
229+;------------------------------------------------------------------------------
230+trigger_death:
231+ move.w #STATE_DYING,frog_state
232+ move.w #DEATH_FRAMES,death_timer
233+
234+ ; Flash screen red by changing Copper colour
235+ move.w #FLASH_COLOUR,flash_colour
236+ rts
237+
238+;------------------------------------------------------------------------------
239+; RESPAWN_FROG - Reset frog to starting position
240+;------------------------------------------------------------------------------
241+respawn_frog:
242+ move.w #START_GRID_X,frog_grid_x
243+ move.w #START_GRID_Y,frog_grid_y
244+ move.w #STATE_IDLE,frog_state
245+ clr.w frog_anim_frame
246+ clr.w joy_prev
247+
248+ ; Reset flash colour to black
249+ move.w #COLOUR_BLACK,flash_colour
250+
251+ bsr grid_to_pixels
252+ rts
253+
254+;══════════════════════════════════════════════════════════════════════════════
255+; CAR MANAGEMENT
256+;══════════════════════════════════════════════════════════════════════════════
257+
258+erase_all_cars:
259+ lea car_data,a2
260+ moveq #NUM_CARS-1,d7
181261
262+.loop:
263+ move.w (a2),d0
264+ move.w 2(a2),d1
182265 bsr calc_screen_addr
183266 bsr wait_blit
184267
...
192275 dbf d7,.loop
193276 rts
194277
195-;------------------------------------------------------------------------------
196-; MOVE_ALL_CARS - Update all car positions
197-;------------------------------------------------------------------------------
198278 move_all_cars:
199279 lea car_data,a2
200280 moveq #NUM_CARS-1,d7
201281
202282 .loop:
203- move.w (a2),d0 ; X position
204- move.w 4(a2),d1 ; Speed (signed)
205- add.w d1,d0 ; Move
283+ move.w (a2),d0
284+ move.w 4(a2),d1
285+ add.w d1,d0
206286
207- ; --- Wrap at screen edges ---
208- tst.w d1 ; Check direction
287+ tst.w d1
209288 bmi.s .check_left
210289
211- ; Moving right
212290 cmp.w #320,d0
213291 blt.s .store
214- sub.w #320+32,d0 ; Wrap from right to left
292+ sub.w #320+32,d0
215293 bra.s .store
216294
217295 .check_left:
218- ; Moving left
219296 cmp.w #-32,d0
220297 bgt.s .store
221- add.w #320+32,d0 ; Wrap from left to right
298+ add.w #320+32,d0
222299
223300 .store:
224- move.w d0,(a2) ; Store new X
225-
301+ move.w d0,(a2)
226302 lea CAR_STRUCT_SIZE(a2),a2
227303 dbf d7,.loop
228304 rts
229305
230-;------------------------------------------------------------------------------
231-; DRAW_ALL_CARS - Draw all cars to screen
232-;------------------------------------------------------------------------------
233306 draw_all_cars:
234307 lea car_data,a2
235308 moveq #NUM_CARS-1,d7
236309
237310 .loop:
238- move.w (a2),d0 ; X position
239- move.w 2(a2),d1 ; Row
311+ move.w (a2),d0
312+ move.w 2(a2),d1
240313
241- ; Skip if offscreen
242314 cmp.w #-32,d0
243315 blt.s .next
244316 cmp.w #320,d0
...
263335 rts
264336
265337 ;══════════════════════════════════════════════════════════════════════════════
266-; BLITTER UTILITIES
338+; FROG ROUTINES
267339 ;══════════════════════════════════════════════════════════════════════════════
268-
269-wait_blit:
270- btst #6,DMACONR(a5)
271- bne.s wait_blit
272- rts
273340
274-clear_screen:
275- bsr.s wait_blit
276- move.l #screen_plane,BLTDPTH(a5)
277- move.w #0,BLTDMOD(a5)
278- move.w #$0100,BLTCON0(a5)
279- move.w #0,BLTCON1(a5)
280- move.w #(SCREEN_H<<6)|SCREEN_W/2,BLTSIZE(a5)
281- rts
341+update_frog:
342+ move.w frog_state,d0
282343
283-calc_screen_addr:
284- ; D0 = pixel X, D1 = row number -> A0 = screen address
285- lea screen_plane,a0
344+ cmp.w #STATE_DYING,d0
345+ beq .dying
286346
287- move.w d1,d2
288- mulu #CELL_SIZE,d2
289- add.w #GRID_ORIGIN_Y,d2
290- mulu #SCREEN_W,d2
291- add.l d2,a0
347+ tst.w d0
348+ beq .idle
349+ bra .hopping
292350
293- move.w d0,d2
294- lsr.w #3,d2
295- ext.l d2
296- add.l d2,a0
297- rts
351+.dying:
352+ ; Count down death timer
353+ subq.w #1,death_timer
354+ bgt.s .still_dying
298355
299-;══════════════════════════════════════════════════════════════════════════════
300-; FROG ROUTINES
301-;══════════════════════════════════════════════════════════════════════════════
356+ ; Death complete - respawn
357+ bsr respawn_frog
358+ bra .done
302359
303-update_frog:
304- tst.w frog_state
305- beq .idle
306- bra .hopping
360+.still_dying:
361+ ; Flash effect: alternate colour
362+ move.w death_timer,d0
363+ and.w #4,d0 ; Flash every 4 frames
364+ beq.s .flash_off
365+ move.w #FLASH_COLOUR,flash_colour
366+ bra .done
367+.flash_off:
368+ move.w #COLOUR_BLACK,flash_colour
369+ bra .done
307370
308371 .idle:
309372 bsr read_joystick_edge
...
394457 move.w #STATE_IDLE,frog_state
395458
396459 .done:
460+ ; Update flash colour in Copper list
461+ move.w flash_colour,flash_copper
397462 rts
463+
464+;══════════════════════════════════════════════════════════════════════════════
465+; UTILITY ROUTINES
466+;══════════════════════════════════════════════════════════════════════════════
398467
399468 grid_to_pixels:
400469 move.w frog_grid_x,d0
...
428497 move.l VPOSR(a5),d0
429498 and.l d1,d0
430499 bne.s .wait
500+ rts
501+
502+wait_blit:
503+ btst #6,DMACONR(a5)
504+ bne.s wait_blit
505+ rts
506+
507+clear_screen:
508+ bsr.s wait_blit
509+ move.l #screen_plane,BLTDPTH(a5)
510+ move.w #0,BLTDMOD(a5)
511+ move.w #$0100,BLTCON0(a5)
512+ move.w #0,BLTCON1(a5)
513+ move.w #(SCREEN_H<<6)|SCREEN_W/2,BLTSIZE(a5)
514+ rts
515+
516+calc_screen_addr:
517+ lea screen_plane,a0
518+ move.w d1,d2
519+ mulu #CELL_SIZE,d2
520+ add.w #GRID_ORIGIN_Y,d2
521+ mulu #SCREEN_W,d2
522+ add.l d2,a0
523+ move.w d0,d2
524+ lsr.w #3,d2
525+ ext.l d2
526+ add.l d2,a0
431527 rts
432528
433529 update_sprite:
...
459555 frog_anim_frame: dc.w 0
460556 joy_prev: dc.w 0
461557
462-; Car data: X position, Row, Speed, (padding)
463-; Row 7 = road lane 1 (rows 7-11 are road lanes)
558+death_timer: dc.w 0
559+flash_colour: dc.w 0
560+
464561 car_data:
465- ; Lane 1 (row 7) - moving right, speed 1
466562 dc.w 0,7,1,0
467563 dc.w 160,7,1,0
468-
469- ; Lane 2 (row 8) - moving left, speed -2
470564 dc.w 100,8,-2,0
471565 dc.w 250,8,-2,0
472-
473- ; Lane 3 (row 9) - moving right, speed 2
474566 dc.w 50,9,2,0
475567 dc.w 200,9,2,0
476-
477- ; Lane 4 (row 10) - moving left, speed -1
478568 dc.w 80,10,-1,0
479569 dc.w 220,10,-1,0
480-
481- ; Lane 5 (row 11) - moving right, speed 3
482570 dc.w 30,11,3,0
483571 dc.w 180,11,3,0
484572
...
487575 ;══════════════════════════════════════════════════════════════════════════════
488576
489577 copperlist:
490- dc.w COLOR00,COLOUR_BLACK
578+ dc.w COLOR00
579+flash_copper:
580+ dc.w COLOUR_BLACK ; This gets modified for flash effect
491581
492582 dc.w $0100,$1200
493583 dc.w $0102,$0000
...
512602 dc.w SPR0PTL
513603 sprptl_val: dc.w $0000
514604
515- ; ROW 1: HOME ZONE
516605 dc.w $2c07,$fffe
517606 dc.w COLOR00,COLOUR_HOME
518607 dc.w $3407,$fffe
...
520609 dc.w $3807,$fffe
521610 dc.w COLOR00,COLOUR_HOME
522611
523- ; ROWS 2-6: WATER ZONE
524612 dc.w $3c07,$fffe
525613 dc.w COLOR00,COLOUR_WATER1
526614 dc.w $4c07,$fffe
...
532620 dc.w $7c07,$fffe
533621 dc.w COLOR00,COLOUR_WATER1
534622
535- ; ROW 7: MEDIAN
536623 dc.w $8c07,$fffe
537624 dc.w COLOR00,COLOUR_MEDIAN
538625
539- ; ROWS 8-12: ROAD ZONE
540626 dc.w $9c07,$fffe
541627 dc.w COLOR00,COLOUR_ROAD1
542628 dc.w $ac07,$fffe
...
548634 dc.w $dc07,$fffe
549635 dc.w COLOR00,COLOUR_ROAD1
550636
551- ; ROW 13: START ZONE
552637 dc.w $ec07,$fffe
553638 dc.w COLOR00,COLOUR_START
554639 dc.w $f407,$fffe