Games seem like a great application for OOP - there are lots of game objects interacting with each other and updating their state accordingly. And it's true - many great games have been written this way.
But there are also some glaring flaws with OO design.
If you just want to know about ECS and not the problem with OOP that gave birth to ECS, you can skip to the Composition section.
It's one of the first concepts taught to students learning OOP, and for good reason - it's one of the foundational pillars of OOP.
I imagine an explanation would go something like this:
Dogs and cats are both animals. They share some characteristics, such as having legs, while others differ, such as cats having whiskers but dogs not. To express this,
Catare both classes that inherit from
Animal. In this way, the shared code (such as legs) are implemented in the
Animalclass, which helps prevent code duplication. Similarly, code common to all dogs would go in the
Dogclass. This helps prevent code duplication between classes such sa
Poodlewhich are both
Sounds great! We can successfully apply DRY (Don't Repeat Yourself) in an intuitive way using inheritance trees!
Say we have
Donkey classes. A
Mule is an animal which has a horse mother and a donkey father. (Yes, they exist!)
Where would a mule go in the inheritance tree?
It has characteristics of both horses and donkeys, as well as its own. If it inherits from
Horse, then we have to duplicate the
Donkey code, and if it inherits from
Donkey, then the
Horse code is duplicated.
There must be a way to inherit from both, right?
What if a class could inherit from multiple parent classes? This is known as multiple inheritance.
Could this be the solution?
This is known as the Deadly Diamond of Death. It poses the question: If class D's parents both have a method with the same name, which method is inherited by D?
There's no clean solution to this problem so most languages don't support multiple inheritance.
The other way to go about things in OOP is composition.
Composition is composing a class by building it out of several smaller, more specialised component classes. This allows for more flexibility and completely solves the inheritance problem. By simply picking and choosing the functionality needed, inheritance is avoided altogether.
The outer class no longer has any of its own functionality, so I'll refer to it as the container class.
However, there's now a new problem.
Say we have an object with
Velocity components. Where would the function
updatePosition live? A function has to be bound to one component, even though in many cases, like this one, the action it performs involves many components.
Finally, we arrive at ECS, the brilliant solution to all of the problems above.
Instead of components having methods, why not have an external function update the components instead? This would mean that the function isn't bound to any one component, but instead is an outside force which can affect all components involved in said action.
This is the core idea of ECS.
ECS is made up of three main parts:
- Entity, which holds components.
- Component, which holds data.
- System, which performs operations on entities and components.
Systems only operate on entities that have certain components, for example, the
Movement system would only care about entities with
Velocity components. This means that there are no more dependencies, since any entity can have any combination of components and only those with a matching set will be affected by any system.
ECS is definitely a fresh way to think about many problems, and has great potential.
I'm the author of WolfECS, a new ECS framework for written in Typescript. It's the fastest web-based ECS implementation I know of. WolfECS abstracts away a lot of the underlying implementation of ECS so all you have to worry about is your components and systems. https://github.com/EnderShadow8/wolf-ecs