Glusoft

Implementing a Game State Machine with SDL3

Overview: The state machine

A state machine (or finite state machine, FSM) is a programming design pattern used to model the behavior of a system by breaking it into a set of states, and defining how it transitions from one state to another based on inputs or events.

in this program we want to implement a simple state machine like this, something very common in a game:

File Dialog with SDL3 Dialog API

Initialization of the window, renderer and the font

SDL_Init(SDL_INIT_VIDEO);
TTF_Init();

SDL_Window *win = SDL_CreateWindow("State Machine SDL3", 800, 600, 0);
SDL_Renderer *renderer = SDL_CreateRenderer(win, NULL);

TTF_Font *font = TTF_OpenFont("FreeSans.ttf", 24);
if (!font) {
    SDL_Log("Could not load font: %s", SDL_GetError());
    return 1;
}

The states handling

To implement the state machine we first need to define the states in an enum:

typedef enum {
    STATE_MENU,
    STATE_PLAYING,
    STATE_PAUSED,
    STATE_EXIT
} GameState;

And then we define the function for each states:

For the menu state:

void handle_menu(SDL_Event *e, GameState *state) {
    if (e->type == SDL_EVENT_KEY_DOWN && e->key.key == SDLK_RETURN) {
        *state = STATE_PLAYING;
    }
}

For the playing state:

void handle_playing(SDL_Event *e, GameState *state) {
    if (e->type == SDL_EVENT_KEY_DOWN) {
        if (e->key.key == SDLK_P) {
            *state = STATE_PAUSED;
        } else if (e->key.key == SDLK_ESCAPE) {
            *state = STATE_EXIT;
        }
    }
}

For the pause state:

void handle_paused(SDL_Event *e, GameState *state) {
    if (e->type == SDL_EVENT_KEY_DOWN && e->key.key == SDLK_R) {
        *state = STATE_PLAYING;
    }
}

We then need a function to render the text of the states :

void render_text(SDL_Renderer *renderer, TTF_Font *font, std::string text, float x, float y) {
    SDL_Color white = {255, 255, 255, 255};
    SDL_Surface *surface = TTF_RenderText_Blended(font, text.c_str(), text.size(), white);
    if (!surface) return;

    SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
    SDL_FRect dst = {x, y, (float)surface->w, (float)surface->h};
    SDL_RenderTexture(renderer, texture, NULL, &dst);

    SDL_DestroyTexture(texture);
    SDL_DestroySurface(surface);
}

The event loop

The event loop is pretty simple we need to call the state handling functions accrding to the state:

SDL_Event e;
while (SDL_PollEvent(&e)) {
    if (e.type == SDL_EVENT_QUIT) {
        state = STATE_EXIT;
    }

    switch (state) {
        case STATE_MENU:    handle_menu(&e, &state); break;
        case STATE_PLAYING: handle_playing(&e, &state); break;
        case STATE_PAUSED:  handle_paused(&e, &state); break;
        default: break;
    }
}

The rendering

The rendering part of the program is tto call the render_text function with the right text to display:

SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);

switch (state) {
    case STATE_MENU:
        render_text(renderer, font, "Press Enter to Start", 50, 50);
        break;
    case STATE_PLAYING:
        render_text(renderer, font, "Playing... Press P to Pause, Esc to Quit", 50, 50);
        break;
    case STATE_PAUSED:
        render_text(renderer, font, "Paused... Press R to Resume, Esc to Quit", 50, 50);
        break;
    default: break;
}

The cleanup

We need to desallocate the objects:

TTF_CloseFont(font);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(win);
TTF_Quit();
SDL_Quit();

Download the full project : Implementing a Game State Machine with SDL3

Need another OS ? => Windows, Mac, Linux