How to write a game engine in pure C: Part 1 – State Manager

Index:

The core of any game is the engine, game engines are the most important piece, the foundation or everything to be built on. It has to work flawlessly, be performant, flexible and easy to use and understand.

Engines are a way to organize code, you can write games without one, just like any other software, but it’ll end up being a bloody mess, trust me.

I like to write games and i thought a full tutorial for the most important part (the engine) would be really cool to write for me and to read for you (hope). In this new serie we’ll explore the process of writing a hole 2D game engine in pure C (C89), why C? I don’t know… I like C and I think it’s mostly self explanatory, anyone should know C…

In this first post we will cover one of the most important parts for any engine, the state manager or state machine.

The state manager

Games are divided in states or scenes, just like applications are divided in screens. It’s a convenient way to organize code to be easily maintainable and keep it split by “concepts”.
In a simple game (the kind of game we will build with this engine) we would have one or more states, that represents different screens for the user. We would have the main menu, for example, a hole state with GUI and stuff to interact with, and the game state, the action itself, where all the game is played.

To make this work we need a mechanism to switch between scenes and keep track of what’s happening without initializing and killing things manually in every flip.

This is where the state manager comes to play. There are different ways of doing this but i like stack-based state managers, what does it means?

THE STACK BASED STATE MANAGER

First, our scenes will look like standalone applications and we will store them in a stack, one in top of the others; then, in the main loop, our stack manager will use the state that is on the top of the stack.
If we are in the game state and want to show the pause menu, for example, we would push a new state into the stack (the pause menu state), this state would go to the top of the stack, just above the game state and the main loop would access it instead, pausing the state that was running before and giving the user access to the new one.

If we are on the pause state and wants to resume the game, we just have to pop the state from the stack, leaving the previous state (the game state) as head of the stack again and voila… the main loop will use it since it is now the top state again.

On every flip, states that goes out of the stack (popped) have to be destroyed and states that goes into the stack (pushed) has to be initialized, this, and managing the stack itself is what a state manager does.

THE CODE

First of all, we need two data structures, state and the statemanager itself.

the state represents the block of code that conceptually belongs to a game part or scene, it’ll need a initializer to set everything needed up, a destroyer, to clean after running (freeing memory is a thing in C) and update and draw methods to access on the main loop.

As you might know, C is not an Object-Oriented Language (thanks god) and we have no classes or complex data types so we have to do it ourselves.

There are different ways to do this but I’ll do it like this, my states are collections of pointers to functions (like methods) conceptually related, so, my state game will have pointers to the function that initializes the game state, to the function that deletes it, to the function that updates on the game and to the function that draws the game state.

For convenience i’ll define types for my function pointers:

typedef unsigned int (*fnPtr)();
typedef unsigned int (*fnPtrFl)(float);

And then i can define my State structure, just four pointers:

typedef struct {
  fnPtr init;
  fnPtrFl update;
  fnPtrFl draw;
  fnPtr destroy;
} State;

The StateManager type is more interesting, it’ll have the stack and two numbers, ‘capacity’ will tell us how many states the stack can hold, and top will tell us the index of the top state.

stack is an array of pointers (thats what ** means), the states will be defined elsewhere and the statemanager will keep a pointer to them, this is important so the states are kept in memory just once (the PSP have 32mb of ram, did you know? this is how you can do great things with poor hardware)

typedef struct {
  State **stack;
  int capacity;
  int top;
} StateManager;

With our types defined we need to write some functions. I like to have data structures and functions to work with them instead of having objects with methods and stuff, it saves memory and makes the software pretty self-explanatory (the State typedef is a special case, ok?).

so, lets define some functions:

int STATEMANAGER_init(StateManager *statemanager);
int STATEMANAGER_free(StateManager *statemanager);
int STATEMANAGER_push(StateManager *statemanager, State *state);
int STATEMANAGER_pop(StateManager *statemanager);
State *STATEMANAGER_top(StateManager *statemanager);
int STATEMANAGER_update(StateManager *statemanager, float deltatime);
int STATEMANAGER_draw(StateManager *statemanager, float deltatime);

Why STATEMANAGER_xxxx? well, in C89 we don’t have namespaces neither so, if you don’t want to end up with twelve ‘draw’ functions that does different things i recommend you to follow some conventions, i’m experimenting with this one, just for fun 😀

Back to the statemanager … The functions, we have some important stuff here, first, init and free functions, those initializes and destroys the state manager itself.

int STATEMANAGER_init(StateManager *statemanager) {
  statemanager -> capacity = 3;
  statemanager -> stack = malloc(statemanager -> capacity * sizeof(State*));
  statemanager -> top = -1;
  return 0;
}

