Loading...
Searching...
No Matches
Game States Tutorial

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:

#include <snes.h>
#define STATE_TITLE 0
#define STATE_PLAYING 1
#define STATE_PAUSED 2
#define STATE_GAME_OVER 3
static u8 game_state;
int main(void) {
while (1) {
switch (game_state) {
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
OpenSNES Master Header.
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:

static void fade_out(u8 speed) {
u8 i;
for (brightness = 15; brightness >= 0; brightness--) {
for (i = 0; i < speed; i++) {
}
}
}
static void fade_in(u8 speed) {
u8 i;
for (brightness = 0; brightness <= 15; brightness++) {
for (i = 0; i < speed; i++) {
}
}
}
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:

static void transition_to_gameplay(void) {
/* Fade to black */
/* Screen is dark — force blank for safe VRAM access */
/* Load gameplay assets */
/* Reset game variables */
score = 0;
lives = 3;
/* Turn screen back on and fade in */
fade_in(2);
}
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:

static void stateTitle(void) {
/* Poll for START press */
if ((pad_keys[0] & KEY_START) == 0) {
return; /* Not pressed — keep waiting */
}
/* Wait for START release to prevent immediate pause */
do {
} while (pad_keys[0] & KEY_START);
/* Transition to gameplay */
}
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
#define KEY_START
Definition input.h:74

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:

/* Display ready message in tilemap */
writestring(ST_READY, blockmap, 0x248, 0x3F6);
dmaCopyVram((u8 *)blockmap, 0x0000, 0x800);
/* Wait for START press */
do { WaitForVBlank(); } while ((pad_keys[0] & KEY_START) == 0);
/* Wait for START release */
do { WaitForVBlank(); } while (pad_keys[0] & KEY_START);
/* Clear message and begin */
writestring(ST_BLANK, blockmap, 0x248, 0x3F6);
dmaCopyVram((u8 *)blockmap, 0x0000, 0x800);
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:

static void handle_pause(void) {
if ((pad0 & KEY_START) == 0) return;
/* Show "PAUSED" in tilemap and flush to VRAM */
writestring(ST_PAUSED, blockmap, 0x269, 0x3F6);
dmaCopyVram((u8 *)blockmap, 0x0000, 0x800);
/* Three-phase wait: release -> press -> release */
do { WaitForVBlank(); } while (pad_keys[0] & KEY_START);
do { WaitForVBlank(); } while ((pad_keys[0] & KEY_START) == 0);
do { WaitForVBlank(); } while (pad_keys[0] & KEY_START);
/* Clear message and resume */
writestring(ST_BLANK, blockmap, 0x269, 0x3F6);
dmaCopyVram((u8 *)blockmap, 0x0000, 0x800);
}
#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:

u16 pressed = pad & ~prev_pad; /* newly pressed this frame */
paused = 1;
/* ... three-phase wait ... */
paused = 0;
}
if (paused) return; /* skip all game logic */
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 void die(void) {
if (lives == 0) {
/* Show GAME OVER message */
writestring(ST_GAMEOVER, blockmap, 0x267, 0x3F6);
dmaCopyVram((u8 *)blockmap, 0x0000, 0x800);
/* Halt — player must reset */
while (1) { WaitForVBlank(); }
}
/* Still have lives: reset ball position, continue */
lives--;
pos_x = 94;
pos_y = 109;
/* ... */
}
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:

static void stateGameOver(void) {
/* Force blank for guaranteed DMA */
/* Wait for START to restart */
while ((pad_keys[0] & KEY_START) == 0) {
}
do { WaitForVBlank(); } while (pad_keys[0] & KEY_START);
startGame(); /* reinitialize board, score, spawn first piece */
}
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:

static void stateGameOver(void) {
/* Show game over message */
/* Wait for player acknowledgement */
while ((pad_keys[0] & KEY_START) == 0) {
}
do { WaitForVBlank(); } while (pad_keys[0] & KEY_START);
/* Fade out, reload, fade in */
/* Reset all game state and reload assets */
fade_in(2);
}
#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