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);
}