Skip to content
Game 1 Unit 5 of 128 1 hr learning time

Creature Walks

The creature moves right each frame. Variables in RAM track position. The main loop updates sprite control words per frame.

4% of Exodus

The creature appeared in Unit 4 but stayed frozen in place. Now it walks. Every frame, the x position increases by one pixel. The sprite slides across the terrain from left to right.

This is the first time the main loop does real work. Until now, the loop just waited for vertical blank and checked the mouse button. Now it also updates the creature’s position and writes new values into the sprite control words.

Exodus Unit 5

The creature walks right across the landscape. It doesn’t know about the terrain yet — it passes over cliffs and gaps as if they weren’t there. Collision comes later.

Variables in RAM

The creature’s position needs to persist between frames. Constants won’t work — the position changes every frame. Instead, two words in memory hold the current x and y:

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

            even
creature_x: dc.w    0
creature_y: dc.w    0

dc.w 0 defines a word (16-bit) in memory, initialised to zero. The labels creature_x and creature_y give these memory locations names. The even directive ensures word alignment — the 68000 requires word and longword accesses to be on even addresses.

At startup, the program writes the starting position into these variables:

            move.w  #CREATURE_START_X,creature_x
            move.w  #CREATURE_START_Y,creature_y

The Main Loop

            ; === Main Loop ===
mainloop:
            ; Wait for vertical blank
            move.l  #$1ff00,d1
.vbwait:
            move.l  VPOSR(a5),d0
            and.l   d1,d0
            bne.s   .vbwait

            ; --- Update creature position ---
            move.w  creature_x,d0
            add.w   #CREATURE_SPEED,d0
            move.w  d0,creature_x

            ; --- Update sprite control words ---
            bsr     update_sprite

            ; --- Check exit ---
            btst    #6,$bfe001
            bne.s   mainloop

Three things happen every frame:

  1. Wait for vertical blank — the same VPOSR polling loop as before. This ensures we update once per frame, not as fast as the CPU can run.

  2. Update position — read creature_x from memory into D0, add the speed, write it back. ADD.W #CREATURE_SPEED,D0 adds one pixel per frame. At 50 frames per second (PAL), the creature crosses the 320-pixel screen in about 6 seconds.

  3. Update spriteBSR update_sprite rewrites the sprite control words using the new position. The DMA controller reads these words next frame and draws the sprite at the updated position.

The update_sprite subroutine is almost identical to set_sprite_pos from Unit 4, but now it reads from the variables instead of using constants.

Why Variables, Not Registers?

The 68000 has eight data registers (D0-D7) — wouldn’t it be faster to keep the position in a register? It would, but registers get destroyed by subroutine calls. draw_rect destroys D0-D5, and update_sprite uses several registers too. Storing the position in memory means it survives any subroutine call.

Later, when there are multiple creatures, each creature’s position will be a pair of words in a data table. RAM variables are the natural pattern.

Experiment: Change the Speed

CREATURE_SPEED      equ 1           ; Try 1, 2, 4
  • 1 — slow walk (6 seconds to cross the screen)
  • 2 — brisk walk (3 seconds)
  • 4 — running (1.5 seconds)

The creature still walks off the right edge of the screen. It doesn’t stop or turn around — there’s no boundary check yet. That’s Unit 6.

The Complete Code

;──────────────────────────────────────────────────────────────
; EXODUS - A terrain puzzle for the Commodore Amiga
; Unit 5: Creature Walks
;
; The creature moves right each frame.
; Variables in RAM track position.
; The main loop updates sprite control words per frame.
;──────────────────────────────────────────────────────────────

;══════════════════════════════════════════════════════════════
; TWEAKABLE VALUES
;══════════════════════════════════════════════════════════════

COLOUR_SKY_DEEP     equ $0016
COLOUR_SKY_UPPER    equ $0038
COLOUR_SKY_MID      equ $005B
COLOUR_SKY_LOWER    equ $007D
COLOUR_SKY_HORIZON  equ $009E

COLOUR_TERRAIN      equ $0741

COLOUR_SPR0_1       equ $0FFF
COLOUR_SPR0_2       equ $0F80
COLOUR_SPR0_3       equ $0000

; Creature starting position (bitplane coordinates)
CREATURE_START_X    equ 20
CREATURE_START_Y    equ 110
CREATURE_SPEED      equ 1           ; Pixels per frame

; Terrain
GROUND_L_X          equ 0
GROUND_L_Y          equ 152
GROUND_L_W          equ 128
GROUND_L_H          equ 104

GROUND_R_X          equ 128
GROUND_R_Y          equ 120
GROUND_R_W          equ 192
GROUND_R_H          equ 136

PLATFORM_X          equ 24
PLATFORM_Y          equ 104
PLATFORM_W          equ 72
PLATFORM_H          equ 8

