Entity component system
I’ve been playing with Amethyst a little bit. It’s a library that leverages an Entity-Component-System Architecture (which I’ll refer to as ECS).
I think ECS is interesting (even if you, like me, don’t intend to do serious game dev) because:
- It’s completely unintuitive at first
- It flies in the face of (college taught) object oriented design
- It shows the difference between composition and inheritance
- It can improve your software design
- It can improve performance
Entities
Game entities are the basic nouns in your game. For example, maybe you have a player, a block of terrain, a sheep, an arrow.
A key part of an ECS is that these entities are incredibly simple, they’re just an ID (like an integer).
This is opposed to some other approaches that might have entities represented by a nested inheritance structure.
Components
Obviously you can’t do much without data associated with the identifier, so we add that through components.
Components are just dumb data. They do not contain logic. The most common component for a 2D game is likely a position component which just contains an X-value and a Y-value.
Now how do we find the position of a given entity? We can just use the entity ID as an index into a big array of all positions containing several 100 position components.
System
We have a way to represent an entity and data about that entity, now we just need to add in logic.
This is done through systems. Systems are essentially just functions that can ask for groupings of components to operate over. They can potentially mutate the components.
For example, assume we have hundreds of ball
entities in our environment. Each ball has a position
and
a velocity
component. We can also have a few static box
entities, which only have a position
.
A basic system for running movement simulation would request all components of type position
and velocity
,
grouped by entity, and for each entity update the velocity
based on the effect of gravity, then update
position
based on the current velocity
.
This would mean only the ball
entities actually move, while the platforms stay static.
If we assume each entity has a collision_shape
component and an is_dead
component, we can even
develop a system that requests groups of (position, collision_shape, is_dead)
, and checks if the collision
shapes collided. If they have, it can mark both collided objects as dead. Another renderer system
could read is_dead
and not render the entity if so.
Performance
What did all this reorganizing buy us? Well, for one thing, if there’s anything computer architecture likes, it’s predictable, sequential access patterns. Fundamentally this is because sequential iteration over a vector makes it really easy to figure out where future data is going to come from. This allows your CPU to prefetch data and prevent cache misses.
Now, rather conveniently, all of our data is in these nice long sequential vectors.
Organization
Another nice part of ECS is that adding and reusing functionality across entities is really easy. For example,
you likely will have an entity for your camera. Do you want a camera that moves according to physics rules you
implemented for a ball? Well, you can do that! Just slap a velocity
component on there and now you’ve got
your motion system running on your camera entity! Yes that’s a weird example, but it demonstrates the flexibility
inherent in this sort of architecture. Any entity can have any component with minimal refactor.