C64 Workshop Part 3

Sprites and Joystick Control

In Part 2, we learned how to create graphics using characters and colours in screen memory. Now we’ll explore sprites—hardware-accelerated movable objects—and learn how to control them with a joystick.

You can use your drawing from Part 2 as a background, if applicable!

Objective

Create a program that displays a custom sprite on screen (on top of a background) and allows the user to move it around using joystick input.

Requirements

Bonus


Understanding Sprites

Sprites are movable 24×21 pixel objects that the VIC-II graphics chip handles independently from the character screen. The C64 has 8 hardware sprites (numbered 0-7). Each sprite can be positioned anywhere on screen and has its own colour.

Sprite Shape Data: Each sprite is defined by 63 bytes (24×21 pixels ÷ 8 pixels per byte). Each bit in these bytes represents one pixel—1 for visible, 0 for transparent.

Sprite Pointers: Tell the VIC-II chip where to find sprite shape data. Located at addresses 2040-2047 (one byte per sprite). The pointer value × 64 gives the actual data address.

Sprite Position: Each sprite has X and Y coordinates. Sprite 0’s position is controlled by addresses 53248 (X) and 53249 (Y).

Sprite Enable: Set bit 0 of address 53269 to enable sprite 0, bit 1 for sprite 1, etc. To enable sprite 0: POKE 53269,1

Sprite Colour: Sprite 0’s colour is at address 53287. Values 0-15 (same as character colours).


Understanding Joystick Input

The C64 reads joystick state from memory address 56320 (joystick port 2, the standard gaming port). Each bit represents a different direction or button. When a direction is pressed or the button is pushed, that bit becomes 0.

Joystick Port 2: Address 56320

When nothing is pressed, the value is 127. When you press a direction or fire, the corresponding bit(s) are subtracted from 127.

Joystick Input Demonstrator:

Click buttons to see how the joystick byte changes:

Binary:
01111111
Decimal:
127
BASIC Code:
J=PEEK(56320)
J will be 127

Resources / Reference

Designing Sprite Data

The easiest way to design sprites is on graph paper (24 columns × 21 rows). Mark filled pixels with 1, empty pixels with 0.

Each sprite is 24 pixels wide × 21 pixels tall. Since each byte represents 8 pixels, we need 3 bytes per row × 21 rows = 63 bytes total.

Sprite Editor:

Click pixels to toggle them on/off, then click Generate to get your DATA statements:

Example - Simple Face:

    111111111111111111111111  (row 0: all black)
    111111111111111111111111
    111111111111111111111111
    111111111111111111111111
    111111111111111111111111
    111111111111111111111111
    111000000111111000000111  (row 6: eyes)
    111000000111111000000111
    111111111111111111111111
    111111111111111111111111
    111111111111111111111111
    111111111111111111111111
    111111111111111111111111
    111111111111111111111111
    111000000000000000000111  (row 14: mouth)
    111100000000000000011111
    111111111111111111111111
    111111111111111111111111
    111111111111111111111111
    111111111111111111111111
    111111111111111111111111

Each group of 8 bits becomes one DATA byte. For the face’s eye row: 11100000 = 224, 01111110 = 126, 00000111 = 7

Setting Up a Sprite

Step 1: Store sprite data in memory

Sprite data must be stored at an address divisible by 64. Recommended address for BASIC programs: 8192 ($2000 in hex).

Step 2: Set the sprite pointer

Sprite pointers are at addresses 2040-2047 (one for each sprite). The pointer value is your sprite data address divided by 64.

For sprite data at 8192: POKE 2040, 128 (since 8192/64 = 128)

10 POKE 2040, 128

Step 3: Set sprite colour:

20 POKE 53287, 1

Sets sprite 0 to white (colour value 1).

Step 4: Enable the sprite:

30 POKE 53269, 1

Turns on sprite 0 (sets bit 0). Use value 3 for sprites 0 and 1, value 7 for sprites 0-2, etc.

Step 5: Position the sprite:

40 POKE 53248, 100: POKE 53249, 100

Sets sprite 0’s X position to 100 and Y position to 100.

Loading Sprite Shape Data

Use DATA statements and READ to load your 63-byte sprite design into memory at address 8192 (where the VIC-II will look for it).

How it works:

  1. DATA statements can be anywhere in your program - they’re skipped during execution
  2. BASIC maintains an internal pointer that tracks your position in DATA statements (ordered by line number)
  3. Each READ gets the next value from DATA and advances the pointer
  4. POKE writes each byte to memory address 8192+I
  5. After 63 READs, your sprite data is in memory at 8192-8254
10 REM LOAD SPRITE DATA INTO MEMORY AT 8192
20 FOR I=0 TO 62
30 READ A: REM GET NEXT BYTE FROM DATA
40 POKE 8192+I,A: REM WRITE IT TO MEMORY
50 NEXT I
60 REM NOW SPRITE DATA IS AT ADDRESS 8192
1000 REM FACE SPRITE DATA (63 BYTES)
1010 DATA 255,255,255,255,255,255,255,255,255
1020 DATA 255,255,255,255,255,255,255,255,255
1030 DATA 224,126,7,224,126,7,255,255,255
1040 DATA 255,255,255,255,255,255,255,255,255
1050 DATA 255,255,255,255,255,255,224,0,7
1060 DATA 240,0,31,255,255,255,255,255,255
1070 DATA 255,255,255,255,255,255,255,255,255

