A new cool (simple and powerful) kid joined my (game) programming toolbox: Entity Component System (ECS).
It's not "new" (it's been around since 1998), but only now it landed on my purview as a game dev.
Entity Component System (ECS) brings composability to game development ๐ช Split data and logic, and just compose entities with what they need to operate Clean, composable and powerful ๐ฅ
And it's here to stay ๐
Ah, and remember when I discussed FP in game development last week? Well, you may not actually need it ๐
Problems with classical game dev (that you may not know)
Here is some code I have written for a game in Dart:
class Npc extends SpriteComponent
// ๐ Some behaviors from somewhere else
with
HasGameRef,
CollisionCallbacks,
FlameBlocReader<GameStateCubit, GameState> {
// ๐ Data all stored inside the entity
Vector2 _direction = Vector2(0, 1);
double speed = 200.0;
double stength;
bool isChanging = false;
PlayerStatus playerStatus;
final int id;
Npc(Vector2 position, {required this.id, required this.playerStatus})
: stength = Random().nextDouble() {
this.position = position;
_direction = Vector2.random() * (Random().nextBool() ? 1 : -1);
}
@override
void update(double dt) {
super.update(dt);
// ๐ Something "is changing" and mutates the game state
if (!isChanging) {
position.add(_direction * speed * dt);
}
}
@override
void onCollisionStart(
Set<Vector2> intersectionPoints, PositionComponent other) {
super.onCollisionStart(intersectionPoints, other);
// ๐ A bunch of checks for collisions all over the place
if (bloc.state.isEnded == null) {
if (other is Wall) {
_direction.invert();
} else if (other is Npc &&
!other.isChanging &&
other.playerStatus != playerStatus) {
if (stength > other.stength) {
other.changeStatus(playerStatus, false);
} else {
changeStatus(other.playerStatus, false);
}
}
}
}
}
This is your classic (broken) game dev workflow:
- A class that represents your game entity
- The class extends a bunch of behaviors from other classes (who knows what)
- The class encapsulates all the data and logic for that entity
- A series of (imperative) checks read the game state and decide what to do, often by mutating something in the world (even outside the entity i.e.
changeStatus
) - Each entity cares about its own behaviors, sharing features with inheritance
Read this all again. It's a recipe for disaster ๐ซ
This goes deep into OOP and mutability madness, quickly. And we are talking about a game here.
Games are complex. They aim to recreate slices of reality. Reality is a mess. And OOP doesn't help.
I define that workflow as "classic" since most game engines are based on it. You cannot escape it if you sneak your head into making a game.
FP rescued software development, what about game dev?
FP restores your sanity when you work with software (outside of games).
Patterns like composability, immutability, dependency injection, pure functions. These are all checks to help you scale (and local reasoning).
But the game dev world has been mostly untouched by FP. A game is a mutable thing, and having to keep everything pure and never changing brings more pain than it's worth.
Furthermore, game engines are mutable, all of them, mostly for performance and "usability" reasons.
Wrap it with FP it's just indiscreet ๐๐ผโโ๏ธ
Entity Component System to the rescue
But there is another path forward. It integrates with all game engines, it's simple, and brings composability back to game dev.
It's called Entity Component System (ECS) ๐
ECS implementation in TypeScript achieved ๐ซก TypeScript only, extensible, working with any renderer, type safe and composable ๐น๏ธ May be the beginning of something
Separate data from logic
The major pain point of OOP game dev is that logic and data are sandwiched together. It means that everything can change anything, data runs free and unrestricted.
In ECS data is all inside components, and logic is all inside systems.
Integrate physics with matterjs and ECS in your game ๐งฑ Each system as simple as it gets, a function and a query is all you need ๐
This simple pattern brings back the ability to compose logic and control mutations:
- Define components that encapsulate data
- Define systems that act on a subset of components
// ๐ Query all entities with position and velocity components attached
const moving = query({ position: Position, velocity: Velocity });
export const MovementSystem: SystemUpdate<GameEventMap> = ({
world,
deltaTime,
}) => {
// ๐ The system acts on everything that matches the query (all components required)
moving(world).forEach(({ position, velocity }) => {
position.x += velocity.vx * deltaTime;
position.y += velocity.vy * deltaTime;
});
};
Entities are just numbers
Where is your player here? Enemies? NPCs?
The mental model is flipped. Entities become containers of behaviors.
A "Player" is something that has a position, velocity, and input (all components).
These components define the concept of a "Player", and systems are responsible for making them do stuff.
You see composition here:
- Logic is all inside systems, and you can reason about them individually
- Entities only care about their components, what will happen to them is abstracted away
// `playerId` is just a number
const playerId = createEntity();
// A "player" is a container of components
// It has a position, velocity, sprite, etc.
addComponent(
playerId,
Velocity.idle,
initialPosition,
new Player(),
new Sprite({ sprite: playerSprite }),
new Collider({ body: playerBody })
);
I then published a package: @typeonce/ecs
I went deep into the rabbit hole, and emerged with a new package: @typeonce/ecs
.
@typeonce/ecs
makes ECS in TypeScript structured and type-safe. I am iterating on the API by building multiple games with it.
It's working great, bringing the excitement back to the game dev world.
Working Entity Component System with TypeScript in action ๐น๏ธ Simple architecture for organised code and powerful results ๐
Resources
Until I publish a more complete guide (๐), here are some resources for you:
- The Unknown Design Pattern
- Anatomy of a knockout
- Specs and Legion, two very different approaches to ECS
- A TypesScript ECS in 99 Lines of Code
- Introduction to Entity Systems
You can also ask AI about it. Since the pattern has been around for 20 years, AI seems particularly knowledgeable about it ๐ค
I already see how these patterns can be combined with everything happening on the web to build something magical ๐ช
You can follow the (undocumented yet) progress on Github.
Something is coming out of this, stay tuned ๐
See you next ๐