Introduction

Presentation

This book will teach you everything you need to know about building video games with the libgote. I'll try to add information about the various aspects of the library and how you can link them together to make a complete video game.

Refer to the API documentation for an in-depth detail of all the functions provided by the library.

Building from source

Cloning the repository

Using SSH

Make sure your SSH key is linked to your GitHub account.

git clone git@github.com:nasso/libgote.git

Using HTTPS

git clone https://github.com/nasso/libgote.git

Building libgote

Just navigate in the root directory of the repository and call make:

cd libgote
make

If you want to build it with debug flag (-g3), add DEBUG=1 to the make command:

make DEBUG=1

Generating the documentation

The library documentation is generated using Doxygen. A online version is available here.

First, make sure the ./target directory exists at the root of the repository. It is automatically created when building the library, but you can also use mkdir.

Concepts

This chapter will give you an overview of the different concepts you need to be familiar with to use libgote. Some of those concepts aren't specific to libgote and you will easily find more resource online about them: entity-component-system (ECS), game states and game assets.

State

Basics

As described in this article, a state (or game-state) represents a "mode" our game is in. For instance, we could separate an RPG in different game states:

  • A "local map" state where the player can move around a terrain
  • A "world map" state where a zoomed-out map of the world is displayed
  • A "combat" state for fights!
  • A "pause menu" state displaying the pause menu of the game
  • ...

Different game states and the transitions between them

Essentially, every state has its own start, stop and update functions.

Usage

To create a state with the libgote, use calloc to allocate a gt_state_t structure and set the callback functions you care about:

gt_state_t *create_combat_state(void)
{
  gt_state_t *state = my_calloc(1, sizeof(gt_state_t));

  state->update = &combat_state_update;
  return (state);
}

Of course, update isn't the only callback available to game states. Here is a list of the most commonly used:

  • on_start - called when the state starts.
  • on_stop - called when the state stops.
  • update - called on every iteration of the game loop while the state is active.
  • destroy - called when the state is destroyed, use it to free the self field.

The libgote will safely ignore any callback set to NULL. This is why you should use calloc to allocate the gt_state_t structure (calloc ensures all the allocated bytes are set to NULL or 0).

The self field allows you to store arbitrary data associated with your state. Its value is passed as an argument to all the callbacks. In most cases, you will use it to keep a reference to data you want to clean-up when the state is stopped:

/* Structure holding the data associated with the state */
struct combat_state {
  player_t *player;
};

/* Create the player when the state starts */
static void combat_state_on_start(void *ptr, gt_world_t *world)
{
  struct combat_state *self = ptr;

  self->player = player_create(world);
}

/* Remove the player when the state stops */
static void combat_state_on_stop(void *ptr, gt_world_t *world)
{
  struct combat_state *self = ptr;

  player_remove(self->player, world);
}

/* Create the state, its data structure, and set the callbacks */
gt_state_t *create_combat_state(void)
{
  gt_state_t *state = my_calloc(1, sizeof(gt_state_t));

  state->self = my_calloc(1, sizeof(struct combat_state));
  state->destroy = &my_free;
  state->on_start = &combat_state_on_start;
  state->on_stop = &combat_state_on_stop;
  return (state);
}

Make sure not to forget to set state->destroy to an appropriate freeing function! If you don't do it, memory WILL leak!

State machine

Game states are usually managed by something called a state machine. It is the one responsible for calling the right function at every iteration of the game loop. Only one state can be active at a time!

The state machine calls the update function of the currently active state

The state machine is often implemented as a stack, where states can be pushed and popped. The state at the top of the stack is the active state.

A game state beind pushed and then popped from the state machine

Transitions

A change in the state machine's stack is called a state transition. There are 4 different kinds of transitions in the libgote:

  • Push transitions - a state is pushed on top of the stack: it becomes the active state.
  • Pop transitions - the state on top of the stack is removed (the state below it, if any, becomes active).
  • Switch transitions - the state on top of the stack is replaced with another state.
  • Quit transitions - all the states are removed from the stack, killing the the state machine.

As soon as the state stack becomes empty, the state machine is killed and the game is effectively terminated.

Additionnal callbacks

In the libgote, states have actually 3 extra callbacks I didn't mention yet. You might need them in some special cases:

  • on_pause - called when a state is pushed on top of the current one.
  • on_resume - called when the state above gets popped.
  • shadow_update - same as update, but is called on ALL the states in the stack.

World and Resource

World

All the data global to your game is stored in what is called a "World". That includes, for instance, all the entities of your game. Entities are covered in their own chapter.

The world really is just a hash table mapping keys (character strings) to resources.

Resource

What is a resource? Anything you want! The only requirement is that it must be a pointer. Other than that, you can have anything you want except for function pointers, because of the C language specification forbidding it. If you really need to store a function pointer in your World, wrap them in a heap-allocated structure and it will be fine!

Usage in the libgote

Creating a new world

You will rarely ever need to create or destroy a world directly (libgote does it for you when you create a gt_app_t). Though, here's how you would do it:

// create a world this way:
gt_world_t *world = gt_world_create();

// destroy it when you're done!
gt_world_destroy(world);

Resource insertion

When inserting a resource in the world, you can optionally specify a destructor to be called when the resource is removed or the world destroyed:

list_t *cool_stuff = list_from(3, "furries", "undertale", "you");

// the destroyer must be explicitly cast because C cannot implicitly cast
// function pointers, even when they are compatible.
gt_world_insert(world, "cool_stuff", data, (void (*)(void*)) &list_destroy);

For resources which do not need to be destroyed (i.e. static value), just set the destroyer to NULL:

gt_world_insert(world, "some_string", "Hey! This is a resource!", NULL);

Resource retrieval

