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
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.
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.
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
velocity component. We can also have a few static
box entities, which only have a
A basic system for running movement simulation would request all components of type
grouped by entity, and for each entity update the
velocity based on the effect of gravity, then update
position based on the current
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
is_dead and not render the entity if so.
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.
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.