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

Cars on the Road

Blitter-drawn cars. Multiple lanes. Different speeds. Traffic.

11% of Signal

What You’re Building

Traffic.

One car doesn’t make a game. This unit adds ten cars across all five road lanes, each moving at different speeds. Some go left, some go right. The result: a busy road that’s challenging to cross.

By the end of this unit:

  • 10 cars across 5 road lanes
  • 2 cars per lane, staggered positions
  • Different speeds per lane
  • Bidirectional traffic
  • Screen wrapping at edges

Unit 7 Screenshot

Object Data Structures

Each car needs several properties. We store them in a simple array:

; Car Data Structure
; 8 bytes per car: X position, row, speed, padding

; Constants
NUM_CARS        equ 10          ; Total cars
CAR_STRUCT_SIZE equ 8           ; Bytes per car

; Structure layout:
;   Offset 0: X position (word) - pixel position 0-319
;   Offset 2: Row (word) - grid row 7-11 (road lanes)
;   Offset 4: Speed (word) - signed: positive=right, negative=left
;   Offset 6: Reserved (word) - padding for alignment

car_data:
            ; Lane 1 (row 7) - moving right, speed 1
            dc.w    0,7,1,0
            dc.w    160,7,1,0

            ; Lane 2 (row 8) - moving left, speed -2
            dc.w    100,8,-2,0
            dc.w    250,8,-2,0

            ; Lane 3 (row 9) - moving right, speed 2
            dc.w    50,9,2,0
            dc.w    200,9,2,0

            ; Lane 4 (row 10) - moving left, speed -1
            dc.w    80,10,-1,0
            dc.w    220,10,-1,0

            ; Lane 5 (row 11) - moving right, speed 3
            dc.w    30,11,3,0
            dc.w    180,11,3,0

Speed is signed: positive values move right, negative values move left.

The Car Loop

Each frame, we process all cars in three passes:

mainloop:
    bsr     wait_vblank
    bsr     update_frog
    bsr     update_sprite

    bsr     erase_all_cars      ; Pass 1: Clear old positions
    bsr     move_all_cars       ; Pass 2: Update positions
    bsr     draw_all_cars       ; Pass 3: Draw new positions

    bra     mainloop

Why three passes? Erasing all cars first prevents visual glitches where a fast car might be drawn before a slow car in the same area is erased.

Processing All Cars

We use a register as a pointer to walk through the car array. The movement routine also handles screen wrapping:

; Move All Cars with Screen Wrapping
; Update each car's X position, wrap at screen edges

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

.loop:
            move.w  (a2),d0             ; X position
            move.w  4(a2),d1            ; Speed (signed)
            add.w   d1,d0               ; Move

            ; --- Wrap at screen edges ---
            tst.w   d1                  ; Check direction
            bmi.s   .check_left

            ; Moving right: wrap when past right edge
            cmp.w   #320,d0
            blt.s   .store
            sub.w   #320+32,d0          ; Wrap from right to left
            bra.s   .store

.check_left:
            ; Moving left: wrap when past left edge
            cmp.w   #-32,d0
            bgt.s   .store
            add.w   #320+32,d0          ; Wrap from left to right

.store:
            move.w  d0,(a2)             ; Store new X

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

The dbf instruction (Decrement and Branch if False) is the 68000’s loop primitive. It decrements the counter and branches if the result isn’t -1.

We wrap at -32 and 320 to account for the car’s 32-pixel width. This ensures cars fully exit before reappearing.

Lane Configuration

Each lane has its own character:

LaneRowSpeedDirectionFeel
171RightSlow and steady
282LeftQuick, watch out
392RightQuick matching
4101LeftSlow return
5113RightFast and dangerous

The mix of speeds and directions creates interesting patterns for the player to navigate.

Skipping Offscreen Cars

When drawing, we skip cars that are completely offscreen:

; Draw All Cars with Blitter
; Loop through car data, skip offscreen, draw visible cars

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

