The Game Loop
The heartbeat of every game
Every game runs on the same fundamental pattern: read input, update state, render graphics, repeat. Understanding the game loop is the first step to building anything interactive.
The game loop is the core rhythm that drives every video game. Each frame, the game reads player input, updates the game world, draws the result, and repeats. This simple pattern—running 50 or 60 times per second—creates the illusion of continuous motion and responsiveness.
The basic pattern
initialise game state
repeat forever:
read input
update game state
render graphics
wait for next frame
This structure appears in every game from Pong to modern AAA titles.
Frame timing
Fixed frame rate
Most 8-bit games synchronise to the display refresh:
| System | Refresh rate | Frame time |
|---|---|---|
| PAL | 50 Hz | 20 ms |
| NTSC | 60 Hz | 16.67 ms |
Synchronising to vertical blank (VBlank) ensures:
- Consistent game speed.
- Tear-free graphics updates.
- Predictable timing for music.
Variable timestep
Modern games often use:
delta_time = current_time - last_frame_time
update(delta_time)
But 8-bit games typically assume fixed timing.
Implementation approaches
Busy-wait loop
The simplest pattern — poll the raster register until it crosses a known scanline:
main_loop:
jsr read_input
jsr update_game
jsr render_graphics
wait_vsync:
lda $d011 ; VIC-II control reg; bit 7 = high bit of raster line
bpl wait_vsync ; loop while raster < 256 (visible area on PAL is 0-249)
jmp main_loop
The $D011 bit-7 check waits for the raster to enter the upper border / vblank region (raster ≥ 256 on PAL = lines 256-311). Simple but wastes cycles during the wait.
Interrupt-driven
The C64 design Cadaver describes as tried and true keeps the raster interrupts small and the main loop free-running. The interrupts do only what has to land on a particular raster line — “the immediate setting of VIC registers (like screen-splits and sprite multiplexing) and playing music/sound” — and “all the time-consuming things like movement, AI & scrolling are done in the main program”. The two halves meet through a frame counter:
; Raster IRQ at the bottom of the screen: VIC writes and music only
irq_handler:
; ... set registers for the next frame, play music ...
inc frame_count
dec $d019 ; acknowledge
rti
; Main program: logic, then wait for the next frame
main_loop:
jsr read_input
jsr update_game
jsr render_graphics
lda frame_count
.wait:
cmp frame_count
beq .wait
jmp main_loop
The payoff is graceful failure. If the main loop overruns, the interrupts still fire on time and the screen still shows correctly; the game slows down as a whole instead of tearing or flickering. Richard Bayliss’s guide puts the same thing in fewer words: the routines “that will synchronize your game code and loop the game engine continuously” come first, and “Most C64 games also use IRQ raster interrupts to perform various tasks.”
Phase structure
1. Input phase
Read all input sources at the start:
- Joystick positions
- Keyboard state
- Network data (for multiplayer)
Store values for consistent use throughout the frame.
2. Update phase
Process game logic in order:
- Player movement and actions
- Enemy AI and movement
- Physics and collisions
- Scoring and game state
- Sound triggers
3. Render phase
Draw everything to screen:
- Clear or scroll background
- Draw static elements
- Draw moving objects (sprites)
- Update score display
Critical: On systems without double-buffering, update graphics during VBlank to avoid tearing.
Timing considerations
VBlank window
| System | VBlank cycles |
|---|---|
| C64 PAL | ~7,000 cycles (112 border lines × 63; no badlines outside the display window) |
| NES | ~2,270 cycles |
| ZX Spectrum | varies by border |
Graphics updates must complete within this window.
Splitting work
If update takes too long:
- Spread AI across multiple frames
- Update only visible sprites
- Use dirty rectangle rendering
- Run the logic every second or fourth frame and interpolate sprite positions in between, as Metal Warrior 4 does — see Frameskipping and Interpolation. Gauntlet III and Myth scroll at 50 Hz while moving their sprites at a lower rate.
State machines within the loop
game_loop:
lda game_state
cmp #STATE_TITLE
beq handle_title
cmp #STATE_PLAYING
beq handle_playing
cmp #STATE_GAMEOVER
beq handle_gameover
Different states run different logic while sharing the same loop structure.
Common mistakes
| Mistake | Problem | Solution |
|---|---|---|
| No frame sync | Game speed varies | Wait for VBlank |
| Render during display | Visual tearing | Update in VBlank only |
| Too much per frame | Slowdown | Spread work across frames |
| Input once per object | Inconsistent response | Read once, use everywhere |
Platform-specific notes
Commodore 64
Use raster interrupts or poll $D012 for VBlank. The common shape is a bottom-of-screen interrupt that writes the VIC registers and plays the music, with everything else in the main loop, synchronised by a frame counter.
ZX Spectrum
Check FRAMES system variable or use HALT to wait for interrupt.
NES
NMI fires at VBlank start—natural synchronisation point.
Amiga
Copper can trigger interrupts; also use VBlank interrupt.