Entity-component system (ECS)
Overview
Section titled OverviewEntity-component systems are typically used for games where you achieve certain behaviors/qualities for entities via composition of components.
- Entity: typically just a bundle of components and maybe an ID.
- Component: typically just data and incredibly simple functions to act on that data. E.g. in a game, a
LifeComponent
might only storelife
andmaxLife
, and the simple functions to act on that data may be things likegetPercentLife
orrestoreToFull
. - System: logic tying everything together. It usually concerns itself with specific entities, e.g. a
FireSystem
may only be interested with entities that have anOnFireComponent
.
Components do not know which entity they belong to. In other words, a LifeComponent
doesn’t have an Entity
reference within it. It’s up to the system to know how to act on a particular entity.
Benefits
Section titled Benefits- It’s easy to understand what an entity can do simply by its components. E.g. a monster in your game may have
HealthComponent
,DamageComponent
, andAiComponent
. The player may have the same but withInputComponent
swapped forAiComponent
. - You can increase cache coherence if you store component data in contiguous memory.
- Systems can be made to act in parallel even if they’re acting on the same entities.
- Data and logic are not co-located, which means that when you add something like a
TargetLocationComponent
, you also need to add aTargetSeekingSystem
. It’s tempting to just cram this logic directly into an entity, which would then make it harder to reuse. - You usually need “tag” components, which are components with no data, e.g.
IsPlayerComponent
. This is because entities otherwise have no concept of what they represent. This isn’t exactly a con, but it can be strange to have to manifest a class out of what would otherwise just be abool
.
Usage with Godot
Section titled Usage with GodotGodot decided not to use an ECS-first approach. You can still compose behaviors by adding nodes that act on their parents, meaning you don’t need an ECS if all you want is different behaviors on a particular node. E.g. there could be a MoveRandomlyComponent
which does something like GetParent().Position += randomVector
. These components could even register themselves with some central system so that you can fetch all entities with a certain type of component.
For Skeleseller, I decided to make an ECS library rather than always use Godot nodes. I don’t fully remember the reasoning behind this, and if I were to redo the project, I may have tried prototyping using Godot nodes.
Component
is an abstract base class that looks like this:
Any Godot node should be able to have components. There are a few ways to accomplish this:
- Add a
ComponentHolder
which inherits fromNode
to each entity’s scene tree. - Attach an
Entity
script to all of your custom nodes, then have that script store your components.
I went with the ComponentHolder
and use static functions in Ecs.cs
to interact with entities and add/remove/get components:
This system does provide any cache-coherence benefits since we have to look up the ComponentHolder
every time.