.loop:
            move.w  (a2),d0             ; X position
            move.w  2(a2),d1            ; Row

            ; Skip if offscreen
            cmp.w   #-32,d0
            blt.s   .next
            cmp.w   #320,d0
            bge.s   .next

            ; Calculate screen address and draw
            bsr     calc_screen_addr
            bsr     wait_blit

            ; Set up Blitter for A→D copy
            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)  ; A→D copy
            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

This is a small optimisation, but more importantly it prevents drawing into invalid screen memory.

The Blitter Cost

Ten cars means ten blitter operations per frame for erasing, plus ten for drawing—twenty total. Each blit of a 32×12 car takes approximately:

Words per blit: 2 × 12 = 24 words
Cycles per word: ~4 DMA cycles
Total: ~96 DMA cycles per blit
20 blits: ~1920 DMA cycles

The Amiga has plenty of DMA bandwidth for this. We could easily have 50+ cars before running into trouble.

Why Not Use Sprites?

We’re using the frog as a sprite (which makes collision detection easier later). For cars, the Blitter approach works well because:

  1. All cars look the same: We reuse one graphic
  2. Cars move horizontally: Easy screen address calculation
  3. Many cars needed: More than 8 sprites would require multiplexing
  4. No transparency needed: Cars don’t overlap each other (much)

Key Takeaways

  • Object arrays store multiple similar entities
  • Three-pass update (erase, move, draw) prevents visual glitches
  • Signed speed enables bidirectional movement
  • Screen wrapping creates continuous traffic flow
  • Offscreen culling prevents invalid memory access
  • Blitter loops handle many objects efficiently

The Code

;══════════════════════════════════════════════════════════════════════════════
; SIGNAL - A Frogger-style game for the Commodore Amiga
; Unit 7: Cars on the Road
;
; One car isn't traffic. This unit adds multiple cars across all five road
; lanes, each moving at different speeds. Some go left, some go right.
; The Blitter draws them all efficiently using a loop.
;══════════════════════════════════════════════════════════════════════════════

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

; Screen setup
SCREEN_W        equ 40
SCREEN_H        equ 256
PLANE_SIZE      equ SCREEN_W*SCREEN_H

; Grid constants
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

; Frog animation
HOP_FRAMES      equ 8
PIXELS_PER_FRAME equ 2
STATE_IDLE      equ 0
STATE_HOPPING   equ 1
DIR_UP          equ 0
DIR_DOWN        equ 1
DIR_LEFT        equ 2
DIR_RIGHT       equ 3
FROG_HEIGHT     equ 16

; Car constants
NUM_CARS        equ 10          ; Total cars
CAR_WIDTH       equ 2           ; 32 pixels = 2 words
CAR_HEIGHT      equ 12
CAR_STRUCT_SIZE equ 8           ; Bytes per car: x(2), row(2), speed(2), pad(2)

; 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

; Sprite and car colours
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

            ; --- System takeover ---
            move.w  #$7fff,INTENA(a5)
            move.w  #$7fff,INTREQ(a5)
            move.w  #$7fff,DMACON(a5)

            ; --- Clear screen ---
            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 ---
            move.w  #START_GRID_X,frog_grid_x
            move.w  #START_GRID_Y,frog_grid_y
            move.w  #STATE_IDLE,frog_state
            clr.w   joy_prev
            bsr     grid_to_pixels

            ; --- 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 ---
            bsr     update_frog
            bsr     update_sprite

            ; --- Update cars ---
            bsr     erase_all_cars
            bsr     move_all_cars
            bsr     draw_all_cars

            bra     mainloop

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

;------------------------------------------------------------------------------
; ERASE_ALL_CARS - Clear all cars from screen
;------------------------------------------------------------------------------
erase_all_cars:
            lea     car_data,a2
            moveq   #NUM_CARS-1,d7

.loop:
            move.w  (a2),d0             ; X position
            move.w  2(a2),d1            ; Row

            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 - Update all car positions
;------------------------------------------------------------------------------
move_all_cars:
            lea     car_data,a2
            moveq   #NUM_CARS-1,d7

