This tutorial covers game state management on the SNES, including state machines, screen transitions, and common game flow patterns (title screen, gameplay, pause, game over).
What Are Game States?
A game state represents what the game is currently doing. Most SNES games cycle through a small set of states:
| State | Purpose |
| Title | Show title screen, wait for START |
| Playing | Run game logic, handle input, render |
| Paused | Freeze game logic, show overlay message |
| Game Over | Display result, wait for restart |
Each state has its own update logic, and transitions between states involve screen fades, VRAM reloads, or both.
Simple State Machine
The standard SNES pattern uses a state variable and a switch in the main loop. Define states as constants and dispatch each frame:
#define STATE_TITLE 0
#define STATE_PLAYING 1
#define STATE_PAUSED 2
#define STATE_GAME_OVER 3
while (1) {
break;
break;
break;
break;
}
}
return 0;
}
int main(void)
Entry point — initialize audio, display controls, run transport loop.
Definition main.c:37
void consoleInit(void)
Initialize SNES hardware.
void WaitForVBlank(void)
Wait for next VBlank period.
static u16 bx
Definition main.c:159
#define STATE_GAME_OVER
Game over: board frozen, "GAME OVER" rainbow cycle, await restart.
Definition main.c:91
#define STATE_PLAYING
Active gameplay: piece falling, player input, gravity.
Definition main.c:87
static void stateGameOver(void)
Game over state: display frozen board with rainbow "GAME OVER" text.
Definition main.c:583
static u8 game_state
Definition main.c:143
static void stateTitle(void)
Title screen state: rainbow "PRESS START" text until player begins.
Definition main.c:625
static void statePlaying(void)
Main gameplay state: handle input, apply gravity, lock pieces.
Definition main.c:443
#define STATE_TITLE
Title screen: displaying "PRESS START" with rainbow cycle.
Definition main.c:85
#define BG_MODE1
Definition video.h:28
unsigned char u8
8-bit unsigned integer (0 to 255)
Definition types.h:46
void setMode(u8 mode, u8 flags)
Set background mode.
This is the exact pattern used in examples/games/tetris/main.c. Each state function runs once per frame and can change game_state to trigger a transition.
State Transitions
Transitioning between states often requires reloading VRAM, resetting variables, or fading the screen. The SNES PPU silently ignores VRAM writes during active display, so transitions must happen during VBlank or forced blank.
Fade Out / Fade In
The INIDISP register ($2100) controls screen brightness (0-15). Stepping through brightness levels over multiple frames creates a smooth fade:
}
}
}
}
}
}
void setBrightness(u8 brightness)
Set screen brightness.
static u8 i
Definition main.c:156
static void fade_in(u8 speed)
Fade the screen from black to full brightness.
Definition main.c:99
static void fade_out(u8 speed)
Fade the screen from full brightness to black.
Definition main.c:75
signed char s8
8-bit signed integer (-128 to 127)
Definition types.h:43
A speed of 1 gives a fast 16-frame fade (~267ms). A speed of 3 gives a slower cinematic fade. See examples/graphics/effects/fading/ for a complete working example.
VRAM Reload Between States
When switching from title screen to gameplay (or vice versa), you typically need to load different tiles, tilemaps, and palettes. Use forced blank to guarantee VRAM writes succeed:
}
void setScreenOff(void)
Disable screen display (blank)
void setScreenOn(void)
Enable screen display.
void dmaCopyCGram(u8 *source, u16 startColor, u16 size)
Copy palette data to CGRAM (PVSnesLib compatible)
void dmaCopyVram(u8 *source, u16 vramAddr, u16 size)
Copy data to VRAM (PVSnesLib compatible)
static u16 lives
Definition main.c:165
static u16 score
Definition main.c:161
The key rule: call setScreenOff() before bulk VRAM/CGRAM writes, and setScreenOn() after. This is the forced blank pattern used throughout the OpenSNES examples.
Title Screen Implementation
A title screen waits for the player to press START, then transitions to gameplay. The pattern from examples/games/tetris/main.c:
return;
}
do {
}
u16 pad_keys[]
Raw joypad state buffer written by the NMI handler every frame.
static void startGame(void)
Initialize a new game: clear board, reset state, start music.
Definition main.c:393
The release-wait loop (do { WaitForVBlank(); } while (pad_keys[0] & KEY_START)) prevents the START press from triggering an immediate pause in the gameplay state. This is a standard debounce pattern on the SNES.
Complete Title Flow
Breakout (examples/games/breakout/main.c) shows the full sequence: display "READY" text, wait for START, clear the message, then enter the game loop:
u16 blockmap[]
BG1 tilemap RAM copy (0x400 entries = 2KB) at WRAM $0800.
#define ST_READY
Definition main.c:105
static void writestring(const char *st, u16 *tilemap, u16 pos, u16 offset)
Write a null-terminated string to a tilemap buffer.
Definition main.c:202
#define ST_BLANK
Definition main.c:108
Pause Screen
Pausing overlays a message on the current gameplay, freezes all game logic, and resumes when START is pressed again. The key is a three-phase wait: release START, wait for START press, wait for release again.
From examples/games/breakout/main.c:
}
#define ST_PAUSED
Definition main.c:107
static u16 pad0
Definition main.c:167
static void handle_pause(void)
Handle pause functionality.
Definition main.c:530
The three-phase wait prevents the unpause START press from being read as a new pause request on the next frame. Tetris (examples/games/tetris/main.c) uses the same pattern with a prev_pad edge-detection approach:
}
const char str_paused[]
"PAUSED" message string
static u8 paused
Definition main.c:155
unsigned short u16
16-bit unsigned integer (0 to 65535)
Definition types.h:52
void hudShowMessage(const char *str)
Definition hud.c:108
Pause Design Rules
- Do not reload VRAM for a simple pause overlay. Write to a tilemap buffer and DMA it.
- Freeze game logic by returning early from the update function while paused.
- The NMI handler keeps running during pause. Input is still read, OAM is still transferred. Only your game logic stops.
- Redraw sprites before entering the pause loop if you use delta rendering, so the screen looks correct while frozen.
Game Over
Game over detection happens in gameplay logic. When the end condition is met, display a message and wait for player input before restarting.
Simple Game Over (Breakout)
Breakout detects game over when lives reach zero. It shows a message and halts:
}
}
static s16 pos_x
Definition main.c:177
static s16 pos_y
Definition main.c:178
#define ST_GAMEOVER
Definition main.c:106
static void die(void)
Handle player losing a life.
Definition main.c:481
Restart Game Over (Tetris)
Tetris allows restarting. When a new piece cannot spawn (board full), the game transitions to STATE_GAME_OVER, which shows a message, waits for START, then calls startGame() to reinitialize everything:
}
}
const char str_gameover[]
"GAME OVER" message string
void hudClearMessage(void)
Definition hud.c:139
void renderFlush(void)
Definition render.c:376
void renderBoard(void)
Definition render.c:271
Game Over with Fade
Combining game over with a fade transition creates a polished feel:
}
}
#define TILEMAP_ADDR
VRAM word address for the BG1 tilemap (32x32 = 2KB)
Definition main.c:119
State Machine Summary
| Concern | Pattern |
| State storage | static u8 game_state; with #define constants |
| Dispatch | switch (game_state) in main loop, one call per frame |
| Transition | Set game_state = NEW_STATE; then return; |
| VRAM reload | setScreenOff() before DMA, setScreenOn() after |
| Fade | Step setBrightness(0..15) across frames |
| Button debounce | Three-phase: release, press, release |
| Pause | Overlay text, freeze logic, same VRAM |
| Game over | Message, wait for input, reinitialize or halt |
Next Steps