For one of my game programming classes at Ohio State I decided to implement an Entity-Component-System (ECS). A widely used pattern in game design, an ECS allows for a simpler gameobject pattern then polymorphism would. The reasons for this are discussed elsewhere[1], this blog post is solely for implementation.
§ Component
I'm covering Components before Entities as this is where the entire game is implemented.Components describe any behavior shared between many Entities. Examples include Transform
, Collider
, Sprite
, Script
, etc. I modelled my implemetation off of the Unity style of ECS, as that's what many game programmers are used to. The implementation of the Component
base class is exceedingly simple:
class Component
{
public Entity entity;
public virtual void Update(GameTime gameTime) { }
}
Each component type subclasses Component
. I'll include a Transform
type below as an example:
class Transform : Component
{
public Vector2 position = Vector2.Zero;
public Vector2 scale = Vector2.Zero;
public float layerDepth = 0;
public float rotation = 0;
}
§ Entity
Entities are the class for any given "thing" in the scene. Your player, their weapon, the enemies, the walls, all are "entities" in this model. My Entity
class starts with the properties below:
class Entity
{
public int ID { get; set; }
List<Component> components = new List<Component>();
...
}
The ID could be a string or UUID, but an int
will work just fine for the 2D sprite-based game I'm making in this class. The components
property references all the behaviors an entity has. Let's add some methods to this class to add a Component
to the Entity
:
class Entity
{
... // class properties
public void AddComponent(Component component)
{
components.Add(component);
component.entity = this;
}
}
This method is pretty self-explanatory, the tricky (generics) part is what comes next- retrieving components. But first, why would we need to retrieve components?
§ Sidebar: Example Character Entity
If we are implementing a normal character, we'd subclass the Entity
class and register components on it like so:
class MyAwesomeCharacter : Entity
{
public MyAwesomeCharacter(Texture2D tex)
{
// add a `Transform` component to store the character's position
Transform transform = new Transform();
transform.position = new Vector2(100, 100);
AddComponent(transform);
// add a `Sprite` component to store the character's texture
Sprite sprite = new Sprite();
sprite.texture = tex;
AddComponent(sprite); // Assume `Sprite : Component`
}
}
Cool! Now we have a character that stores both a Sprite
and a Transform
. Now wouldn't it be cool if the Sprite
could know where on the screen to draw by accessing the value of Transform
? This is where generics come in.
§ Retrieving Components
In our implementation of the Sprite
component above, we'd like to be able to access the values of the attached Entity's Transform
component. It is pertinent to mention that each Entity
should have only one of each type of Component
.
class Sprite : Component
{
Texture2D texture;
public virtual void Update(float gameTime) {
// We'd like to do something like this:
Transform t = entity.GetComponent<Transform>();
GameEngine.DrawSprite(texture, t.position); // assume the fictitious GameEngine class
}
}
How would we implement this generic GetComponent
call? It's actually not too bad using C# generics. See below:
class Entity
{
... // class properties and AddComponent method
public T GetComponent<T>() where T : Component
{
foreach (Component component in components)
{
if (component.GetType().Equals(typeof(T)))
{
return (T)component;
}
}
return null;
}
}
This just magically works. Well, it's not magic, C# just has nice reflection capabilities built in. This will allow any component to access any other component on the same Entity.
§ Component Conclusion
We now have a class Component
that we can use to make endless reusable components that may be useful for our entities, and these components can interact with each other seamlessly.
§ System
There's one more part of ECS- the S. All game run in what's called a game loop, usually implemented as some sort of infinite while loop that runs every frame, updating the entire game logic then rendering to the screen. The System
in ECS refers loosely to calling Update
on each and every Component
on each Entity
in the scene. We could simply do the following:
foreach(Entity entity in scene) // assume scene is a list or something iterable
{
foreach(Component component in entity.components)
{
component.Update(deltaTime)
}
}
This works, but is slow. You of course won't run into any issues with this in a small game with 1000 objects or so but on a larger project with a couple extra orders of magnitude and this will begin to crawl.
§ Sidebar: CPU Cache Misses
A cache miss can occur when the data referenced by an instruction isn't found in the CPU cache, so it has to be loaded from memory (this is sloooow). There are plenty[2] [3] of excellent presentations on why this is bad and how to address this issue, but the jist of it is that we need to store each type of Component
in its own system. For example, when processing many collider components, the CPU can have them all in cache at once, instead of having the whole entity.
§ System Class
To address the performance concerns of an ECS, we can do some object-oriented trickery. I created a BaseSystem
class then subclassed said class for each type of Component
I wish to support.
class BaseSystem<T> where T : Component
{
protected static List<T> components = new List<T>();
public static void Register(T component)
{
components.Add(component);
}
public static void Update(GameTime gameTime)
{
foreach (T component in components)
{
component.Update(gameTime);
}
}
}
class TransformSystem : BaseSystem<Transform> { }
class SpriteSystem : BaseSystem<Sprite> { }
class ColliderSystem : BaseSystem<Collider> { }
Now, if we make a change to our component implementations:
class Transform : Component
{
... // properties
public Transform() {
TransformSystem.Register(this);
}
... // methods
}
... we've allowed for the CPU to pull only the components it is processing. In our main game loop we can run the following for any types of components we are using:
TransformSystem.Update(gameTime);
SpriteSystem.Update(gameTime);
ColliderSystem.Update(gameTime);
This will still update all our components but with better performance then the naive solution. As an added benefit, we can now be sure we update all colliders before we render all sprites, and ensure other gameplay-critical orderings remain intact.
§ Addendum
The complete code for this Entity-Component-System can be found on Github. Please let me know if there's anything I can do to improve this ECS implementation!
§ Footnotes
The fantastic article on ECS at cowboyprogramming.com ↩︎
Bob Nystrom's talk on the downsides of ECS and why cache misses need to be addressed youtube.com ↩︎
Elizabeth Baumel's talk for Unity on data-oriented design youtube.com ↩︎