Reading the Joystick

Read the joystick state from memory address 56320:

10 J=PEEK(56320)

Check for specific directions by testing bits:

20 IF (J AND 1)=0 THEN PRINT "UP"
30 IF (J AND 2)=0 THEN PRINT "DOWN"
40 IF (J AND 4)=0 THEN PRINT "LEFT"
50 IF (J AND 8)=0 THEN PRINT "RIGHT"
60 IF (J AND 16)=0 THEN PRINT "FIRE"

The AND operator checks if a specific bit is set. When that direction/button is active, the bit is 0.

Moving the Sprite

Store current position in variables and modify based on joystick:

100 X=100: Y=100: REM INITIAL POSITION
110 J=PEEK(56320)
120 IF (J AND 1)=0 THEN Y=Y-1: REM UP
130 IF (J AND 2)=0 THEN Y=Y+1: REM DOWN
140 IF (J AND 4)=0 THEN X=X-1: REM LEFT
150 IF (J AND 8)=0 THEN X=X+1: REM RIGHT
160 POKE 53248,X: POKE 53249,Y
170 GOTO 110

Boundary Checking

Keep sprite on screen by limiting X and Y values:

100 REM X RANGE: 24-343, Y RANGE: 50-249
110 IF X<24 THEN X=24
120 IF X>343 THEN X=343
130 IF Y<50 THEN Y=50
140 IF Y>249 THEN Y=249

The C64’s visible sprite area extends beyond the 320×200 screen to approximately X: 24-343, Y: 50-249.

AND Operator

The AND operator performs bitwise AND operation. Used for testing individual bits in the joystick byte.

Example:

10 A=5: REM BINARY: 00000101
20 B=1: REM BINARY: 00000001
30 PRINT A AND B: REM RESULT: 1
40 PRINT A AND 2: REM RESULT: 0

Each bit is compared: if both are 1, result is 1. If either is 0, result is 0.

For joystick: (PEEK(56320) AND 1)=0 tests if bit 0 (UP) is pressed.


Sprite Register Quick Reference

Register Address Purpose
Sprite 0 X Position 53248 Horizontal position (0-255, extended with 53264)
Sprite 0 Y Position 53249 Vertical position (0-255)
Sprite Enable 53269 Bit N enables sprite N (1=on, 0=off)
Sprite 0 Colour 53287 Colour value 0-15
Sprite Pointer 0 2040 Points to sprite data (value × 64 = address)
Sprite Multicolour Mode 53276 Bit N enables multicolour for sprite N
Sprite Expand X 53277 Bit N doubles width of sprite N
Sprite Expand Y 53271 Bit N doubles height of sprite N

All Sprite Pointers and Colours

Sprite Pointer X Pos Y Pos Colour
0 2040 53248 53249 53287
1 2041 53250 53251 53288
2 2042 53252 53253 53289
3 2043 53254 53255 53290
4 2044 53256 53257 53291
5 2045 53258 53259 53292
6 2046 53260 53261 53293
7 2047 53262 53263 53294

Joystick Port 2 Values

Input Address Bit Check Value Example
Up 56320 0 (J AND 1)=0 IF (J AND 1)=0 THEN Y=Y-1
Down 56320 1 (J AND 2)=0 IF (J AND 2)=0 THEN Y=Y+1
Left 56320 2 (J AND 4)=0 IF (J AND 4)=0 THEN X=X-1
Right 56320 3 (J AND 8)=0 IF (J AND 8)=0 THEN X=X+1
Fire 56320 4 (J AND 16)=0 IF (J AND 16)=0 THEN GOSUB 1000

Note: Joystick port 1 is at address 56321 (use if you have two joysticks).


Example: Minimal Sprite Demo

This example shows the minimum code to display a simple sprite:

10 REM SETUP SPRITE 0
20 GOSUB 1000: REM LOAD SPRITE DATA
30 POKE 2040,128: REM SPRITE POINTER (8192/64=128)
40 POKE 53287,1: REM WHITE COLOUR
50 POKE 53269,1: REM ENABLE SPRITE 0
60 X=160: Y=100: REM CENTER POSITION
170 POKE 53248,X: POKE 53249,Y
1000 REM LOAD SPRITE DATA AT 8192 (128*64)
1010 FOR I=0 TO 62
1020 READ A: POKE 8192+I,A
1030 NEXT I
1040 RETURN
1050 DATA 0,0,0,0,126,0,1,255,128
1060 DATA 3,255,192,7,255,224,15,255,240
1070 DATA 15,255,240,31,255,248,31,255,248
1080 DATA 31,255,248,31,255,248,31,255,248
1090 DATA 15,255,240,15,255,240,7,255,224
1100 DATA 3,255,192,1,255,128,0,126,0
1110 DATA 0,0,0,0,0,0,0,0,0

Tips for Success


Previous: Part 2 - Drawing Graphics | Next: Part 4 - Collision Detection and Sound | Back to Home