Getting a resource from the world is straightforward:

const char *my_resource = gt_world_get(world, "some_string");

my_printf("%s", my_resource); // "Hey! This is a resource!"

Resource removal

Removing a resource from the world is, also, straightforward:

gt_world_remove(world, "some_string");

Note that gt_world_remove does NOT return anything! The resource is destroyed as soon as you remove it from the world.

Entity and Resource

Entity

An entity represents any object of your game. Examples of entities include the player, NPCs, an item, a button or a map. An entity doesn't hold any data (other than its existence)! Essentially, entities can be thought of as elements in a big array containing all the objects that make up a scene of your game:

playervillagerswordplay_button

Component

A component is what gives properties to an entity. Without components, entities are... Well, useless! They don't do anything! They don't even have a position, a color, a sprite... All this information comes from components.

At any given point, an entity either has or doesn't have a component. It cannot have, say, the same component twice. Keeping the example from above, it's as if each component type added one new line to the following table:

entity
Component Avalue 1
Component Bvalue 2
Component Cvalue 3

Entities are not required to have a value for all component types. For example, we could say that the player only needs a position and a sprite:

player
Position(3, 4)
Spriteplayer.png
Color-

The player doesn't have a "color", so the value for the "color" component line is just left empty.

Adding an entity adds a column to the table!

playervillagerswordplay_button
Position(3, 4)(17, 12)(-4, 1)(40, 160)
Spriteplayer.pngvillager.pngsword.png-
Color---#88FF00

Even if only one entity requires a "color" component the line for the "color" component spans all the table. That can create "holes" in the table, but it's okay because there are ways to eliminate that problem we will see very shortly.

Usage with the libgote

Creating a component class

Say you want to create a new kind of component. For this example, we will create a "position" component class.

The first step is to declare a new gt_component_class_t global constant. Usually, you will want to declare it in a header file with the extern keyword:

extern const gt_component_class_t POSITION_COMP;

And then, define it in your source file:

const gt_component_class_t POSITION_COMP = {
  .name = "position_component",
  .destroyer = &my_free,
};

There are two important fields here:

  • name - a static, unique character string that will be used as the key for the storage resource in the world. It must be unique and must not be used as the key for any other resource in the world.
  • destroyer - a pointer to the function to be called when the component data is to be destroyed.

Speaking about the component data: it can be anything you want! It is common to have a constructor for your component data (declared in the same header file as the gt_component_class_t it corresponds to):

typedef struct {
  f64_t x;
  f64_t y;
} position_comp_t;

position_comp_t *position_component(f64_t x, f64_t y);

An example implementation could be:

position_comp_t *position_component(f64_t x, f64_t y)
{
  position_comp_t *self = my_calloc(1, sizeof(position_comp_t));

  self->x = x;
  self->y = y;
  return (self);
}

Registering components

Components are stored in "storages" kept as a Resource in the World. Think of a storage as a simple array (that's basically what they are anyway). Keeping the table example from earlier, each component "storage" corresponds to a line of the table. That means there's one storage per component type.

playervillagerswordplay_button
Position(3, 4)(17, 12)(-4, 1)(40, 160)
Spriteplayer.pngvillager.pngsword.png-
Color---#88FF00

In this example, there are 3 storages: one for Position components, one for Sprite components and another for Color components. Each line is a storage.

There are two implementations of storage in the libgote:

  • gt_vec_storage - literally just a vector: better when most entities will have this component (there's very few "holes").
  • gt_map_storage - uses a hash map: better when few entities will have this component (there are many, big "holes"). Not implemented as of writing this. Do not use.

As a component storage is just a resource in the world, you add them just like any other resource:

// create the storage (here it's just a vector/array!)
gt_storage_t *strg = gt_vec_storage();

gt_world_insert(
  world,
  POSITION_COMP.name,
  strg,
  (void (*)(void*)) &gt_storage_destroy
);

Though, having to do that for every component type would be kinda annoying. This is why the gt_world_register function exists! It takes care of the last argument (the destroyer):

gt_storage_t *strg = gt_vec_storage();

gt_world_register(world, POSITION_COMP.name, strg);

But there's even better than this! The gt_world_register_component function!

gt_world_register_component(world, &POSITION_COMP, &gt_vec_storage);

Here, all you have to give is a pointer to the gt_component_class_t and a pointer to the constructor for the storage implementation to use. It does the exact same thing as the code snippets above!

Creating an entity

You cannot create an entity from nothing (there's no "gt_entity_create" function). The reason for this is that an entity only makes sense when it is part of a world, with the appropriate storages available.

You can create entities with the gt_world_create_entity function:

The arguments are:

  • The world
  • How many components to initialize the entity with
  • For each initial component:

For example, to create an entity using our position_comp_t:

gt_entity_t *ent = gt_world_create_entity(world, 1,
  &POSITION_COMP, position_component(2.0, 6.0)
);

Or with more components you've created (don't forget to change the component count!):

gt_entity_t *player = gt_world_create_entity(world, 3,
  &POSITION_COMP, position_component(2.0, 6.0),
  &SPRITE_COMP, sprite_component("player.png"),
  &INVENTORY_COMP, inventory_component()
);

Of course, if you want to create an entity without any component:

gt_entity_t *ent = gt_world_create_entity(world, 0);

Removing the entity

When you're done using an entity, you have to manually remove it from the world! States do not know about the entities you've created, so they are not removed automatically!

To remove entities, use the gt_world_remove_entity function:

/* Remove the player entity when the state stops */
static void combat_state_on_stop(void *ptr, gt_world_t *world)
{
  struct combat_state *self = ptr;

  gt_world_remove_entity(world, self->player);
}

System

Dispatcher

Event channel

Asset format