;══════════════════════════════════════════════════════════════
; DISPLAY CONSTANTS
;══════════════════════════════════════════════════════════════

SCREEN_WIDTH    equ 320
SCREEN_HEIGHT   equ 256
BYTES_PER_ROW   equ SCREEN_WIDTH/8
BITPLANE_SIZE   equ BYTES_PER_ROW*SCREEN_HEIGHT

SPRITE_HEIGHT   equ 12

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

CUSTOM      equ $dff000
DMACON      equ $096
INTENA      equ $09a
INTREQ      equ $09c
COP1LC      equ $080
COPJMP1     equ $088
BPLCON0     equ $100
BPLCON1     equ $102
BPLCON2     equ $104
BPL1MOD     equ $108
DIWSTRT     equ $08e
DIWSTOP     equ $090
DDFSTRT     equ $092
DDFSTOP     equ $094
BPL1PTH     equ $0e0
BPL1PTL     equ $0e2
SPR0PTH     equ $120
SPR0PTL     equ $122
COLOR00     equ $180
COLOR01     equ $182
COLOR17     equ $1a2
COLOR18     equ $1a4
COLOR19     equ $1a6
VPOSR       equ $004

;══════════════════════════════════════════════════════════════
; CODE (Chip RAM)
;══════════════════════════════════════════════════════════════

            section code,code_c

start:
            lea     CUSTOM,a5

            ; --- Take over the machine ---
            move.w  #$7fff,INTENA(a5)
            move.w  #$7fff,INTREQ(a5)
            move.w  #$7fff,DMACON(a5)

            ; --- Initialise creature position ---
            move.w  #CREATURE_START_X,creature_x
            move.w  #CREATURE_START_Y,creature_y

            ; --- Draw terrain into bitplane ---
            move.w  #GROUND_L_X,d0
            move.w  #GROUND_L_Y,d1
            move.w  #GROUND_L_W,d2
            move.w  #GROUND_L_H,d3
            bsr     draw_rect

            move.w  #GROUND_R_X,d0
            move.w  #GROUND_R_Y,d1
            move.w  #GROUND_R_W,d2
            move.w  #GROUND_R_H,d3
            bsr     draw_rect

            move.w  #PLATFORM_X,d0
            move.w  #PLATFORM_Y,d1
            move.w  #PLATFORM_W,d2
            move.w  #PLATFORM_H,d3
            bsr     draw_rect

            ; --- Patch bitplane pointer ---
            lea     bitplane,a0
            move.l  a0,d0
            swap    d0
            lea     bpl1pth_val,a1
            move.w  d0,(a1)
            swap    d0
            lea     bpl1ptl_val,a1
            move.w  d0,(a1)

            ; --- Patch sprite pointer ---
            lea     sprite_data,a0
            move.l  a0,d0
            swap    d0
            lea     spr0pth_val,a1
            move.w  d0,(a1)
            swap    d0
            lea     spr0ptl_val,a1
            move.w  d0,(a1)

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

            ; --- Enable DMA ---
            move.w  #$83a0,DMACON(a5)

            ; === Main Loop ===
mainloop:
            ; Wait for vertical blank
            move.l  #$1ff00,d1
.vbwait:
            move.l  VPOSR(a5),d0
            and.l   d1,d0
            bne.s   .vbwait

            ; --- Update creature position ---
            move.w  creature_x,d0
            add.w   #CREATURE_SPEED,d0
            move.w  d0,creature_x

            ; --- Update sprite control words ---
            bsr     update_sprite

            ; --- Check exit ---
            btst    #6,$bfe001
            bne.s   mainloop

.halt:
            bra.s   .halt

;──────────────────────────────────────────────────────────────
; update_sprite — Write current position into sprite data
;
; Reads creature_x and creature_y variables.
;──────────────────────────────────────────────────────────────
update_sprite:
            lea     sprite_data,a0

            move.w  creature_y,d0
            add.w   #$2c,d0             ; VSTART (display offset)
            move.w  d0,d1
            add.w   #SPRITE_HEIGHT,d1   ; VSTOP

            move.w  creature_x,d2
            add.w   #$80,d2             ; HSTART (display offset)

            ; Word 0: VSTART[7:0] | HSTART[8:1]
            move.b  d0,d3
            lsl.w   #8,d3
            move.w  d2,d4
            lsr.w   #1,d4
            or.b    d4,d3
            move.w  d3,(a0)+

            ; Word 1: VSTOP[7:0] | control bits
            move.b  d1,d3
            lsl.w   #8,d3
            moveq   #0,d4
            btst    #8,d0
            beq.s   .no_vs8
            bset    #2,d4
.no_vs8:
            btst    #8,d1
            beq.s   .no_ve8
            bset    #1,d4
.no_ve8:
            btst    #0,d2
            beq.s   .no_h0
            bset    #0,d4
