Interactive Sprites: Joystick Control

Moving a sprite automatically is cool, but letting the user control it is what makes a game! This tutorial builds on our animation example by adding joystick support. We'll read input from the joystick port to move our sprite around the screen and use the fire button to trigger a simple explosion effect.

How the Amiga Reads Joysticks

The Amiga reads the state of the standard joystick (connected to Port 1) through one of its custom chips, the CIA (Complex Interface Adapter). Specifically, we read from a register often labelled `JOY0DAT` at address `$DFF00A`. This register gives us a snapshot of the joystick's direction and button states.

We'll read this register during our vertical blank interrupt. This creates a responsive "game loop" where we check for input and update the sprite's position 50 times per second (on a PAL Amiga).

Illustration of an Amiga joystick controlling a sprite

The "Explosion" Effect

How do we make the sprite "explode"? In this example, we'll use a simple but effective trick. We will define two different sets of sprite data: one for the normal ship and another that looks like a burst or explosion. When the fire button is pressed, our interrupt code will simply point the hardware to the explosion sprite data instead of the ship data. This instantly changes the sprite's appearance on screen.

The Complete Code (joystick_sprite.asm):

;-----------------------------------------------------
;  Joystick Controlled Sprite with Explosion
;  - Moves with joystick in Port 1
;  - "Explodes" on fire button press
;-----------------------------------------------------
CUSTOM          equ     $DFF000
INTENA          equ     CUSTOM+$9A
INTREQ          equ     CUSTOM+$9C
DMACON          equ     CUSTOM+$96
SPR0PTH         equ     CUSTOM+$120
SPR0POS         equ     CUSTOM+$140
SPR0CTL         equ     CUSTOM+$142
SPRCOLOR17      equ     CUSTOM+$1A2
SPRCOLOR18      equ     CUSTOM+$1A4
SPRCOLOR19      equ     CUSTOM+$1A6
JOY0DAT         equ     $DFF00A ; Joystick 1 Data
CIAAPRA         equ     $BFE001 ; CIA Port A (for fire button)

EXEC_BASE equ $4 LVL3_INT_VECTOR equ $6C

start: lea CUSTOM,a5 move.l EXEC_BASE,a6 move.l LVL3_INT_VECTOR(a6),old_int_vector(pc) move.w #$C000,INTENA(a5)

.waitvb: move.w $DFF005,d0 btst #8,d0 beq.s .waitvb

lea     vblank_interrupt(pc),a0
move.l  a0,LVL3_INT_VECTOR(a6)

lea     sprite_ship(pc),a0
move.l  a0,SPR0PTH(a5)

move.w  #$0f80,SPRCOLOR17(a5)
move.w  #$0ff0,SPRCOLOR18(a5)
move.w  #$0fff,SPRCOLOR19(a5)

move.w  #$8100,DMACON(a5)
move.w  #$C020,INTENA(a5)

forever_loop: btst #6,CIAAPRA bne.s forever_loop

exit: move.w #$C020,INTENA(a5) move.l old_int_vector(pc),LVL3_INT_VECTOR(a6) move.w #$7FFF,DMACON(a5) rts

vblank_interrupt: movem.l d0-d2/a0-a1/a5,-(sp) lea CUSTOM,a5

; --- Read Joystick ---
move.w  JOY0DAT(a5),d0
move.w  d0,d1

; Vertical Movement (Y-axis)
btst    #0,d0
bne.s   .no_down
addq.w  #1,sprite_y(pc)

.no_down: btst #1,d0 bne.s .no_up subq.w #1,sprite_y(pc) .no_up:

; Horizontal Movement (X-axis)
lsr.w   #8,d1
btst    #0,d1
bne.s   .no_right
addq.w  #1,sprite_x(pc)

.no_right: btst #1,d1 bne.s .no_left subq.w #1,sprite_x(pc) .no_left:

; --- Check Fire Button ---
btst    #6,CIAAPRA
bne.s   .no_fire
lea     sprite_explosion(pc),a0 ; Point to explosion data
move.l  a0,SPR0PTH(a5)
bra.s   .update_pos

.no_fire: lea sprite_ship(pc),a0 ; Point back to ship data move.l a0,SPR0PTH(a5)

.update_pos: move.w sprite_y(pc),d1 move.w sprite_x(pc),d2 lsl.w #8,d1 add.b d2,d1 move.w d1,SPR0POS(a5)

move.w  sprite_y(pc),d1
add.w   #16,d1 ; Sprite height is 16 lines
lsl.w   #8,d1
add.b   d2,d1
move.w  d1,SPR0CTL(a5)

move.w  #$0020,INTREQ(a5)
move.w  #$0020,INTREQ(a5)
movem.l (sp)+,d0-d2/a0-a1/a5
rte

; — Data Section — old_int_vector: dc.l 0 sprite_x: dc.w $88 sprite_y: dc.w $64

sprite_ship: dc.w $0000,$0000 ; First two words ignored, set by VBlank dc.w $0180,$0180, dc.w $03C0,$03C0 dc.w $07E0,$07E0, dc.w $0FF0,$0FF0 dc.w $1FF8,$1FF8, dc.w $3FFC,$3FFC dc.w $7FFE,$7FFE, dc.w $FFFF,$FFFF dc.w $FFFF,$FFFF, dc.w $7FFE,$7FFE dc.w $3FFC,$3FFC, dc.w $1FF8,$1FF8 dc.w $0FF0,$0FF0, dc.w $07E0,$07E0 dc.w $03C0,$03C0, dc.w $0180,$0180 dc.w $0000,$0000

sprite_explosion: dc.w $0000,$0000 ; Ignored control words dc.w $1008,$1008, dc.w $4892,$2442 dc.w $2442,$4892, dc.w $9004,$8221 dc.w $9004,$4118, dc.w $27C2,$4118 dc.w $13C8,$8221, dc.w $0FF0,$0FF0 dc.w $0FF0,$0FF0, dc.w $13C8,$8221 dc.w $27C2,$4118, dc.w $9004,$4118 dc.w $9004,$8221, dc.w $2442,$4892 dc.w $4892,$2442, dc.w $1008,$1008 dc.w $0000,$0000

How to Compile and Run on macOS

  1. Save the Code: Save the complete code above into a file named `joystick_sprite.asm`.
  2. Assemble: Open your Terminal, navigate to the folder where you saved the file, and run: `vasmm68k_mot -Fhunk -o joystick_sprite joystick_sprite.asm`
  3. Set up Emulator: Make sure your joystick is enabled in the emulator's input settings (usually for Port 1). Mount the folder containing your new `joystick_sprite` executable as a hard drive (e.g., `DH0:`).
  4. Run in Emulator: Boot into Workbench, open the Shell, and run your program by typing `joystick_sprite`.
  5. See the Result: You can now move the sprite with the joystick. Pressing the fire button will change it to an explosion pattern, and releasing it will change it back to the ship. Click the left mouse button to exit.