.loop:
            move.w  (a2),d0             ; X position
            move.w  4(a2),d1            ; Speed (signed)
            add.w   d1,d0               ; Move

            ; --- Wrap at screen edges ---
            tst.w   d1                  ; Check direction
            bmi.s   .check_left

            ; Moving right
            cmp.w   #320,d0
            blt.s   .store
            sub.w   #320+32,d0          ; Wrap from right to left
            bra.s   .store

.check_left:
            ; Moving left
            cmp.w   #-32,d0
            bgt.s   .store
            add.w   #320+32,d0          ; Wrap from left to right

.store:
            move.w  d0,(a2)             ; Store new X

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

;------------------------------------------------------------------------------
; DRAW_ALL_CARS - Draw all cars to screen
;------------------------------------------------------------------------------
draw_all_cars:
            lea     car_data,a2
            moveq   #NUM_CARS-1,d7

.loop:
            move.w  (a2),d0             ; X position
            move.w  2(a2),d1            ; Row

            ; Skip if offscreen
            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

;══════════════════════════════════════════════════════════════════════════════
; BLITTER UTILITIES
;══════════════════════════════════════════════════════════════════════════════

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:
            ; D0 = pixel X, D1 = row number -> A0 = screen address
            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

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

update_frog:
            tst.w   frog_state
            beq     .idle
            bra     .hopping

.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:
            rts

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

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

; Car data: X position, Row, Speed, (padding)
; Row 7 = road lane 1 (rows 7-11 are road lanes)
car_data:
            ; Lane 1 (row 7) - moving right, speed 1
            dc.w    0,7,1,0
            dc.w    160,7,1,0

            ; Lane 2 (row 8) - moving left, speed -2
            dc.w    100,8,-2,0
            dc.w    250,8,-2,0

            ; Lane 3 (row 9) - moving right, speed 2
            dc.w    50,9,2,0
            dc.w    200,9,2,0

            ; Lane 4 (row 10) - moving left, speed -1
            dc.w    80,10,-1,0
            dc.w    220,10,-1,0

            ; Lane 5 (row 11) - moving right, speed 3
            dc.w    30,11,3,0
            dc.w    180,11,3,0

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

copperlist:
            dc.w    COLOR00,COLOUR_BLACK

            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

            ; ROW 1: HOME ZONE
            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

            ; ROWS 2-6: WATER ZONE
            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

            ; ROW 7: MEDIAN
            dc.w    $8c07,$fffe
            dc.w    COLOR00,COLOUR_MEDIAN

            ; ROWS 8-12: ROAD ZONE
            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

            ; ROW 13: START ZONE
            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

Traffic fills the road, but the frog can walk right through it! In Unit 8, we’ll add collision detection—finally making the game dangerous.

What Changed

