In the previous lesson, we brought our sprite to life with simple animation. Now that the character can walk and look animated, it’s time to let it interact with the environment — by firing projectiles.
Projectiles form the basis of many action games. Whether it’s a fireball, bullet, or energy blast, the concept is the same — an object that moves independently across the screen after being launched by the player.
In this lesson, we’ll build a system that allows our sprite to shoot a projectile whenever the space bar is pressed. The projectile will travel across the screen until it goes off-screen, where it will deactivate automatically.
This introduces important ideas such as independent object motion, activation flags, and basic collision principles.
The projectile system uses similar logic to the moving sprite, but with its own variables and behaviour. The main difference is that projectiles move automatically once fired rather than responding directly to key presses.
Here’s what happens step by step:
A new sprite pattern (for example, a small square or bullet) is stored in video memory just like the walking frames.
Separate memory locations (ProjX, ProjY, and ProjActive) keep track of its position and whether it’s active (visible on-screen).
When the player presses the space bar, the Fire routine checks if a projectile is already active.
ProjActive to 1.SpriteX and SpriteY values so the projectile starts at the character’s position.Once active, the UpdateProjectile routine moves the projectile forward by increasing its X coordinate on every frame.
If the projectile moves beyond the screen boundary, the program calls DeactProj to deactivate it and reset its position off-screen.
The player’s sprite is updated independently of the projectile. Both routines are called in the MainLoop, ensuring the player can move freely while the projectile continues to move automatically.
This creates the illusion of the player firing a moving object across the screen.
The following program expands upon the walking animation example.
In addition to movement and animation, pressing the space bar fires a small projectile that travels across the screen.
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
JP NZ, ClearLoop ; Jump to start unless BC is zero
; ---------------------
; Define Sprite Pattern
; ---------------------
LD A,0
OUT (9),A
LD A,88
OUT (9),A
; Sprite Frame 0
LD A,%00011000 : OUT (8),A
LD A,%00111100 : OUT (8),A
LD A,%00011000 : OUT (8),A
LD A,%00011111 : OUT (8),A
LD A,%00011010 : OUT (8),A
LD A,%00011000 : OUT (8),A
LD A,%00100100 : OUT (8),A
LD A,%01000010 : OUT (8),A
; Sprite Frame 1
LD A,8
OUT (9),A
LD A,88
OUT (9),A
LD A,%00011000 : OUT (8),A
LD A,%00111100 : OUT (8),A
LD A,%00011000 : OUT (8),A
LD A,%00011111 : OUT (8),A
LD A,%00011010 : OUT (8),A
LD A,%00011000 : OUT (8),A
LD A,%00011000 : OUT (8),A
LD A,%00011000 : OUT (8),A
; Sprite Projectile
LD A,16
OUT (9),A
LD A,88
OUT (9),A
LD A,%00000000 : OUT (8),A
LD A,%00000000 : OUT (8),A
LD A,%00000000 : OUT (8),A
LD A,%00111111 : 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
MainLoop:
CALL UpdatePlayerSprite
CALL UpdateProjectile
CALL CheckKeys
JP MainLoop
UpdatePlayerSprite:
LD A,0
OUT (9),A
LD A,123
OUT (9),A
LD A,(SpriteY)
OUT (8),A
LD A,(SpriteX)
OUT (8),A
LD A, (SpriteFrm)
OUT (8),A
LD A,15
OUT (8),A
RET
UpdateProjectile:
LD A,4
OUT (9),A
LD A,123
OUT (9),A
LD A,(ProjY)
OUT (8),A
LD A,(ProjX)
ADD A, 1
LD (ProjX), A
OUT (8),A
LD A, 2
OUT (8),A
LD A,12
OUT (8),A
; Check if off screen
LD A, (ProjX)
CP 249
CALL Z, DeactProj
RET
CheckKeys:
CALL Delay
RST 8
DEFB 181
CP 0 ; No Key Pressed
JP Z, MainLoop
; W Pressed
CP 87
JP Z, MoveUp
; A Pressed
CP 65
JP Z, MoveLeft
; S Pressed
CP 83
JP Z, MoveDown
; D Pressed
CP 68
JP Z, MoveRight
; Space Pressed
CP 32
JP Z, Fire
RET
MoveUp:
LD A, (SpriteY)
DEC A
LD (SpriteY), A
CALL FlipFrame
JP MainLoop
MoveLeft:
LD A, (SpriteX)
DEC A
LD (SpriteX), A
CALL FlipFrame
JP MainLoop
MoveDown:
LD A, (SpriteY)
INC A
LD (SpriteY), A
CALL FlipFrame
JP MainLoop
MoveRight:
LD A, (SpriteX)
INC A
LD (SpriteX), A
CALL FlipFrame
JP MainLoop
Fire:
; Return is Projectile already active
LD A, (ProjActive)
CP 1
RET Z
; Active Projectile
LD A, 1
LD (ProjActive), A
; Set Initial Position
LD A, (SpriteY)
LD (ProjY), A
LD A, (SpriteX)
LD (ProjX), A
RET
Delay:
LD BC,1000 ; adjust this number for smooth speed
DLoop:
DEC BC
LD A,B
OR C
JP NZ,DLoop
RET
DeactProj:
LD A, 0
LD (ProjActive), A
LD A, 216
LD (ProjY), A
LD (ProjX), A
RET
FlipFrame:
LD A,(SpriteFrm)
XOR 1
LD (SpriteFrm), A
RET
SpriteX:
DEFB 100
SpriteY:
DEFB 120
SpriteFrm:
DEFB 1
ProjY:
DEFB 216
ProjX:
DEFB 216
ProjActive:
DEFB 0
JP $ ; JP Idefinitely
When you run the program, you’ll see your familiar walking sprite. Use W, A, S, and D to move around as before.
Now, when you press the space bar, the character fires a projectile that travels horizontally across the screen.
Once the projectile moves past the right edge, it disappears automatically and can be fired again.
This demonstrates how multiple independent objects can be updated and managed within the same main loop — a key principle of game logic.
The projectile system introduces the idea of independent entities in motion. Each projectile has its own position, behaviour, and life cycle, making it possible to manage multiple moving elements on screen simultaneously.
In more advanced games, this principle expands into:
This simple example provides the foundation for more complex interactions and game mechanics.
ProjX and ProjY store the projectile’s position.ProjActive ensures only one projectile is active at a time.Fire routine activates a projectile when the space bar is pressed.UpdateProjectile routine moves it forward each frame.DeactProj removes the projectile when it leaves the screen.With this system in place, your sprite can now not only move and animate but also interact with the world, taking your Tatung Einstein game to the next level.