Introduction
Hello! In our last lesson, we built a professional-grade game loop that separates a fixed-timestep update from a variable-timestep render. We used simple previousState and currentState structs to hold our object's position for interpolation, ensuring our simulation is stable and rendering is smooth.
This was a great start, but a struct holding only a vec3 position is quite limited. A game world is filled with diverse objects: the player, enemies, bullets, inanimate scenery, power-ups, and more. A player might have health and velocity, a static wall has a position but doesn't move, and a bullet might have a high velocity but no health. How can we represent all these different objects without creating dozens of slightly different structs, or one giant, wasteful struct with every possible property?
Today's learning outcome is to design a basic Entity struct to represent any object in the game world. We will move beyond a single, monolithic object definition and explore a powerful architectural pattern based on composition. This will allow us to build any type of game object by combining small, reusable pieces of data, much like building with LEGO bricks.
1. A Quick Refresher on C structs
Before we design our entity system, let's quickly solidify our understanding of C structs, as they are the fundamental tool we'll be using. Since you're new to C, this video will be an excellent refresher on the syntax and some powerful features like typedef and how structs handle pointers.
struct Basics | C Programming Tutorial
This video from Portfolio Courses covers the essentials of using structs in C. Pay close attention to how typedef is used to create cleaner code and the important caveat about what happens when a struct containing a pointer is copied.
Please watch the following segments: 00:00 - 04:55: Covers the basic syntax for defining a struct, creating instances, and accessing members with the . operator. 06:44 - 08:20: Explains how to use typedef to create an alias for a struct type, which is a very common and recommended practice in C. 13:23 - 14:22: Shows what happens when you assign one struct to another (p1 = p2). This is a direct memory copy. 17:49 - 24:38: This is the most critical part. It explains the behavior when a struct contains a pointer. The assignment copies the pointer's address, not the data it points to. Understanding this is crucial for avoiding hard-to-find bugs.
2. The Problem: Inheritance vs. Composition
With your background in C# and Python, you're likely familiar with object-oriented programming and inheritance, where you create a "base" class and then more specialized "child" classes that inherit its properties. While powerful, this can become rigid and complex in game development. What if you want a "Flying, Shooting Enemy"? Do you inherit from FlyingEnemy and ShootingEnemy? Many languages, including C, don't support multiple inheritance, leading to tangled hierarchies.
A more flexible approach is composition. Instead of an object being a single, rigid thing, it has a collection of properties.

This brings us to the Entity-Component-System (ECS) pattern, a cornerstone of modern game engine design.
- Entity: A general-purpose object. In its purest form, it's just a unique ID number. It doesn't have any data or behavior itself.
- Component: A small, data-only
structthat holds a specific piece of information. For example, aTransformComponentholds position and rotation, while aHealthComponentholds current and max health. - System: The logic that operates on entities that have a certain set of components. For example, a
PhysicsSystemwould look for all entities that have both aTransformComponentand aPhysicsComponentand update their positions.
By mixing and matching components, we can create any object imaginable.