Unit 6 → Unit 7
+156-133
11 ;══════════════════════════════════════════════════════════════════════════════
22 ; SIGNAL - A Frogger-style game for the Commodore Amiga
3-; Unit 6: The Blitter Introduction
4-;
5-; Hardware sprites are limited to 8. A Frogger game needs dozens of moving
6-; objects: cars, trucks, logs, turtles. The solution? The Blitter—a DMA
7-; coprocessor that copies rectangular blocks of memory at high speed.
3+; Unit 7: Cars on the Road
84 ;
9-; This unit introduces the Blitter with a simple demonstration: one car
10-; driving across the road, drawn using the Blitter instead of sprites.
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.
118 ;══════════════════════════════════════════════════════════════════════════════
129
1310 ;══════════════════════════════════════════════════════════════════════════════
...
1512 ;══════════════════════════════════════════════════════════════════════════════
1613
1714 ; Screen setup
18-SCREEN_W equ 40 ; 320 pixels = 40 bytes per line
19-SCREEN_H equ 256 ; PAL lines
20-PLANE_SIZE equ SCREEN_W*SCREEN_H ; 10240 bytes per bitplane
15+SCREEN_W equ 40
16+SCREEN_H equ 256
17+PLANE_SIZE equ SCREEN_W*SCREEN_H
2118
22-; Grid constants (from Unit 5)
19+; Grid constants
2320 GRID_COLS equ 20
2421 GRID_ROWS equ 13
2522 CELL_SIZE equ 16
...
2825 START_GRID_X equ 9
2926 START_GRID_Y equ 12
3027
31-; Animation
28+; Frog animation
3229 HOP_FRAMES equ 8
3330 PIXELS_PER_FRAME equ 2
3431 STATE_IDLE equ 0
...
4037 FROG_HEIGHT equ 16
4138
4239 ; Car constants
40+NUM_CARS equ 10 ; Total cars
4341 CAR_WIDTH equ 2 ; 32 pixels = 2 words
44-CAR_HEIGHT equ 12 ; 12 lines tall
45-CAR_SPEED equ 2 ; Pixels per frame
46-CAR_ROW equ 10 ; Road lane (row 10 = scanline 204)
42+CAR_HEIGHT equ 12
43+CAR_STRUCT_SIZE equ 8 ; Bytes per car: x(2), row(2), speed(2), pad(2)
4744
4845 ; Zone colours
4946 COLOUR_BLACK equ $0000
...
5754 COLOUR_START equ $0262
5855 COLOUR_START_LIT equ $0373
5956
60-; Sprite and playfield colours
57+; Sprite and car colours
6158 COLOUR_FROG equ $00f0
6259 COLOUR_EYES equ $0ff0
6360 COLOUR_OUTLINE equ $0000
64-COLOUR_CAR equ $0f00 ; Red car on bitplane
61+COLOUR_CAR equ $0f00
6562
6663 ;══════════════════════════════════════════════════════════════════════════════
6764 ; HARDWARE REGISTERS
...
7370 VPOSR equ $004
7471 JOY1DAT equ $00c
7572
76-; Blitter registers
7773 BLTCON0 equ $040
7874 BLTCON1 equ $042
7975 BLTAFWM equ $044
...
114110 ; --- Clear screen ---
115111 bsr clear_screen
116112
117- ; --- Set up bitplane pointer in copper list ---
113+ ; --- Set up bitplane pointer ---
118114 lea screen_plane,a0
119115 move.l a0,d0
120116 swap d0
...
140136 swap d0
141137 lea sprptl_val,a1
142138 move.w d0,(a1)
143-
144139 bsr update_sprite
145-
146- ; --- Initialise car ---
147- clr.w car_x
148140
149141 ; --- Install copper list ---
150142 lea copperlist,a0
151143 move.l a0,COP1LC(a5)
152144 move.w d0,COPJMP1(a5)
153145
154- ; --- Enable DMA (including Blitter) ---
155- move.w #$87e0,DMACON(a5) ; Master + blit + copper + bitplane + sprite
146+ ; --- Enable DMA ---
147+ move.w #$87e0,DMACON(a5)
156148
157149 ;══════════════════════════════════════════════════════════════════════════════
158150 ; MAIN LOOP
...
165157 bsr update_frog
166158 bsr update_sprite
167159
168- ; --- Update car ---
169- bsr erase_car ; Erase at old position
170- bsr move_car ; Update position
171- bsr draw_car ; Draw at new position
160+ ; --- Update cars ---
161+ bsr erase_all_cars
162+ bsr move_all_cars
163+ bsr draw_all_cars
172164
173165 bra mainloop
174166
175167 ;══════════════════════════════════════════════════════════════════════════════
176-; BLITTER ROUTINES
168+; CAR MANAGEMENT
177169 ;══════════════════════════════════════════════════════════════════════════════
178170
179171 ;------------------------------------------------------------------------------
180-; WAIT_BLIT - Wait for Blitter to finish
172+; ERASE_ALL_CARS - Clear all cars from screen
181173 ;------------------------------------------------------------------------------
182-wait_blit:
183- btst #6,DMACONR(a5) ; Test BBUSY flag
184- bne.s wait_blit ; Loop while busy
185- rts
174+erase_all_cars:
175+ lea car_data,a2
176+ moveq #NUM_CARS-1,d7
186177
187-;------------------------------------------------------------------------------
188-; CLEAR_SCREEN - Clear the screen buffer using Blitter
189-;------------------------------------------------------------------------------
190-clear_screen:
191- bsr.s wait_blit
178+.loop:
179+ move.w (a2),d0 ; X position
180+ move.w 2(a2),d1 ; Row
192181
193- ; Clear entire plane: D channel only, minterm = 0
194- move.l #screen_plane,BLTDPTH(a5)
195- move.w #0,BLTDMOD(a5) ; No modulo (continuous)
196- move.w #$0100,BLTCON0(a5) ; D only, minterm 0 (clear)
182+ bsr calc_screen_addr
183+ bsr wait_blit
184+
185+ move.l a0,BLTDPTH(a5)
186+ move.w #SCREEN_W-CAR_WIDTH*2,BLTDMOD(a5)
187+ move.w #$0100,BLTCON0(a5)
197188 move.w #0,BLTCON1(a5)
198- move.w #(SCREEN_H<<6)|SCREEN_W/2,BLTSIZE(a5) ; Trigger!
189+ move.w #(CAR_HEIGHT<<6)|CAR_WIDTH,BLTSIZE(a5)
199190
191+ lea CAR_STRUCT_SIZE(a2),a2
192+ dbf d7,.loop
200193 rts
201194
202195 ;------------------------------------------------------------------------------
203-; ERASE_CAR - Clear car at current position
196+; MOVE_ALL_CARS - Update all car positions
204197 ;------------------------------------------------------------------------------
205-erase_car:
206- bsr.s wait_blit
198+move_all_cars:
199+ lea car_data,a2
200+ moveq #NUM_CARS-1,d7
207201
208- ; Calculate screen address for car position
209- move.w car_x,d0
210- move.w #CAR_ROW,d1
211- bsr calc_screen_addr ; A0 = destination
202+.loop:
203+ move.w (a2),d0 ; X position
204+ move.w 4(a2),d1 ; Speed (signed)
205+ add.w d1,d0 ; Move
212206
213- ; Erase: D channel only, minterm 0
214- move.l a0,BLTDPTH(a5)
215- move.w #SCREEN_W-CAR_WIDTH*2,BLTDMOD(a5)
216- move.w #$0100,BLTCON0(a5)
217- move.w #0,BLTCON1(a5)
218- move.w #(CAR_HEIGHT<<6)|CAR_WIDTH,BLTSIZE(a5)
207+ ; --- Wrap at screen edges ---
208+ tst.w d1 ; Check direction
209+ bmi.s .check_left
210+
211+ ; Moving right
212+ cmp.w #320,d0
213+ blt.s .store
214+ sub.w #320+32,d0 ; Wrap from right to left
215+ bra.s .store
216+
217+.check_left:
218+ ; Moving left
219+ cmp.w #-32,d0
220+ bgt.s .store
221+ add.w #320+32,d0 ; Wrap from left to right
222+
223+.store:
224+ move.w d0,(a2) ; Store new X
219225
226+ lea CAR_STRUCT_SIZE(a2),a2
227+ dbf d7,.loop
220228 rts
221229
222230 ;------------------------------------------------------------------------------
223-; DRAW_CAR - Draw car at current position using Blitter
231+; DRAW_ALL_CARS - Draw all cars to screen
224232 ;------------------------------------------------------------------------------
225-draw_car:
226- bsr.s wait_blit
233+draw_all_cars:
234+ lea car_data,a2
235+ moveq #NUM_CARS-1,d7
227236
228- ; Calculate screen address
229- move.w car_x,d0
230- move.w #CAR_ROW,d1
231- bsr calc_screen_addr ; A0 = destination
237+.loop:
238+ move.w (a2),d0 ; X position
239+ move.w 2(a2),d1 ; Row
232240
233- ; Copy A→D: source graphics to screen
241+ ; Skip if offscreen
242+ cmp.w #-32,d0
243+ blt.s .next
244+ cmp.w #320,d0
245+ bge.s .next
246+
247+ bsr calc_screen_addr
248+ bsr wait_blit
249+
234250 move.l #car_gfx,BLTAPTH(a5)
235251 move.l a0,BLTDPTH(a5)
236-
237- move.w #$ffff,BLTAFWM(a5) ; No masking
252+ move.w #$ffff,BLTAFWM(a5)
238253 move.w #$ffff,BLTALWM(a5)
239- move.w #0,BLTAMOD(a5) ; Source: no gaps
240- move.w #SCREEN_W-CAR_WIDTH*2,BLTDMOD(a5) ; Dest: screen width
241- move.w #$09f0,BLTCON0(a5) ; A→D, D=A
254+ move.w #0,BLTAMOD(a5)
255+ move.w #SCREEN_W-CAR_WIDTH*2,BLTDMOD(a5)
256+ move.w #$09f0,BLTCON0(a5)
242257 move.w #0,BLTCON1(a5)
243- move.w #(CAR_HEIGHT<<6)|CAR_WIDTH,BLTSIZE(a5) ; Trigger!
258+ move.w #(CAR_HEIGHT<<6)|CAR_WIDTH,BLTSIZE(a5)
244259
260+.next:
261+ lea CAR_STRUCT_SIZE(a2),a2
262+ dbf d7,.loop
245263 rts
246264
247-;------------------------------------------------------------------------------
248-; CALC_SCREEN_ADDR - Calculate screen address from grid position
249-;------------------------------------------------------------------------------
250-; Input: D0 = pixel X, D1 = row number
251-; Output: A0 = screen address
265+;══════════════════════════════════════════════════════════════════════════════
266+; BLITTER UTILITIES
267+;══════════════════════════════════════════════════════════════════════════════
268+
269+wait_blit:
270+ btst #6,DMACONR(a5)
271+ bne.s wait_blit
272+ rts
273+
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
252282
253283 calc_screen_addr:
284+ ; D0 = pixel X, D1 = row number -> A0 = screen address
254285 lea screen_plane,a0
255286
256- ; Calculate Y offset: row × CELL_SIZE × SCREEN_W
257287 move.w d1,d2
258- mulu #CELL_SIZE,d2 ; D2 = row in pixels (from top of grid)
259- add.w #GRID_ORIGIN_Y,d2 ; Add top margin
260- mulu #SCREEN_W,d2 ; D2 = byte offset for Y
288+ mulu #CELL_SIZE,d2
289+ add.w #GRID_ORIGIN_Y,d2
290+ mulu #SCREEN_W,d2
261291 add.l d2,a0
262292
263- ; Calculate X offset: pixel_x / 8 (convert to bytes)
264293 move.w d0,d2
265- lsr.w #3,d2 ; D2 = X in bytes
294+ lsr.w #3,d2
266295 ext.l d2
267296 add.l d2,a0
268-
269- rts
270-
271-;------------------------------------------------------------------------------
272-; MOVE_CAR - Update car position
273-;------------------------------------------------------------------------------
274-move_car:
275- add.w #CAR_SPEED,car_x
276- cmp.w #320,car_x
277- blt.s .no_wrap
278- clr.w car_x
279-.no_wrap:
280297 rts
281298
282299 ;══════════════════════════════════════════════════════════════════════════════
283-; FROG ROUTINES (from Unit 5)
300+; FROG ROUTINES
284301 ;══════════════════════════════════════════════════════════════════════════════
285302
286303 update_frog:
...
404421
405422 move.w d1,d0
406423 rts
407-
408-;══════════════════════════════════════════════════════════════════════════════
409-; UTILITY ROUTINES
410-;══════════════════════════════════════════════════════════════════════════════
411424
412425 wait_vblank:
413426 move.l #$1ff00,d1
...
446459 frog_anim_frame: dc.w 0
447460 joy_prev: dc.w 0
448461
449-car_x: dc.w 0
462+; Car data: X position, Row, Speed, (padding)
463+; Row 7 = road lane 1 (rows 7-11 are road lanes)
464+car_data:
465+ ; Lane 1 (row 7) - moving right, speed 1
466+ dc.w 0,7,1,0
467+ dc.w 160,7,1,0
468+
469+ ; Lane 2 (row 8) - moving left, speed -2
470+ dc.w 100,8,-2,0
471+ dc.w 250,8,-2,0
472+
473+ ; Lane 3 (row 9) - moving right, speed 2
474+ dc.w 50,9,2,0
475+ dc.w 200,9,2,0
476+
477+ ; Lane 4 (row 10) - moving left, speed -1
478+ dc.w 80,10,-1,0
479+ dc.w 220,10,-1,0
480+
481+ ; Lane 5 (row 11) - moving right, speed 3
482+ dc.w 30,11,3,0
483+ dc.w 180,11,3,0
450484
451485 ;══════════════════════════════════════════════════════════════════════════════
452486 ; COPPER LIST
...
455489 copperlist:
456490 dc.w COLOR00,COLOUR_BLACK
457491
458- ; --- Bitplane control: 1 plane, colour on ---
459- dc.w $0100,$1200 ; BPLCON0: 1 plane
460- dc.w $0102,$0000 ; BPLCON1: no scroll
461- dc.w $0104,$0000 ; BPLCON2: default priority
462- dc.w $0108,$0000 ; BPL1MOD
463- dc.w $010a,$0000 ; BPL2MOD
492+ dc.w $0100,$1200
493+ dc.w $0102,$0000
494+ dc.w $0104,$0000
495+ dc.w $0108,$0000
496+ dc.w $010a,$0000
464497
465- ; --- Bitplane pointer ---
466- dc.w $00e0 ; BPL1PTH
498+ dc.w $00e0
467499 bplpth_val: dc.w $0000
468- dc.w $00e2 ; BPL1PTL
500+ dc.w $00e2
469501 bplptl_val: dc.w $0000
470502
471- ; --- Playfield colours ---
472- dc.w $0180,$0000 ; Colour 0: transparent (shows Copper bg)
473- dc.w $0182,COLOUR_CAR ; Colour 1: red (car from bitplane)
503+ dc.w $0180,$0000
504+ dc.w $0182,COLOUR_CAR
474505
475- ; --- Sprite palette ---
476506 dc.w $01a2,COLOUR_FROG
477507 dc.w $01a4,COLOUR_EYES
478508 dc.w $01a6,COLOUR_OUTLINE
479509
480- ; --- Sprite 0 pointer ---
481510 dc.w SPR0PTH
482511 sprpth_val: dc.w $0000
483512 dc.w SPR0PTL
...
527556 dc.w $f807,$fffe
528557 dc.w COLOR00,COLOUR_START
529558
530- ; BOTTOM BORDER
531559 dc.w $fc07,$fffe
532560 dc.w COLOR00,COLOUR_BLACK
533561
...
539567
540568 even
541569 car_gfx:
542- ; 32×12 pixel car (2 words wide × 12 lines)
543- dc.w $0ff0,$0ff0 ; ..####....####..
544- dc.w $3ffc,$3ffc ; ##########..####
545- dc.w $7ffe,$7ffe ; ##############..
546- dc.w $ffff,$ffff ; ################
547- dc.w $ffff,$ffff ; ################
548- dc.w $ffff,$ffff ; ################
549- dc.w $ffff,$ffff ; ################
550- dc.w $ffff,$ffff ; ################
551- dc.w $ffff,$ffff ; ################
552- dc.w $7ffe,$7ffe ; ##############..
553- dc.w $3c3c,$3c3c ; Wheels
570+ dc.w $0ff0,$0ff0
571+ dc.w $3ffc,$3ffc
572+ dc.w $7ffe,$7ffe
573+ dc.w $ffff,$ffff
574+ dc.w $ffff,$ffff
575+ dc.w $ffff,$ffff
576+ dc.w $ffff,$ffff
577+ dc.w $ffff,$ffff
578+ dc.w $ffff,$ffff
579+ dc.w $7ffe,$7ffe
580+ dc.w $3c3c,$3c3c
554581 dc.w $0000,$0000
555-
556-;══════════════════════════════════════════════════════════════════════════════
557-; SPRITE DATA
558-;══════════════════════════════════════════════════════════════════════════════
559582
560583 even
561584 frog_data:
...
581604 dc.w $0000,$0000
582605
583606 ;══════════════════════════════════════════════════════════════════════════════
584-; SCREEN BUFFER (Chip RAM)
607+; SCREEN BUFFER
585608 ;══════════════════════════════════════════════════════════════════════════════
586609
587610 section chipbss,bss_c