int STATEMANAGER_free(StateManager *statemanager) {
  do {
    STATEMANAGER_pop(statemanager);
  } while (statemanager -> top > -1);
  free(statemanager -> stack);
  return 0;
}

STATEMANAGER_init will set the stack minimum capacity, allocate some memory for it and set the index to the top of the stack, at this point, none.
Free will call pop until the stack is empty and then will free the memory allocated with malloc (remember kids, no malloc without free).
Free calls pop because, as we’ll see later, it’ll pop the top state from the stack, call it’s destroy method and move the top index down, so no need to do it manually here.

Then we have the push, pop and top functions, push will put a state into the stack, pop will take the top state out, and top will return the active state (the one in the top).

Our stack is dynamically allocated so we can save some memory, as you saw in the init function, it’s capacity is set to 3, a game can have more than 3 states, so it’ll have to have the ability to grow, for this we need a conveniency function that i called scale. scale doubles the state managers capacity and reallocs the memory, so more states can fit in, it’s written like this:

int STATEMANAGER_scale(StateManager *statemanager) {
  statemanager -> capacity *= 2;
  statemanager -> stack = realloc(statemanager -> stack, statemanager -> capacity * sizeof(State*));
  return statemanager -> capacity;
}

Now we can implement our stack management functions…

int STATEMANAGER_push(StateManager *statemanager, State *state) {
  if (statemanager -> top + 1 == statemanager -> capacity) STATEMANAGER_scale(statemanager);
  statemanager -> top++;
  statemanager -> stack[statemanager -> top] = state;
  if (state -> init != NULL) state -> init();
  return statemanager -> top;
}

int STATEMANAGER_pop(StateManager *statemanager) {
  if (statemanager -> top == -1) return 0;
  State *top = STATEMANAGER_top(statemanager);
  if (top -> destroy != NULL) top -> destroy();
  statemanager -> stack[statemanager -> top] = NULL;
  statemanager -> top--;
  return statemanager -> top;
}

State *STATEMANAGER_top(StateManager *statemanager) {
  return statemanager -> stack[statemanager -> top];
}

Nothing tricky here, push will check if we reached the limit of the stack and scale if needed, then it’ll increase the top index, add the state pointer to the stack, call it’s init method if it’s defined and then return the new index.
Pop just get’s the top state, calls it’s destroy method and removes it from the stack, decreasing the index and returning the new top index.
Top can’t be simpler, it returns the active state.

And last, but not least, the loop functions… on each iteration, the game main loop will call some functions, two of those are the update and draw state methods, we have two ways to do this… to call in the loop STATEMANAGER_top() -> update(); or to let the manager…well…manage.

int STATEMANAGER_update(StateManager *statemanager, float deltatime) {
  State *state = STATEMANAGER_top(statemanager);
  if (state -> update != NULL) return state -> update(deltatime);
  return 1;
}

int STATEMANAGER_draw(StateManager *statemanager, float deltatime) {
  State *state = STATEMANAGER_top(statemanager);
  if (state -> draw != NULL) return state -> draw(deltatime);
  return 1;
}

It’s the same, check’s if the top state has update or draw method and if it does, call it.
We will make this work on the next posts.

So our statemanager systems looks like this now:

statemanager.h

#ifndef ENGINE_STATEMANAGER_H
#define ENGINE_STATEMANAGER_H

typedef unsigned int (*fnPtr)();
typedef unsigned int (*fnPtrFl)(float);

typedef struct {
  fnPtr init;
  fnPtrFl update;
  fnPtrFl draw;
  fnPtr destroy;
} State;

typedef struct {
  State **stack;
  int capacity;
  int top;
} StateManager;

int STATEMANAGER_init(StateManager *statemanager);
int STATEMANAGER_free(StateManager *statemanager);
int STATEMANAGER_push(StateManager *statemanager, State *state);
int STATEMANAGER_pop(StateManager *statemanager);
State *STATEMANAGER_top(StateManager *statemanager);
int STATEMANAGER_update(StateManager *statemanager, float deltatime);
int STATEMANAGER_draw(StateManager *statemanager, float deltatime);

#endif

Remember to fit your headers inside guardians to keep them from loading twice…

statemanager.c

#include "statemanager.h"
#include 

int STATEMANAGER_scale(StateManager *statemanager) {
  statemanager -> capacity *= 2;
  statemanager -> stack = realloc(statemanager -> stack, statemanager -> capacity * sizeof(State*));
  return statemanager -> capacity;
}

