The Blitter Introduction
Why 8 sprites isn't enough. DMA graphics engine. BOBs for cars.
What You’re Building
The Blitter.
Hardware sprites are wonderful—automatic, fast, zero CPU cost. But the Amiga only has 8 of them. A Frogger game needs dozens of moving objects: five lanes of cars, five lanes of logs and turtles, plus the frog. Eight sprites won’t cut it.
The solution? The Blitter—a DMA coprocessor that copies rectangular blocks of memory at blazing speed.
By the end of this unit:
- One car drives across the road
- Drawn using Blitter, not sprites
- You understand BOBs vs sprites
- Foundation for many moving objects

Why Not Just More Sprites?
The Amiga’s 8 hardware sprites have limitations:
- Only 16 pixels wide (though they can be any height)
- 3 colours each (plus transparent)
- Multiplexing is tricky (reusing sprites mid-screen)
For a game with many similar objects moving horizontally, the Blitter is often easier.
What Is the Blitter?
The Block Image Transferrer is a DMA coprocessor. It copies rectangular regions of memory without CPU involvement. While the Blitter works, the CPU can do other things.
Key capabilities:
- Copy graphics from one place to another
- Combine multiple sources using logic operations
- Shift data for pixel-precise positioning
- Fill areas with solid colour
- Draw lines (yes, hardware line drawing!)
We’ll start with simple copying.
Setting Up Bitplanes
Unlike our sprite-only previous units, we now need screen memory:
SCREEN_W equ 40 ; 320 pixels = 40 bytes per line
SCREEN_H equ 256 ; PAL lines
PLANE_SIZE equ SCREEN_W*SCREEN_H ; 10240 bytes
; In chip BSS section
screen_plane: ds.b PLANE_SIZE
The Copper points at this memory, and the Blitter draws into it.
Blitter Registers
The Blitter has four channels: A, B, C, and D (destination). For a simple copy, we use A (source) and D (destination):
| Register | Purpose |
|---|---|
| BLTCON0 | Control: which channels, what operation |
| BLTCON1 | Control: shift amount, special modes |
| BLTAPTH | Source A pointer (high word) |
| BLTDPTH | Destination D pointer (high word) |
| BLTAMOD | Source A modulo (bytes to skip per row) |
| BLTDMOD | Destination D modulo |
| BLTSIZE | Size and trigger—writing here starts the blit |
A Simple Copy
To copy our car graphics to the screen:
; Draw Car with Blitter
; A→D copy: source graphics to screen
draw_car:
bsr wait_blit
; Calculate screen destination
move.w car_x,d0
move.w #CAR_ROW,d1
bsr calc_screen_addr ; A0 = destination
; Set up channels
move.l #car_gfx,BLTAPTH(a5) ; A = source graphics
move.l a0,BLTDPTH(a5) ; D = screen
; Masks: no masking needed (full words)
move.w #$ffff,BLTAFWM(a5)
move.w #$ffff,BLTALWM(a5)
; Modulos
move.w #0,BLTAMOD(a5) ; Source: no gaps
move.w #SCREEN_W-CAR_WIDTH*2,BLTDMOD(a5) ; Dest: screen stride
; BLTCON0: A→D copy, D=A minterm
; $09f0 = use A+D channels, minterm F0 (copy A)
move.w #$09f0,BLTCON0(a5)
move.w #0,BLTCON1(a5)
; Size: 12 lines × 2 words wide
move.w #(CAR_HEIGHT<<6)|CAR_WIDTH,BLTSIZE(a5)
rts
Understanding BLTCON0
The value $09f0 breaks down as:
$09f0 = %0000 1001 1111 0000
│ │ ││││
│ │ └┴┴┴─ Minterm: $F0 = D = A
│ └────── USEA: Use channel A
└───────── USED: Use channel D (always for output)
The minterm is a 4-bit pattern that specifies how to combine A, B, and C. $F0 means “D = A”—just copy A to D.
Understanding BLTSIZE
move.w #(CAR_HEIGHT<<6)|CAR_WIDTH,BLTSIZE(a5)
BLTSIZE encodes both dimensions:
- Bits 15-6: Height in lines (max 1024)
- Bits 5-0: Width in words (max 64 words = 1024 pixels)
Writing to BLTSIZE triggers the blit immediately.
Waiting for the Blitter
The Blitter runs asynchronously. Before starting a new blit or reading results, we must wait:
wait_blit:
btst #6,DMACONR(a5) ; Test BBUSY flag
bne.s wait_blit ; Loop while busy
rts
Bit 6 of DMACONR is the “blitter busy” flag.
Erasing the Car
Before drawing at the new position, we erase at the old position. We use the clear_screen approach which uses the Blitter with minterm 0:
; Clear Screen with Blitter
; D channel only, minterm 0 = all zeros
clear_screen:
bsr wait_blit ; Must wait before setting up
; Destination pointer
move.l #screen_plane,BLTDPTH(a5)
; Modulo: 0 = continuous (no gaps between rows)
move.w #0,BLTDMOD(a5)
; BLTCON0: D channel only, minterm 0 (clear)
; $0100 = use D, minterms all 0
move.w #$0100,BLTCON0(a5)
move.w #0,BLTCON1(a5)
; Size: trigger the blit!
; Format: [height << 6] | [width_in_words]
; 256 lines × 20 words = full PAL screen
move.w #(256<<6)|20,BLTSIZE(a5)
rts
Minterm $00 (via BLTCON0 = $0100) means “D = 0”—clear the destination.
The Animation Loop
Each frame:
- Wait for VBlank
- Erase car at old position
- Update car position
- Draw car at new position
mainloop:
bsr wait_vblank
bsr update_frog
bsr update_sprite
bsr erase_car ; Clear old
bsr move_car ; Update position
bsr draw_car ; Draw new
bra mainloop
BOBs vs Sprites
What we’ve created is called a BOB (Blitter Object):
| Feature | Hardware Sprite | BOB |
|---|---|---|
| Count | 8 maximum | Unlimited* |
| Width | 16 pixels | Any size |
| Colours | 3 (+transparent) | As many as bitplanes allow |
| CPU cost | Zero | Low (Blitter DMA) |
| Erasing | Automatic | Must be done manually |
| Behind playfield | Easy | Requires masking |
*Limited by Blitter time per frame
Enabling Blitter DMA
We added blitter DMA to our DMACON setup:
move.w #$87e0,DMACON(a5) ; Master + blit + copper + bitplane + sprite
Bit 6 (BLTEN) enables Blitter DMA.
Key Takeaways
- 8 sprites isn’t enough for games with many objects
- The Blitter copies rectangular blocks via DMA
- BOBs are objects drawn by the Blitter
- BLTCON0 controls which channels and what operation
- BLTSIZE encodes size and triggers the blit
- Always wait for the Blitter before starting another blit
The Code
;══════════════════════════════════════════════════════════════════════════════
; SIGNAL - A Frogger-style game for the Commodore Amiga
; Unit 6: The Blitter Introduction
;
; Hardware sprites are limited to 8. A Frogger game needs dozens of moving
; objects: cars, trucks, logs, turtles. The solution? The Blitter—a DMA
; coprocessor that copies rectangular blocks of memory at high speed.
;
; This unit introduces the Blitter with a simple demonstration: one car
; driving across the road, drawn using the Blitter instead of sprites.
;══════════════════════════════════════════════════════════════════════════════
;══════════════════════════════════════════════════════════════════════════════
; CONSTANTS
;══════════════════════════════════════════════════════════════════════════════
; Screen setup
SCREEN_W equ 40 ; 320 pixels = 40 bytes per line
SCREEN_H equ 256 ; PAL lines
PLANE_SIZE equ SCREEN_W*SCREEN_H ; 10240 bytes per bitplane
; Grid constants (from Unit 5)
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
; 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
CAR_WIDTH equ 2 ; 32 pixels = 2 words
CAR_HEIGHT equ 12 ; 12 lines tall
CAR_SPEED equ 2 ; Pixels per frame
CAR_ROW equ 10 ; Road lane (row 10 = scanline 204)
; 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 playfield colours
COLOUR_FROG equ $00f0
COLOUR_EYES equ $0ff0
COLOUR_OUTLINE equ $0000
COLOUR_CAR equ $0f00 ; Red car on bitplane
;══════════════════════════════════════════════════════════════════════════════
; HARDWARE REGISTERS
;══════════════════════════════════════════════════════════════════════════════
CUSTOM equ $dff000
DMACONR equ $002
VPOSR equ $004
JOY1DAT equ $00c
; Blitter registers
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 in copper list ---
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
; --- Initialise car ---
clr.w car_x
; --- Install copper list ---
lea copperlist,a0
move.l a0,COP1LC(a5)
move.w d0,COPJMP1(a5)
; --- Enable DMA (including Blitter) ---
move.w #$87e0,DMACON(a5) ; Master + blit + copper + bitplane + sprite
;══════════════════════════════════════════════════════════════════════════════
; MAIN LOOP
;══════════════════════════════════════════════════════════════════════════════
mainloop:
bsr wait_vblank
; --- Update frog ---
bsr update_frog
bsr update_sprite
; --- Update car ---
bsr erase_car ; Erase at old position
bsr move_car ; Update position
bsr draw_car ; Draw at new position
bra mainloop
;══════════════════════════════════════════════════════════════════════════════
; BLITTER ROUTINES
;══════════════════════════════════════════════════════════════════════════════
;------------------------------------------------------------------------------
; WAIT_BLIT - Wait for Blitter to finish
;------------------------------------------------------------------------------
wait_blit:
btst #6,DMACONR(a5) ; Test BBUSY flag
bne.s wait_blit ; Loop while busy
rts
;------------------------------------------------------------------------------
; CLEAR_SCREEN - Clear the screen buffer using Blitter
;------------------------------------------------------------------------------
clear_screen:
bsr.s wait_blit
; Clear entire plane: D channel only, minterm = 0
move.l #screen_plane,BLTDPTH(a5)
move.w #0,BLTDMOD(a5) ; No modulo (continuous)
move.w #$0100,BLTCON0(a5) ; D only, minterm 0 (clear)
move.w #0,BLTCON1(a5)
move.w #(SCREEN_H<<6)|SCREEN_W/2,BLTSIZE(a5) ; Trigger!
rts
;------------------------------------------------------------------------------
; ERASE_CAR - Clear car at current position
;------------------------------------------------------------------------------
erase_car:
bsr.s wait_blit
; Calculate screen address for car position
move.w car_x,d0
move.w #CAR_ROW,d1
bsr calc_screen_addr ; A0 = destination
; Erase: D channel only, minterm 0
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)
rts
;------------------------------------------------------------------------------
; DRAW_CAR - Draw car at current position using Blitter
;------------------------------------------------------------------------------
draw_car:
bsr.s wait_blit
; Calculate screen address
move.w car_x,d0
move.w #CAR_ROW,d1
bsr calc_screen_addr ; A0 = destination
; Copy A→D: source graphics to screen
move.l #car_gfx,BLTAPTH(a5)
move.l a0,BLTDPTH(a5)
move.w #$ffff,BLTAFWM(a5) ; No masking
move.w #$ffff,BLTALWM(a5)
move.w #0,BLTAMOD(a5) ; Source: no gaps
move.w #SCREEN_W-CAR_WIDTH*2,BLTDMOD(a5) ; Dest: screen width
move.w #$09f0,BLTCON0(a5) ; A→D, D=A
move.w #0,BLTCON1(a5)
move.w #(CAR_HEIGHT<<6)|CAR_WIDTH,BLTSIZE(a5) ; Trigger!
rts
;------------------------------------------------------------------------------
; CALC_SCREEN_ADDR - Calculate screen address from grid position
;------------------------------------------------------------------------------
; Input: D0 = pixel X, D1 = row number
; Output: A0 = screen address
calc_screen_addr:
lea screen_plane,a0
; Calculate Y offset: row × CELL_SIZE × SCREEN_W
move.w d1,d2
mulu #CELL_SIZE,d2 ; D2 = row in pixels (from top of grid)
add.w #GRID_ORIGIN_Y,d2 ; Add top margin
mulu #SCREEN_W,d2 ; D2 = byte offset for Y
add.l d2,a0
; Calculate X offset: pixel_x / 8 (convert to bytes)
move.w d0,d2
lsr.w #3,d2 ; D2 = X in bytes
ext.l d2
add.l d2,a0
rts
;------------------------------------------------------------------------------
; MOVE_CAR - Update car position
;------------------------------------------------------------------------------
move_car:
add.w #CAR_SPEED,car_x
cmp.w #320,car_x
blt.s .no_wrap
clr.w car_x
.no_wrap:
rts
;══════════════════════════════════════════════════════════════════════════════
; FROG ROUTINES (from Unit 5)
;══════════════════════════════════════════════════════════════════════════════
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
;══════════════════════════════════════════════════════════════════════════════
; UTILITY ROUTINES
;══════════════════════════════════════════════════════════════════════════════
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_x: dc.w 0
;══════════════════════════════════════════════════════════════════════════════
; COPPER LIST
;══════════════════════════════════════════════════════════════════════════════
copperlist:
dc.w COLOR00,COLOUR_BLACK
; --- Bitplane control: 1 plane, colour on ---
dc.w $0100,$1200 ; BPLCON0: 1 plane
dc.w $0102,$0000 ; BPLCON1: no scroll
dc.w $0104,$0000 ; BPLCON2: default priority
dc.w $0108,$0000 ; BPL1MOD
dc.w $010a,$0000 ; BPL2MOD
; --- Bitplane pointer ---
dc.w $00e0 ; BPL1PTH
bplpth_val: dc.w $0000
dc.w $00e2 ; BPL1PTL
bplptl_val: dc.w $0000
; --- Playfield colours ---
dc.w $0180,$0000 ; Colour 0: transparent (shows Copper bg)
dc.w $0182,COLOUR_CAR ; Colour 1: red (car from bitplane)
; --- Sprite palette ---
dc.w $01a2,COLOUR_FROG
dc.w $01a4,COLOUR_EYES
dc.w $01a6,COLOUR_OUTLINE
; --- Sprite 0 pointer ---
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
; BOTTOM BORDER
dc.w $fc07,$fffe
dc.w COLOR00,COLOUR_BLACK
dc.w $ffff,$fffe
;══════════════════════════════════════════════════════════════════════════════
; GRAPHICS DATA
;══════════════════════════════════════════════════════════════════════════════
even
car_gfx:
; 32×12 pixel car (2 words wide × 12 lines)
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 ; Wheels
dc.w $0000,$0000
;══════════════════════════════════════════════════════════════════════════════
; SPRITE DATA
;══════════════════════════════════════════════════════════════════════════════
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 (Chip RAM)
;══════════════════════════════════════════════════════════════════════════════
section chipbss,bss_c
even
screen_plane:
ds.b PLANE_SIZE
Build It
vasmm68k_mot -Fhunkexe -kick1hunks -o signal signal.asm
What’s Next
We have one car. In Unit 7, we’ll add collision detection—the frog needs to die when it hits the car!
What Changed
| 1 | 1 | ;══════════════════════════════════════════════════════════════════════════════ | |
| 2 | 2 | ; SIGNAL - A Frogger-style game for the Commodore Amiga | |
| 3 | - | ; Unit 5: Grid-Based Movement | |
| 3 | + | ; Unit 6: The Blitter Introduction | |
| 4 | 4 | ; | |
| 5 | - | ; Real Frogger doesn't glide—it hops. Each press moves the frog exactly one | |
| 6 | - | ; grid cell, with smooth animation over 8 frames. This unit implements that | |
| 7 | - | ; distinctive movement feel using a simple state machine. | |
| 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. | |
| 8 | + | ; | |
| 9 | + | ; This unit introduces the Blitter with a simple demonstration: one car | |
| 10 | + | ; driving across the road, drawn using the Blitter instead of sprites. | |
| 8 | 11 | ;══════════════════════════════════════════════════════════════════════════════ | |
| 9 | 12 | | |
| 10 | 13 | ;══════════════════════════════════════════════════════════════════════════════ | |
| 11 | - | ; GRID CONSTANTS | |
| 14 | + | ; CONSTANTS | |
| 12 | 15 | ;══════════════════════════════════════════════════════════════════════════════ | |
| 13 | - | | |
| 14 | - | ; Grid dimensions | |
| 15 | - | GRID_COLS equ 20 ; 20 columns (320 ÷ 16) | |
| 16 | - | GRID_ROWS equ 13 ; 13 rows (matching our playfield) | |
| 17 | - | CELL_SIZE equ 16 ; Each cell is 16×16 pixels | |
| 18 | - | | |
| 19 | - | ; Grid origin (pixel coordinates of top-left cell) | |
| 20 | - | GRID_ORIGIN_X equ 48 ; Left margin | |
| 21 | - | GRID_ORIGIN_Y equ 44 ; Top of playfield | |
| 22 | - | | |
| 23 | - | ; Starting position (grid coordinates) | |
| 24 | - | START_GRID_X equ 9 ; Middle column (0-19) | |
| 25 | - | START_GRID_Y equ 12 ; Bottom row (0-12) | |
| 26 | 16 | | |
| 27 | - | ; Animation timing | |
| 28 | - | HOP_FRAMES equ 8 ; Animation lasts 8 frames | |
| 29 | - | PIXELS_PER_FRAME equ 2 ; 2 pixels per frame × 8 frames = 16 pixels | |
| 17 | + | ; 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 | |
| 30 | 21 | | |
| 31 | - | ; Movement states | |
| 32 | - | STATE_IDLE equ 0 ; Waiting for input | |
| 33 | - | STATE_HOPPING equ 1 ; Currently animating a hop | |
| 22 | + | ; Grid constants (from Unit 5) | |
| 23 | + | GRID_COLS equ 20 | |
| 24 | + | GRID_ROWS equ 13 | |
| 25 | + | CELL_SIZE equ 16 | |
| 26 | + | GRID_ORIGIN_X equ 48 | |
| 27 | + | GRID_ORIGIN_Y equ 44 | |
| 28 | + | START_GRID_X equ 9 | |
| 29 | + | START_GRID_Y equ 12 | |
| 34 | 30 | | |
| 35 | - | ; Direction codes | |
| 31 | + | ; Animation | |
| 32 | + | HOP_FRAMES equ 8 | |
| 33 | + | PIXELS_PER_FRAME equ 2 | |
| 34 | + | STATE_IDLE equ 0 | |
| 35 | + | STATE_HOPPING equ 1 | |
| 36 | 36 | DIR_UP equ 0 | |
| 37 | 37 | DIR_DOWN equ 1 | |
| 38 | 38 | DIR_LEFT equ 2 | |
| 39 | 39 | DIR_RIGHT equ 3 | |
| 40 | - | | |
| 41 | 40 | FROG_HEIGHT equ 16 | |
| 41 | + | | |
| 42 | + | ; Car constants | |
| 43 | + | 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 | 47 | | |
| 43 | 48 | ; Zone colours | |
| 44 | 49 | COLOUR_BLACK equ $0000 | |
| ... | |||
| 52 | 57 | COLOUR_START equ $0262 | |
| 53 | 58 | COLOUR_START_LIT equ $0373 | |
| 54 | 59 | | |
| 55 | - | ; Sprite palette | |
| 60 | + | ; Sprite and playfield colours | |
| 56 | 61 | COLOUR_FROG equ $00f0 | |
| 57 | 62 | COLOUR_EYES equ $0ff0 | |
| 58 | 63 | COLOUR_OUTLINE equ $0000 | |
| 64 | + | COLOUR_CAR equ $0f00 ; Red car on bitplane | |
| 59 | 65 | | |
| 60 | 66 | ;══════════════════════════════════════════════════════════════════════════════ | |
| 61 | 67 | ; HARDWARE REGISTERS | |
| ... | |||
| 64 | 70 | CUSTOM equ $dff000 | |
| 65 | 71 | | |
| 66 | 72 | DMACONR equ $002 | |
| 67 | - | DMACON equ $096 | |
| 68 | - | INTENA equ $09a | |
| 69 | - | INTREQ equ $09c | |
| 70 | 73 | VPOSR equ $004 | |
| 71 | 74 | JOY1DAT equ $00c | |
| 75 | + | | |
| 76 | + | ; Blitter registers | |
| 77 | + | BLTCON0 equ $040 | |
| 78 | + | BLTCON1 equ $042 | |
| 79 | + | BLTAFWM equ $044 | |
| 80 | + | BLTALWM equ $046 | |
| 81 | + | BLTCPTH equ $048 | |
| 82 | + | BLTBPTH equ $04c | |
| 83 | + | BLTAPTH equ $050 | |
| 84 | + | BLTDPTH equ $054 | |
| 85 | + | BLTSIZE equ $058 | |
| 86 | + | BLTCMOD equ $060 | |
| 87 | + | BLTBMOD equ $062 | |
| 88 | + | BLTAMOD equ $064 | |
| 89 | + | BLTDMOD equ $066 | |
| 90 | + | | |
| 72 | 91 | COP1LC equ $080 | |
| 73 | 92 | COPJMP1 equ $088 | |
| 93 | + | DMACON equ $096 | |
| 94 | + | INTENA equ $09a | |
| 95 | + | INTREQ equ $09c | |
| 74 | 96 | COLOR00 equ $180 | |
| 75 | 97 | SPR0PTH equ $120 | |
| 76 | 98 | SPR0PTL equ $122 | |
| ... | |||
| 89 | 111 | move.w #$7fff,INTREQ(a5) | |
| 90 | 112 | move.w #$7fff,DMACON(a5) | |
| 91 | 113 | | |
| 92 | - | ; --- Initialise game state --- | |
| 114 | + | ; --- Clear screen --- | |
| 115 | + | bsr clear_screen | |
| 116 | + | | |
| 117 | + | ; --- Set up bitplane pointer in copper list --- | |
| 118 | + | lea screen_plane,a0 | |
| 119 | + | move.l a0,d0 | |
| 120 | + | swap d0 | |
| 121 | + | lea bplpth_val,a1 | |
| 122 | + | move.w d0,(a1) | |
| 123 | + | swap d0 | |
| 124 | + | lea bplptl_val,a1 | |
| 125 | + | move.w d0,(a1) | |
| 126 | + | | |
| 127 | + | ; --- Initialise frog --- | |
| 93 | 128 | move.w #START_GRID_X,frog_grid_x | |
| 94 | 129 | move.w #START_GRID_Y,frog_grid_y | |
| 95 | 130 | move.w #STATE_IDLE,frog_state | |
| 96 | - | move.w #0,frog_anim_frame | |
| 97 | 131 | clr.w joy_prev | |
| 98 | - | | |
| 99 | - | ; --- Calculate initial pixel position --- | |
| 100 | 132 | bsr grid_to_pixels | |
| 101 | 133 | | |
| 102 | - | ; --- Set sprite pointer in copper list --- | |
| 134 | + | ; --- Set sprite pointer --- | |
| 103 | 135 | lea frog_data,a0 | |
| 104 | 136 | move.l a0,d0 | |
| 105 | 137 | swap d0 | |
| ... | |||
| 109 | 141 | lea sprptl_val,a1 | |
| 110 | 142 | move.w d0,(a1) | |
| 111 | 143 | | |
| 112 | - | ; --- Update sprite control words --- | |
| 113 | 144 | bsr update_sprite | |
| 145 | + | | |
| 146 | + | ; --- Initialise car --- | |
| 147 | + | clr.w car_x | |
| 114 | 148 | | |
| 115 | 149 | ; --- Install copper list --- | |
| 116 | 150 | lea copperlist,a0 | |
| 117 | 151 | move.l a0,COP1LC(a5) | |
| 118 | 152 | move.w d0,COPJMP1(a5) | |
| 119 | 153 | | |
| 120 | - | ; --- Enable DMA --- | |
| 121 | - | move.w #$83a0,DMACON(a5) | |
| 154 | + | ; --- Enable DMA (including Blitter) --- | |
| 155 | + | move.w #$87e0,DMACON(a5) ; Master + blit + copper + bitplane + sprite | |
| 122 | 156 | | |
| 123 | 157 | ;══════════════════════════════════════════════════════════════════════════════ | |
| 124 | 158 | ; MAIN LOOP | |
| ... | |||
| 126 | 160 | | |
| 127 | 161 | mainloop: | |
| 128 | 162 | bsr wait_vblank | |
| 163 | + | | |
| 164 | + | ; --- Update frog --- | |
| 129 | 165 | bsr update_frog | |
| 130 | 166 | bsr update_sprite | |
| 131 | - | bra.s mainloop | |
| 167 | + | | |
| 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 | |
| 172 | + | | |
| 173 | + | bra mainloop | |
| 132 | 174 | | |
| 133 | 175 | ;══════════════════════════════════════════════════════════════════════════════ | |
| 134 | - | ; UPDATE_FROG - Main frog logic | |
| 176 | + | ; BLITTER ROUTINES | |
| 135 | 177 | ;══════════════════════════════════════════════════════════════════════════════ | |
| 136 | - | ; State machine: | |
| 137 | - | ; IDLE: Check for joystick input, start hop if valid | |
| 138 | - | ; HOPPING: Animate position, return to IDLE when complete | |
| 178 | + | | |
| 179 | + | ;------------------------------------------------------------------------------ | |
| 180 | + | ; WAIT_BLIT - Wait for Blitter to finish | |
| 181 | + | ;------------------------------------------------------------------------------ | |
| 182 | + | wait_blit: | |
| 183 | + | btst #6,DMACONR(a5) ; Test BBUSY flag | |
| 184 | + | bne.s wait_blit ; Loop while busy | |
| 185 | + | rts | |
| 186 | + | | |
| 187 | + | ;------------------------------------------------------------------------------ | |
| 188 | + | ; CLEAR_SCREEN - Clear the screen buffer using Blitter | |
| 189 | + | ;------------------------------------------------------------------------------ | |
| 190 | + | clear_screen: | |
| 191 | + | bsr.s wait_blit | |
| 192 | + | | |
| 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) | |
| 197 | + | move.w #0,BLTCON1(a5) | |
| 198 | + | move.w #(SCREEN_H<<6)|SCREEN_W/2,BLTSIZE(a5) ; Trigger! | |
| 199 | + | | |
| 200 | + | rts | |
| 201 | + | | |
| 202 | + | ;------------------------------------------------------------------------------ | |
| 203 | + | ; ERASE_CAR - Clear car at current position | |
| 204 | + | ;------------------------------------------------------------------------------ | |
| 205 | + | erase_car: | |
| 206 | + | bsr.s wait_blit | |
| 207 | + | | |
| 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 | |
| 212 | + | | |
| 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) | |
| 219 | + | | |
| 220 | + | rts | |
| 221 | + | | |
| 222 | + | ;------------------------------------------------------------------------------ | |
| 223 | + | ; DRAW_CAR - Draw car at current position using Blitter | |
| 224 | + | ;------------------------------------------------------------------------------ | |
| 225 | + | draw_car: | |
| 226 | + | bsr.s wait_blit | |
| 227 | + | | |
| 228 | + | ; Calculate screen address | |
| 229 | + | move.w car_x,d0 | |
| 230 | + | move.w #CAR_ROW,d1 | |
| 231 | + | bsr calc_screen_addr ; A0 = destination | |
| 232 | + | | |
| 233 | + | ; Copy A→D: source graphics to screen | |
| 234 | + | move.l #car_gfx,BLTAPTH(a5) | |
| 235 | + | move.l a0,BLTDPTH(a5) | |
| 236 | + | | |
| 237 | + | move.w #$ffff,BLTAFWM(a5) ; No masking | |
| 238 | + | 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 | |
| 242 | + | move.w #0,BLTCON1(a5) | |
| 243 | + | move.w #(CAR_HEIGHT<<6)|CAR_WIDTH,BLTSIZE(a5) ; Trigger! | |
| 244 | + | | |
| 245 | + | rts | |
| 246 | + | | |
| 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 | |
| 252 | + | | |
| 253 | + | calc_screen_addr: | |
| 254 | + | lea screen_plane,a0 | |
| 255 | + | | |
| 256 | + | ; Calculate Y offset: row × CELL_SIZE × SCREEN_W | |
| 257 | + | 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 | |
| 261 | + | add.l d2,a0 | |
| 262 | + | | |
| 263 | + | ; Calculate X offset: pixel_x / 8 (convert to bytes) | |
| 264 | + | move.w d0,d2 | |
| 265 | + | lsr.w #3,d2 ; D2 = X in bytes | |
| 266 | + | ext.l d2 | |
| 267 | + | 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: | |
| 280 | + | rts | |
| 281 | + | | |
| 282 | + | ;══════════════════════════════════════════════════════════════════════════════ | |
| 283 | + | ; FROG ROUTINES (from Unit 5) | |
| 284 | + | ;══════════════════════════════════════════════════════════════════════════════ | |
| 139 | 285 | | |
| 140 | 286 | update_frog: | |
| 141 | 287 | tst.w frog_state | |
| ... | |||
| 143 | 289 | bra .hopping | |
| 144 | 290 | | |
| 145 | 291 | .idle: | |
| 146 | - | ; --- Read joystick with edge detection --- | |
| 147 | 292 | bsr read_joystick_edge | |
| 148 | 293 | tst.w d0 | |
| 149 | - | beq .done ; No new input | |
| 294 | + | beq .done | |
| 150 | 295 | | |
| 151 | - | ; --- Check each direction --- | |
| 152 | - | btst #8,d0 ; Up? | |
| 296 | + | btst #8,d0 | |
| 153 | 297 | beq.s .not_up | |
| 154 | 298 | move.w frog_grid_y,d1 | |
| 155 | 299 | tst.w d1 | |
| 156 | - | beq.s .not_up ; Already at top | |
| 300 | + | beq.s .not_up | |
| 157 | 301 | move.w #DIR_UP,frog_dir | |
| 158 | 302 | bra.s .start_hop | |
| 159 | 303 | .not_up: | |
| 160 | - | btst #0,d0 ; Down? | |
| 304 | + | btst #0,d0 | |
| 161 | 305 | beq.s .not_down | |
| 162 | 306 | move.w frog_grid_y,d1 | |
| 163 | 307 | cmp.w #GRID_ROWS-1,d1 | |
| 164 | - | beq.s .not_down ; Already at bottom | |
| 308 | + | beq.s .not_down | |
| 165 | 309 | move.w #DIR_DOWN,frog_dir | |
| 166 | 310 | bra.s .start_hop | |
| 167 | 311 | .not_down: | |
| 168 | - | btst #9,d0 ; Left? | |
| 312 | + | btst #9,d0 | |
| 169 | 313 | beq.s .not_left | |
| 170 | 314 | move.w frog_grid_x,d1 | |
| 171 | 315 | tst.w d1 | |
| 172 | - | beq.s .not_left ; Already at left | |
| 316 | + | beq.s .not_left | |
| 173 | 317 | move.w #DIR_LEFT,frog_dir | |
| 174 | 318 | bra.s .start_hop | |
| 175 | 319 | .not_left: | |
| 176 | - | btst #1,d0 ; Right? | |
| 320 | + | btst #1,d0 | |
| 177 | 321 | beq .done | |
| 178 | 322 | move.w frog_grid_x,d1 | |
| 179 | 323 | cmp.w #GRID_COLS-1,d1 | |
| 180 | - | beq .done ; Already at right | |
| 324 | + | beq .done | |
| 181 | 325 | move.w #DIR_RIGHT,frog_dir | |
| 182 | - | ; Fall through to start_hop | |
| 183 | 326 | | |
| 184 | 327 | .start_hop: | |
| 185 | - | ; --- Begin hopping animation --- | |
| 186 | 328 | move.w #STATE_HOPPING,frog_state | |
| 187 | - | move.w #0,frog_anim_frame | |
| 329 | + | clr.w frog_anim_frame | |
| 188 | 330 | bra.s .done | |
| 189 | 331 | | |
| 190 | 332 | .hopping: | |
| 191 | - | ; --- Advance animation frame --- | |
| 192 | 333 | addq.w #1,frog_anim_frame | |
| 193 | - | | |
| 194 | - | ; --- Move pixel position based on direction --- | |
| 195 | 334 | move.w frog_dir,d0 | |
| 196 | 335 | | |
| 197 | 336 | cmp.w #DIR_UP,d0 | |
| 198 | 337 | bne.s .hop_not_up | |
| 199 | 338 | sub.w #PIXELS_PER_FRAME,frog_pixel_y | |
| 200 | - | bra.s .check_hop_done | |
| 339 | + | bra.s .check_done | |
| 201 | 340 | .hop_not_up: | |
| 202 | 341 | cmp.w #DIR_DOWN,d0 | |
| 203 | 342 | bne.s .hop_not_down | |
| 204 | 343 | add.w #PIXELS_PER_FRAME,frog_pixel_y | |
| 205 | - | bra.s .check_hop_done | |
| 344 | + | bra.s .check_done | |
| 206 | 345 | .hop_not_down: | |
| 207 | 346 | cmp.w #DIR_LEFT,d0 | |
| 208 | 347 | bne.s .hop_not_left | |
| 209 | 348 | sub.w #PIXELS_PER_FRAME,frog_pixel_x | |
| 210 | - | bra.s .check_hop_done | |
| 349 | + | bra.s .check_done | |
| 211 | 350 | .hop_not_left: | |
| 212 | - | ; Must be RIGHT | |
| 213 | 351 | add.w #PIXELS_PER_FRAME,frog_pixel_x | |
| 214 | 352 | | |
| 215 | - | .check_hop_done: | |
| 216 | - | ; --- Check if animation complete --- | |
| 353 | + | .check_done: | |
| 217 | 354 | cmp.w #HOP_FRAMES,frog_anim_frame | |
| 218 | 355 | blt.s .done | |
| 219 | 356 | | |
| 220 | - | ; --- Hop complete: update grid position --- | |
| 221 | 357 | move.w frog_dir,d0 | |
| 222 | - | | |
| 223 | 358 | cmp.w #DIR_UP,d0 | |
| 224 | 359 | bne.s .end_not_up | |
| 225 | 360 | subq.w #1,frog_grid_y | |
| 226 | - | bra.s .snap_to_grid | |
| 361 | + | bra.s .snap | |
| 227 | 362 | .end_not_up: | |
| 228 | 363 | cmp.w #DIR_DOWN,d0 | |
| 229 | 364 | bne.s .end_not_down | |
| 230 | 365 | addq.w #1,frog_grid_y | |
| 231 | - | bra.s .snap_to_grid | |
| 366 | + | bra.s .snap | |
| 232 | 367 | .end_not_down: | |
| 233 | 368 | cmp.w #DIR_LEFT,d0 | |
| 234 | 369 | bne.s .end_not_left | |
| 235 | 370 | subq.w #1,frog_grid_x | |
| 236 | - | bra.s .snap_to_grid | |
| 371 | + | bra.s .snap | |
| 237 | 372 | .end_not_left: | |
| 238 | - | ; Must be RIGHT | |
| 239 | 373 | addq.w #1,frog_grid_x | |
| 240 | 374 | | |
| 241 | - | .snap_to_grid: | |
| 242 | - | ; --- Snap pixel position to grid (prevents drift) --- | |
| 375 | + | .snap: | |
| 243 | 376 | bsr grid_to_pixels | |
| 244 | - | | |
| 245 | - | ; --- Return to idle state --- | |
| 246 | 377 | move.w #STATE_IDLE,frog_state | |
| 247 | 378 | | |
| 248 | 379 | .done: | |
| 249 | 380 | rts | |
| 250 | - | | |
| 251 | - | ;══════════════════════════════════════════════════════════════════════════════ | |
| 252 | - | ; GRID_TO_PIXELS - Convert grid coordinates to pixel coordinates | |
| 253 | - | ;══════════════════════════════════════════════════════════════════════════════ | |
| 254 | - | ; Input: frog_grid_x, frog_grid_y | |
| 255 | - | ; Output: frog_pixel_x, frog_pixel_y | |
| 256 | 381 | | |
| 257 | 382 | grid_to_pixels: | |
| 258 | 383 | move.w frog_grid_x,d0 | |
| ... | |||
| 264 | 389 | mulu #CELL_SIZE,d0 | |
| 265 | 390 | add.w #GRID_ORIGIN_Y,d0 | |
| 266 | 391 | move.w d0,frog_pixel_y | |
| 267 | - | | |
| 268 | 392 | rts | |
| 269 | - | | |
| 270 | - | ;══════════════════════════════════════════════════════════════════════════════ | |
| 271 | - | ; READ_JOYSTICK_EDGE - Read joystick with edge detection | |
| 272 | - | ;══════════════════════════════════════════════════════════════════════════════ | |
| 273 | - | ; Returns only newly-pressed directions (not held directions) | |
| 274 | - | ; Output: D0 = direction bits (only set on transition from not-pressed) | |
| 275 | 393 | | |
| 276 | 394 | read_joystick_edge: | |
| 277 | - | ; --- Read and decode joystick --- | |
| 278 | 395 | move.w JOY1DAT(a5),d0 | |
| 279 | 396 | move.w d0,d1 | |
| 280 | 397 | lsr.w #1,d1 | |
| 281 | - | eor.w d1,d0 ; D0 = decoded current state | |
| 398 | + | eor.w d1,d0 | |
| 282 | 399 | | |
| 283 | - | ; --- Edge detection: current AND NOT previous --- | |
| 284 | 400 | move.w joy_prev,d1 | |
| 285 | - | not.w d1 ; D1 = NOT previous | |
| 286 | - | and.w d0,d1 ; D1 = newly pressed only | |
| 287 | - | | |
| 288 | - | ; --- Save current state for next frame --- | |
| 401 | + | not.w d1 | |
| 402 | + | and.w d0,d1 | |
| 289 | 403 | move.w d0,joy_prev | |
| 290 | 404 | | |
| 291 | - | move.w d1,d0 ; Return newly pressed in D0 | |
| 405 | + | move.w d1,d0 | |
| 292 | 406 | rts | |
| 293 | 407 | | |
| 294 | 408 | ;══════════════════════════════════════════════════════════════════════════════ | |
| 295 | - | ; SUBROUTINES | |
| 409 | + | ; UTILITY ROUTINES | |
| 296 | 410 | ;══════════════════════════════════════════════════════════════════════════════ | |
| 297 | 411 | | |
| 298 | 412 | wait_vblank: | |
| ... | |||
| 317 | 431 | add.w #FROG_HEIGHT,d0 | |
| 318 | 432 | lsl.w #8,d0 | |
| 319 | 433 | move.w d0,2(a0) | |
| 320 | - | | |
| 321 | 434 | rts | |
| 322 | 435 | | |
| 323 | 436 | ;══════════════════════════════════════════════════════════════════════════════ | |
| 324 | 437 | ; VARIABLES | |
| 325 | 438 | ;══════════════════════════════════════════════════════════════════════════════ | |
| 326 | - | | |
| 327 | - | ; Grid position (which cell the frog occupies) | |
| 328 | - | frog_grid_x: dc.w 9 ; Column (0-19) | |
| 329 | - | frog_grid_y: dc.w 12 ; Row (0-12) | |
| 330 | - | | |
| 331 | - | ; Pixel position (for rendering and animation) | |
| 332 | - | frog_pixel_x: dc.w 0 ; Screen X | |
| 333 | - | frog_pixel_y: dc.w 0 ; Screen Y | |
| 334 | 439 | | |
| 335 | - | ; Movement state machine | |
| 336 | - | frog_state: dc.w 0 ; 0=idle, 1=hopping | |
| 337 | - | frog_dir: dc.w 0 ; Direction of current hop | |
| 338 | - | frog_anim_frame: dc.w 0 ; Current frame of hop animation (0-7) | |
| 440 | + | frog_grid_x: dc.w 9 | |
| 441 | + | frog_grid_y: dc.w 12 | |
| 442 | + | frog_pixel_x: dc.w 0 | |
| 443 | + | frog_pixel_y: dc.w 0 | |
| 444 | + | frog_state: dc.w 0 | |
| 445 | + | frog_dir: dc.w 0 | |
| 446 | + | frog_anim_frame: dc.w 0 | |
| 447 | + | joy_prev: dc.w 0 | |
| 339 | 448 | | |
| 340 | - | ; Input state | |
| 341 | - | joy_prev: dc.w 0 ; Previous joystick state (for edge detection) | |
| 449 | + | car_x: dc.w 0 | |
| 342 | 450 | | |
| 343 | 451 | ;══════════════════════════════════════════════════════════════════════════════ | |
| 344 | 452 | ; COPPER LIST | |
| ... | |||
| 346 | 454 | | |
| 347 | 455 | copperlist: | |
| 348 | 456 | dc.w COLOR00,COLOUR_BLACK | |
| 457 | + | | |
| 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 | |
| 464 | + | | |
| 465 | + | ; --- Bitplane pointer --- | |
| 466 | + | dc.w $00e0 ; BPL1PTH | |
| 467 | + | bplpth_val: dc.w $0000 | |
| 468 | + | dc.w $00e2 ; BPL1PTL | |
| 469 | + | bplptl_val: dc.w $0000 | |
| 470 | + | | |
| 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) | |
| 349 | 474 | | |
| 350 | 475 | ; --- Sprite palette --- | |
| 351 | 476 | dc.w $01a2,COLOUR_FROG | |
| ... | |||
| 407 | 532 | dc.w COLOR00,COLOUR_BLACK | |
| 408 | 533 | | |
| 409 | 534 | dc.w $ffff,$fffe | |
| 535 | + | | |
| 536 | + | ;══════════════════════════════════════════════════════════════════════════════ | |
| 537 | + | ; GRAPHICS DATA | |
| 538 | + | ;══════════════════════════════════════════════════════════════════════════════ | |
| 539 | + | | |
| 540 | + | even | |
| 541 | + | 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 | |
| 554 | + | dc.w $0000,$0000 | |
| 410 | 555 | | |
| 411 | 556 | ;══════════════════════════════════════════════════════════════════════════════ | |
| 412 | 557 | ; SPRITE DATA | |
| ... | |||
| 414 | 559 | | |
| 415 | 560 | even | |
| 416 | 561 | frog_data: | |
| 417 | - | dc.w $ec50,$fc00 ; Control words | |
| 562 | + | dc.w $ec50,$fc00 | |
| 418 | 563 | | |
| 419 | 564 | dc.w $0000,$0000 | |
| 420 | 565 | dc.w $07e0,$0000 | |
| ... | |||
| 434 | 579 | dc.w $0000,$0000 | |
| 435 | 580 | | |
| 436 | 581 | dc.w $0000,$0000 | |
| 582 | + | | |
| 583 | + | ;══════════════════════════════════════════════════════════════════════════════ | |
| 584 | + | ; SCREEN BUFFER (Chip RAM) | |
| 585 | + | ;══════════════════════════════════════════════════════════════════════════════ | |
| 586 | + | | |
| 587 | + | section chipbss,bss_c | |
| 588 | + | | |
| 589 | + | even | |
| 590 | + | screen_plane: | |
| 591 | + | ds.b PLANE_SIZE | |
| 437 | 592 | |