Now that we can create and colour detailed sprites, the next step is to bring them to life through movement. On the Tatung Einstein, sprite positions can be easily changed by updating their X and Y coordinates in memory, allowing them to move smoothly across the screen.
In this lesson, we’ll build on the previous example by making our saucer (or smiley) sprite move in four directions using keyboard input. The program uses the ZKSCAN routine to detect key presses and updates the sprite’s position accordingly. A simple delay loop is also introduced to control speed, preventing the sprite from moving too quickly.
This forms the foundation for player-controlled animation, an essential part of any interactive game.
The key to sprite movement lies in changing the X and Y coordinates stored for the sprite. The program checks for keyboard input, adjusts the relevant coordinate, and redraws the sprite at the new position.
Here’s how it works:
CheckKeys routine detects which key has been pressed (W, A, S, or D) and updates the position accordingly.Delay subroutine creates a short pause between movements, slowing down the animation to a smooth and controllable speed.Without this delay, the sprite would move too quickly to see individual steps because the Z80 executes instructions far faster than the screen refresh rate.
The following code moves a sprite in all four directions using the W, A, S, and D keys. It also demonstrates how a simple delay loop can make movement visually smooth and manageable.
ORG 256
; -----------------------
; Enable Graphics Mode II
; ----------------------
; Set Register 0 values
LD A,2
OUT (9),A
LD A,128
OUT (9),A
; Set Register 1 values
LD A,224
OUT (9),A
LD A,129
OUT (9),A
; ---------------------------
; Set Foreground / Background
; ---------------------------
LD A, 240 ; 11110000 (white foreground, black background)
OUT (9), A ; Send to Port 9
LD A, 135 ; 128 (bit 7 set) + 7 (register 7)
OUT (9), A ; Send to Port 9
; -----------------
; Write Blank Tiles
; -----------------
; Top Third Blank Tile
LD A, 0 ; Load zero into A register
OUT (9), A ; Send to Port 9
LD A, 64 ; Load 64 into A register
OUT (9), A ; Send to Port 9
LD A, 0 ; Load 170 into A
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
; Middle Third Blank Tile
LD A, 0 ; Load zero into A register
OUT (9), A ; Send to Port 9
LD A, 72 ; Load 72 into A register
OUT (9), A ; Send to Port 9
LD A, 0 ; Load 170 into A
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
; Bottom Third Blank Tile
LD A, 0 ; Load zero into A register
OUT (9), A ; Send to Port 9
LD A, 80 ; Load 80 into A register
OUT (9), A ; Send to Port 9
LD A, 0 ; Load 170 into A
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
OUT (8), A ; Send to Port 8
; --------------------------------
; Clear Name Table with Blank Tile
; --------------------------------
LD A, 0 ; Low Byte
OUT (9), A ; Send to Port 9
LD A, 120 ; High Byte
OUT (9), A ; Send to Port 9
LD BC, 768 ; Set Counter for table address space
ClearLoop:
LD A, 0 ; Load zero into A register
OUT (8), A ; Send to Port 8
DEC BC ; Decrement BC Counter
LD A, B ; Load B into A register
OR C ; Bitwise OR to compare
JR NZ, ClearLoop ; Jump to start unless BC is zero
; ---------------------
; Define Sprite Pattern
; ---------------------
LD A,0
OUT (9),A
LD A,88
OUT (9),A
LD A,%00001111 : OUT (8),A
LD A,%00011111 : OUT (8),A
LD A,%00111000 : OUT (8),A
LD A,%11111000 : OUT (8),A
LD A,%00111111 : OUT (8),A
LD A,%00011111 : OUT (8),A
LD A,%00001111 : OUT (8),A
LD A,%00000000 : OUT (8),A
LD A,%11110000 : OUT (8),A
LD A,%11111000 : OUT (8),A
LD A,%00011100 : OUT (8),A
LD A,%00011111 : OUT (8),A
LD A,%11111100 : OUT (8),A
LD A,%11111000 : OUT (8),A
LD A,%11110000 : OUT (8),A
LD A,%00000000 : OUT (8),A
LD A,%00000000 : OUT (8),A
LD A,%00000000 : OUT (8),A
LD A,%00000000 : OUT (8),A
LD A,%00000111 : OUT (8),A
LD A,%11000000 : OUT (8),A
LD A,%00111111 : OUT (8),A
LD A,%00011111 : OUT (8),A
LD A,%00001111 : OUT (8),A
LD A,%00000000 : OUT (8),A
LD A,%00000000 : OUT (8),A
LD A,%00000000 : OUT (8),A
LD A,%11100000 : OUT (8),A
LD A,%00000011 : OUT (8),A
LD A,%11111100 : OUT (8),A
LD A,%11111000 : OUT (8),A
LD A,%11110000 : OUT (8),A
; ------------------------
; Define Sprite Attributes
; ------------------------
UpdateSprite:
LD A,0
OUT (9),A
LD A,123
OUT (9),A
; Sprite 0
LD A,(SpriteY)
OUT (8),A
LD A,(SpriteX)
OUT (8),A
LD A,0
OUT (8),A
LD A,15
OUT (8),A
; Sprite 1
LD A,(SpriteY)
OUT (8),A
LD A,(SpriteX)
ADD A, 8
OUT (8),A
LD A,1
OUT (8),A
LD A,15
OUT (8),A
; Sprite 2
LD A,(SpriteY)
OUT (8),A
LD A,(SpriteX)
OUT (8),A
LD A,2
OUT (8),A
LD A,14
OUT (8),A
; Sprite 3
LD A,(SpriteY)
OUT (8),A
LD A,(SpriteX)
ADD A, 8
OUT (8),A
LD A,3
OUT (8),A
LD A,14
OUT (8),A
CheckKeys:
CALL Delay
RST 8
DEFB 181
CP 0 ; No Key Pressed
JR Z, MainLoop
; W Pressed
CP 87
JR Z, MoveUp
; A Pressed
CP 65
JR Z, MoveLeft
; S Pressed
CP 83
JR Z, MoveDown
; D Pressed
CP 68
JR Z, MoveRight
RET
MoveUp:
LD A, (SpriteY)
DEC A
LD (SpriteY), A
JR MainLoop
MoveLeft:
LD A, (SpriteX)
DEC A
LD (SpriteX), A
JR MainLoop
MoveDown:
LD A, (SpriteY)
INC A
LD (SpriteY), A
JR MainLoop
MoveRight:
LD A, (SpriteX)
INC A
LD (SpriteX), A
JR MainLoop
Delay:
LD BC,3000 ; adjust this number for smooth speed
DLoop:
DEC BC
LD A,B
OR C
JR NZ,DLoop
RET
MainLoop:
CALL UpdateSprite
CALL CheckKeys
JR MainLoop
SpriteX:
DEFB 100
SpriteY:
DEFB 120
JP $ ; JP Idefinitely
When you run this program, the sprite can be moved up, down, left, or right using the W, A, S, and D keys. The delay routine ensures smooth, visible movement at a controlled speed.
Try adjusting the value in the Delay subroutine (LD BC,3000) to make the sprite move faster or slower.
The Tatung Einstein’s Z80 processor runs extremely quickly compared to the visible frame rate, so without delay control, sprites would jump instantly across the screen. By using a delay loop, you can finely tune the movement speed, keeping gameplay smooth and natural.
The lesson also introduces an important concept about keyboard handling. The ZKSCAN call is convenient but limited — it can only register one key press at a time. This means you can’t press two movement keys together (for diagonal movement, for instance).
A more advanced approach, called keyboard matrix scanning, overcomes this limitation. It allows multiple keys to be detected simultaneously and will be explored in a later lesson.
Delay routine controls the speed of movement.ZKSCAN is simple but supports only one key at a time.