-
-
Notifications
You must be signed in to change notification settings - Fork 92
Game States Engine
The game states engine is a small, generic finite-state-machine helper bundled with pvsneslib. It lets you split a game into independent "states" (intro, title screen, menu, gameplay, game over, …), each with its own init and update function, and it takes care of calling the right functions at the right time, frame after frame.
It is especially useful for organizing games into clear logical sections such as menus, gameplay, pause screens, and game over states.
| State | Purpose |
|---|---|
| Intro | Show intro screen |
| Menu | Show menu screen, wait for START |
| Playing | Run game, handle input, render |
| Paused | Freeze game, show overlay message |
| Game Over | Display score, wait for restart |
Each state has its own code, logic, etc...
A game state is identified by a small integer (an enum value, e.g. INTRO, MENU).
Each state provides exactly two functions:
-
gstaini_<STATE_ID>(void)— called once, when the engine switches into that state. -
gstaupd_<STATE_ID>(void)— called once per frame, for as long as the state is active.
At any point, your code can call gstaSetState(NEXT_STATE) to tell the engine "stop the current state and switch to NEXT_STATE". The switch actually happens on the next pass of the engine loop: the current state's init/update cycle is abandoned, gstaini_<NEXT_STATE> runs once, and then gstaupd_<NEXT_STATE> starts running every frame.
-
gstaProcess()never returns — it is your game loop. Once you call it (after setting the first state), it loops forever, calling init/update for whichever state is current.
Your game must provide a .h file which:
- Lists every state in a
GSTATESDEFmacro, using_GSTATEDEF. - Turns that list into a
GSTATESenum.
#ifndef _STA_MAIN_H
#define _STA_MAIN_H
#define GSTATESDEF \
_GSTATEDEF(INTRO) \
_GSTATEDEF(TITLE) \
GSTATE_DEF_END
typedef enum {
GSTATESDEF
N_GSTATES
} GSTATES;
#endifEach _GSTATEDEF(NAME) expands to NAME,, so GSTATESDEF produces a comma-separated list of enum values (INTRO, TITLE,).
N_GSTATES is automatically added at the end and gives you the total number of declared states — handy for sanity checks, but not required by the engine.
GSTATE_DEF_END is defined by the engine as nothing; it just closes the macro cleanly (no trailing backslash issues).
To add a new state, add one _GSTATEDEF(YOURSTATE) line here.
Each state lives in its own .c file and implements two functions named after the state:
void gstaini_<STATE_ID>(void); // called once, when entering the state
void gstaupd_<STATE_ID>(void); // called every frame, while the state is activeEach state can change VRAM, musics and can have a different VBlank function with nmiSet().
Base on the example you can find in snes-examples/systems/states.
#include <snes.h>
#include "../res/libfontintro.inc"
#include "states.h"
extern u16 pad0;
void gstaini_INTRO(void) {
consoleInitText(0, 16 * 2, &libfontintro_til, &libfontintro_pal);
consoleDrawText(7, 10, "Hello Intro State!");
}
void gstaupd_INTRO(void) {
pad0 = padsCurrent(0);
if (pad0 & KEY_A) {
gstaSetState(TITLE); // leave INTRO, go to TITLE
}
}gstaini_INTRO sets up the font and prints a message. It runs exactly once.
gstaupd_INTRO runs every frame: it reads the pad, and as soon as the player presses A it calls gstaSetState(TITLE). From that point on the engine will stop calling gstaupd_INTRO and instead call gstaini_TITLE once, then gstaupd_TITLE every frame.
Names must follow the gstaini_<ID> / gstaupd_<ID> pattern, where <ID> is exactly the identifier used in _GSTATEDEF/_GSTATEINIT (e.g. INTRO, TITLE).
Both functions take no parameters and return void.
Nothing stops a state's update function from calling gstaSetState() for another state (this is how transitions happen), or from never calling it at all (a state that never ends, e.g. a "game over — reset console" screen).
In your main file, you declare which states exist for this game and boot the engine.
Base on the example you can find in snes-examples/systems/states.
#include <snes.h>
#include "states.h"
// Forward-declare init/update pairs for each state
DECLARE_GSTATE(INTRO);
DECLARE_GSTATE(TITLE);
// need to undef as it is also defined (empty) in the library
#undef GSTATESINIT
// need to declare local states
#define GSTATESINIT \
_GSTATEINIT(INTRO) \
_GSTATEINIT(TITLE) \
GSTATE_DEF_END
u16 pad0; // shared across all states
int main(void) {
spcBoot();
consoleSetTextMapPtr(0x6800);
consoleSetTextGfxPtr(0x3000);
consoleSetTextOffset(0x0100);
bgSetGfxPtr(0, 0x2000);
bgSetMapPtr(0, 0x6800, SC_32x32);
setMode(BG_MODE1, 0);
bgSetDisable(1);
bgSetDisable(2);
gstaInitEngine(); // register all states declared in STATESINIT
setScreenOn();
gstaSetState(INTRO); // pick the first state to run
gstaProcess(); // enter the state loop — never returns
return 0;
}Step by step:
-
DECLARE_GSTATE(NAME)— a convenience macro (seegamestates.hengine header) that forward-declaresgstaini_NAMEandgstaupd_NAMEasextern, sostates.ccan reference functions defined instateintro.c/statetitle.c. -
GSTATESINIT— redefined locally to list, via_GSTATEINIT(NAME), every state that should be registered with the engine. This must mirror the list of states in your.hfile (GSTATESDEF). -
gstaInitEngine()— expands toGSTATESINIT, i.e. it callsgstaInitFunctions()once per state to register each state's init/update function pointers with the engine. -
gstaSetState(INTRO)— selects which state runs first. -
gstaProcess()— starts the infinite engine loop. This call does not return; it is the game loop from this point on.
That's all for the game states engine, you can now play with it for your own game!