The following video gives a great conceptual overview of ECS.
Entity Component System (ECS) - Definition and Simple Implementation
This short video by Dylan Falconer introduces the ECS pattern and its benefits. It clearly defines entities, components, and systems and explains why this pattern promotes flexibility.
Watch the first five and a half minutes (00:00 - 05:32). Focus on the definitions of Entity, Component, and System, and the core strengths of this approach: flexibility and composability.
3. Designing Our Entity and Component "World"
So, how do we implement this in C? The learning outcome is to "design a basic Entity struct". As we've just learned, in a pure ECS, an entity is just an ID. The "design" is therefore not about a single struct, but about the collection of data structures we use to manage all our entities and their components. We'll call this our World.
Our design will be as follows:
- An Entity will be a simple
typedeffor an integer ID. - Components will be simple C
structs containing only data. - Our World
structwill contain arrays for our component data. For every entity,world.transforms[entity_id]will be its transform component, and so on. - We'll use a bitmask to track which components each entity has. This is a very efficient, C-style approach.
Let's start defining this.
Step 1: Define Entity ID and Component Types
First, let's create a header file, say ecs.h, for our definitions.
// ecs.h
#ifndef ECS_H
#define ECS_H
#define MAX_ENTITIES 1024
// An entity is just an ID.
typedef unsigned int Entity;
// A bitmask of component types.
// We use powers of 2 to allow for bitwise operations.
typedef enum {
COMPONENT_NONE = 0,
COMPONENT_TRANSFORM = 1 << 0, // 1
COMPONENT_PHYSICS = 1 << 1, // 2
COMPONENT_RENDER = 1 << 2, // 4
// Add new component types here
} ComponentType;
#endif // ECS_H
By using powers of two for our enum, we can represent any combination of components with a single integer. For example, an entity with a mask of 5 (binary 101) would have COMPONENT_TRANSFORM and COMPONENT_RENDER.
Step 2: Define The World Struct
The World struct will be the central manager of our game state. For this basic design, we'll use fixed-size arrays.
// ecs.h (continued)
// First, forward-declare our component structs
// (we will define them properly in the next lesson)
struct TransformComponent;
struct PhysicsComponent;
struct RenderComponent;
typedef struct {
// The number of entities currently in use.
unsigned int count;
// A mask for each entity that tells us which components it has.
// The index in this array corresponds to the entity ID.
// e.g., masks[5] is the component mask for entity 5.
int masks[MAX_ENTITIES];
// Arrays of component data.
// The index corresponds to the entity ID.
struct TransformComponent* transforms[MAX_ENTITIES];
struct PhysicsComponent* physics_bodies[MAX_ENTITIES];
struct RenderComponent* renderables[MAX_ENTITIES];
} World;
// We also need functions to manage the world.
World* World_Create();
void World_Destroy(World* world);
Entity World_CreateEntity(World* world);
void World_AddComponent(World* world, Entity entity, ComponentType component_type);
Important Design Choice:
Notice that the World struct holds arrays of pointers to components, not the components themselves. This means if an entity doesn't have a certain component, its entry in the array will simply be NULL, saving memory. We will need to malloc components individually when we add them to an entity. This is a trade-off: it's slightly slower than having contiguous arrays of components, but much more memory-efficient for a sparse world where not all entities have all components.
Step 3: Implement World Management Functions
Now let's create ecs.c to implement these functions.
// ecs.c
#include <stdio.h>
#include <stdlib.h>
#include "ecs.h"
#include "components.h" // We'll create this file in the next lesson
World* World_Create() {
World* world = malloc(sizeof(World));
world->count = 0;
// Initialize all masks and pointers to 0/NULL
for (int i = 0; i < MAX_ENTITIES; ++i) {
world->masks[i] = COMPONENT_NONE;
world->transforms[i] = NULL;
world->physics_bodies[i] = NULL;
world->renderables[i] = NULL;
}
return world;
}
void World_Destroy(World* world) {
// Free any allocated components
for (int i = 0; i < world->count; ++i) {
if (world->transforms[i]) free(world->transforms[i]);
if (world->physics_bodies[i]) free(world->physics_bodies[i]);
if (world->renderables[i]) free(world->renderables[i]);
}
free(world);
}
Entity World_CreateEntity(World* world) {
if (world->count >= MAX_ENTITIES) {
// In a real engine, you might resize your arrays here.
// For now, we'll just error out.
fprintf(stderr, "Error: Maximum entities reached.\n");
return -1; // Return an invalid ID
}
// Find the next available ID
Entity new_entity_id = world->count;
world->count++;
// Ensure its mask is clean
world->masks[new_entity_id] = COMPONENT_NONE;
return new_entity_id;
}
// A simple example for adding a transform component.
// You would have similar functions for other components.
void World_AddTransformComponent(World* world, Entity entity) {
// Check if the entity ID is valid
if (entity >= world->count) return;
// Use bitwise OR to add the component to the mask
world->masks[entity] |= COMPONENT_TRANSFORM;
// Allocate memory for the component data
world->transforms[entity] = malloc(sizeof(struct TransformComponent));
// Initialize with default values (example)
// glm_vec3_zero(world->transforms[entity]->position);
// ...
}
This design fulfills our learning outcome. We have a way to represent any object (Entity), and its structure is defined by the components we add to it, which are managed by our World.
Conclusion
Today, you've taken a massive step in your engine's architecture. Instead of thinking about objects in rigid, hierarchical terms, you've designed a flexible, data-driven system based on composition.
Key Takeaways:
- Composition over Inheritance: Building objects by giving them components is more flexible for game development than using rigid inheritance trees.
- Entities are just IDs: In a component-based architecture, an entity is little more than a unique number used to index into data arrays.
- Components are data: They are simple
structs that hold information for a single aspect of an entity (e.g., position, physics, health). - A
WorldorScenemanages everything: A central struct holds the component data and the masks that link entities to their components. - Bitmasks are an efficient, C-style way to track which components an entity has.
Preview of the Next Lesson:
We've designed the scaffolding for our entity-component system. In the next lesson, we will put flesh on these bones by focusing on the learning outcome: Define core data components like Transform (position, rotation, scale) and PhysicsBody. We will create the actual struct definitions for these components and integrate them into the World we designed today.