int STATEMANAGER_init(StateManager *statemanager) {
  statemanager -> capacity = 3;
  statemanager -> stack = malloc(statemanager -> capacity * sizeof(State*));
  statemanager -> top = -1;
  return 0;
}

int STATEMANAGER_free(StateManager *statemanager) {
  do {
    STATEMANAGER_pop(statemanager);
  } while (statemanager -> top > -1);
  free(statemanager -> stack);
  return 0;
}

int STATEMANAGER_push(StateManager *statemanager, State *state) {
  if (statemanager -> top + 1 == statemanager -> capacity) STATEMANAGER_scale(statemanager);
  statemanager -> top++;
  statemanager -> stack[statemanager -> top] = state;
  if (state -> init != NULL) state -> init();
  return statemanager -> top;
}

int STATEMANAGER_pop(StateManager *statemanager) {
  if (statemanager -> top == -1) return 0;
  State *top = STATEMANAGER_top(statemanager);
  if (top -> destroy != NULL) top -> destroy();
  statemanager -> stack[statemanager -> top] = NULL;
  statemanager -> top--;
  return statemanager -> top;
}

State *STATEMANAGER_top(StateManager *statemanager) {
  return statemanager -> stack[statemanager -> top];
}

int STATEMANAGER_update(StateManager *statemanager, float deltatime) {
  State *state = STATEMANAGER_top(statemanager);
  if (state -> update != NULL) return state -> update(deltatime);
  return 1;
}

int STATEMANAGER_draw(StateManager *statemanager, float deltatime) {
  State *state = STATEMANAGER_top(statemanager);
  if (state -> draw != NULL) return state -> draw(deltatime);
  return 1;
}

How can we test it? well…

#include 
#include "src/statemanager.h"

unsigned int initState1() {
  printf("state 1 created\n");
  return 0;
}

unsigned int destroyState1() {
  printf("state 1 destroyed\n");
  return 0;
}

unsigned int initState2() {
  printf("state 2 created\n");
  return 0;
}

unsigned int updateState2(float deltatime) {
  printf("state 2 update %f\n", deltatime);
  return 0;
}

unsigned int destroyState2() {
  printf("state 2 destroyed\n");
  return 0;
}

int main() {
  StateManager statemanager;
  STATEMANAGER_init(&statemanager);

  State state1 = {0};
  state1.init = initState1;
  state1.destroy = destroyState1;

  State state2 = {0};
  state2.init = initState2;
  state2.update = updateState2;;
  state2.destroy = destroyState2;

  STATEMANAGER_push(&statemanager, &state1);
  STATEMANAGER_update(&statemanager, 10.0f);

  STATEMANAGER_push(&statemanager, &state2);
  STATEMANAGER_update(&statemanager, 10.0f);

  STATEMANAGER_pop(&statemanager);
  STATEMANAGER_update(&statemanager, 10.0f);

  STATEMANAGER_free(&statemanager);
}

Here we initialize the state manager, define the functions for each state and assign them to the pointers on the states.

After that, push the states and do some things on them, lets see, from the first ‘STATEMANAGER_push’…

It pushes the state1 into the stack, it fires it’s init function, it should print to the console ‘state 1 created’

It calls the update function, the state on the top of the stack (and the only one for now) is the state1, it has no update function binded, so it does nothing.

It pushes the state2, does the same than the first push.

It calls the update function, now the top of the stack is the state2, it has a binded update, so it’ll say so in the console.

Pops, the state2 is taken from the stack and destroyed, the state manager calls the destroy function and it’s output is written to the console, the state 1 is active again.

Calls update, state1 has no update method, nothing happens.

Calls free on the state manager, only state1 is left in the stack, so it’ll be freed and the manager destroyed.

Here’s the output:

state 1 created
state 2 created
state 2 update 10.000000
state 2 destroyed
state 1 destroyed

With this we have a fully functional state manager written in pure C89, in the next post we will keep learning and expanding our new engine.

Thank you for reading 😉

See the whole code in github: https://github.com/PRDeving/GameEngine-C89-tutorial

12 thoughts on “How to write a game engine in pure C: Part 1 – State Manager”

  1. While float is probably enough for frame delta time, please don’t use them for game timing. Handling non-delta time as floats can easily lead to nasty frame rate bugs when the game is running a bit longer. Jitter can start to show up just in a few hours. In 20 hours, your game might max at jittery 30 fps due to float precision issues.

    Using doubles has no effect on the performance and avoids any precision issues.

    Like

    1. Absolutely, i used float for deltatime cause its half the size of double and though there wouldnt be time enough between frames to produce any side effect but you are absolutely right about game time and i’d probably use float for it also. Will keep that in mind and explain it as you did whenever the series reach that point. Thank you very much

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s