.no_h0:
            or.b    d4,d3
            move.w  d3,(a0)

            rts

;──────────────────────────────────────────────────────────────
; draw_rect — Fill a byte-aligned rectangle in the bitplane
;──────────────────────────────────────────────────────────────
draw_rect:
            lea     bitplane,a0
            mulu    #BYTES_PER_ROW,d1
            add.l   d1,a0
            lsr.w   #3,d0
            add.w   d0,a0
            lsr.w   #3,d2

            subq.w  #1,d3
.row:
            move.w  d2,d5
            subq.w  #1,d5
            move.l  a0,a1
.col:
            move.b  #$ff,(a1)+
            dbra    d5,.col

            add.w   #BYTES_PER_ROW,a0
            dbra    d3,.row
            rts

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

copperlist:
            dc.w    DIWSTRT,$2c81
            dc.w    DIWSTOP,$2cc1
            dc.w    DDFSTRT,$0038
            dc.w    DDFSTOP,$00d0

            dc.w    BPLCON0,$1200
            dc.w    BPLCON1,$0000
            dc.w    BPLCON2,$0000
            dc.w    BPL1MOD,$0000

            dc.w    BPL1PTH
bpl1pth_val:
            dc.w    $0000
            dc.w    BPL1PTL
bpl1ptl_val:
            dc.w    $0000

            dc.w    SPR0PTH
spr0pth_val:
            dc.w    $0000
            dc.w    SPR0PTL
spr0ptl_val:
            dc.w    $0000

            dc.w    COLOR00,COLOUR_SKY_DEEP
            dc.w    COLOR01,COLOUR_TERRAIN
            dc.w    COLOR17,COLOUR_SPR0_1
            dc.w    COLOR18,COLOUR_SPR0_2
            dc.w    COLOR19,COLOUR_SPR0_3

            dc.w    $3401,$fffe
            dc.w    COLOR00,COLOUR_SKY_UPPER
            dc.w    $4401,$fffe
            dc.w    COLOR00,COLOUR_SKY_MID
            dc.w    $5401,$fffe
            dc.w    COLOR00,COLOUR_SKY_LOWER
            dc.w    $6001,$fffe
            dc.w    COLOR00,COLOUR_SKY_HORIZON
            dc.w    $6801,$fffe
            dc.w    COLOR00,$0000

            dc.w    $ffff,$fffe

;══════════════════════════════════════════════════════════════
; SPRITE DATA (Chip RAM)
;══════════════════════════════════════════════════════════════

            even
sprite_data:
            dc.w    $0000,$0000         ; Control words (updated per frame)

            dc.w    %0000011111100000,%0000000000000000
            dc.w    %0000111111110000,%0000011111100000
            dc.w    %0001111111111000,%0000111111110000
            dc.w    %0001101110111000,%0001111111111000
            dc.w    %0001111111111000,%0000111111110000
            dc.w    %0000111001110000,%0000011111100000
            dc.w    %0000011111100000,%0000000000000000
            dc.w    %0000111111110000,%0000011111100000
            dc.w    %0001111111111000,%0000111111110000
            dc.w    %0001111111111000,%0000111111110000
            dc.w    %0000110000110000,%0000000000000000
            dc.w    %0000110000110000,%0000000000000000

            dc.w    $0000,$0000         ; End marker

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

            even
creature_x: dc.w    0
creature_y: dc.w    0

;══════════════════════════════════════════════════════════════
; BITPLANE DATA
;══════════════════════════════════════════════════════════════

            even
bitplane:
            dcb.b   BITPLANE_SIZE,0

If It Doesn’t Work

  • Creature doesn’t move? Check that the main loop calls update_sprite after modifying creature_x. If the sprite control words aren’t rewritten, the DMA reads the old position.
  • Creature moves too fast? The vertical blank wait might not be working. Without it, the loop runs thousands of times per frame, moving the creature off-screen instantly.
  • Creature jumps or flickers? Make sure update_sprite runs after the vertical blank wait, not during active display. Writing sprite data while Denise is reading it causes visual glitches.
  • Position resets to zero? Check that creature_x and creature_y are defined with dc.w, not equ. Constants (equ) are assembler-time values; dc.w allocates actual RAM.

What You’ve Learnt

  • RAM variablesdc.w defines a word in memory. The label provides a name for reading and writing the value at runtime.
  • Frame-based movement — adding a fixed amount to the position each frame creates smooth motion. The vertical blank wait ensures exactly one update per frame.
  • Main loop structure — wait for blank, update state, update display. This pattern drives every real-time game.
  • Register vs memory — registers are fast but temporary. Memory variables persist across subroutine calls and frames.

What’s Next

The creature walks off the right edge and never comes back. In Unit 6, a boundary check detects the screen edge and reverses the direction. The creature bounces back and forth across the terrain.