Component based entity systems are fantastic and are recognized as the standard for managing the game logic. They remove complex inheritance trees and replace them with simple flat layout.
There are many good articles on the subject of game entity systems out there, yet many developers repeat the same mistakes and that is why I have decided to write this article and name it as it is.
In this article I will briefly go over "classic entity systems", I'll then explain "component based entity systems" and finally I will highlight the point where I feel so many people fail to use them correctly.
If you are already familiar with component-based entity systems feel free to skip to "Component Systems Misuse".
Component-based entity systems came to solve a problem in a commonly used system. Let's have a quick look at classic systems and remind ourselves what we tried to solve.
Classic entities used to be defined by a single Entity or GameObject class which would hold the basic information every entity would have, such as transformations, name and id.
Depending on the game, the entity class would be extended to add additional functionality such as rendering, physics and AI.
This is where problems start to surface, questions about the hierarchy such as "does drawable entity inherit from physics entity? What if physics entity inherited from drawable and a zone or trigger shouldn't be rendered?" and it gets more complicated as you start adding game logic and want to make complex entities and behaviors.
Take the magical dancing blade for example. It might initially behave like a static item but when activated act like a character. Does it inherit from item and duplicate functionality of a character?
Problems like this make it very difficult to add complex entities into a game, they lead to a lot of duplicated code and are a nightmare to maintain.
That's where component based systems come in.
To untangle the hierarchy problems involved with entity systems a flat component-based design was devised. In this system the Entity class is not inherited from; instead, it contains a list of components which define its behavior.
The component class is extended to add specific functionality such as physics, rendering and AI. The entity simply hosts the relevant components to exhibit a desired behavior, kinda like game behavior mix & match organization.
This makes it a lot easier to add new functionalities and maintain the code. No longer do you need to traverse a complicated hierarchy tree to find an obscure behavior or maintain multiple copies of the same code used by strangely similar entities.
This makes component based entities cleaner, more powerful and easier to work with.
Component-based entity systems are very common these days but are also frequently misused. This is probably due to the similarity of components and previous entity systems or maybe just the simple desire to save time during development.
Components should act as a blueprint with only a description for the system implementing it. The biggest mistake developers make is putting all the functionality inside the components, such as update and draw.
There are multiple reasons for this and the biggest is performance.
Hopefully you are familiar with the term data-oriented design; if not I suggest learning about it, but here's a short overview:
The way data is laid out in memory affects the performance of your program.
When the CPU attempts to access data it is not enough for it to be in RAM, the memory needs to be in the cache which is small yet fast memory that is part of the CPU. When the data isn't available in the cache you get a miss and need to copy it from RAM. This costs performance.
Inheritance also has a performance cost. When a virtual function is called it needs to be found because a function also lives in memory. Imagine iterating through a list of entities and then through each of the components of every entity, calling OnUpdate. The CPU is trying to find the correct function for each implementation, calls the function and then moves to the next component and starts all over again.
This is why having a system that iterates an array where the data is laid in a single block of memory is faster.
If you are interested learning more about Data-Oriented Design, check out these slides.
This section probably deserves its own article but hopefully I can deliver the message.
Most modern game engines are multi-threaded and multi-threading is much easier when you know the exact layout of memory and how it is accessed. Take these two scenarios:
Scenario One You have a list of entities. You divide it among threads, each thread iterates through entities and components, calling their update functions. One thread calculates a collision between two physics components but another thread decided the second entity should be removed from the game because the character hp is zero. This is already a mess.
Scenario Two The engine is divided into systems, it is now the physics system's turn to run. It divides its array of physics objects between different threads and calculates velocities and new positions. The only problem you need to think about is how to handle collisions.
The second scenario is much cleaner and easier to work with. It's also less likely to suddenly break in the future when new components are added and therefore easier to maintain.
This is debatable but it tends to be easier to debug systems which are designed to do one specific task like physics and AI. Debugging components can get complicated if they rely on functionalities from parent classes or dependancies on other components.
As usual with programming, good design can go a long way to improving performance and making sure everything runs together.
Components should only have the description needed by the system which does the computation. The system will then create its own internal version of the object and update it when the system is called.
An example physics system will work like this:
As mentioned before, this design makes it a lot better for performance and multi-threaded implementation. This design should be the same with other systems like Rendering and AI. Next time something breaks down you know exactly where it is!
Hopefully you now have a good idea of how component-based entity systems work and why they are so powerful.
Before you go and try to implement everything from this article remember you should still think about your development cycle and not fall into premature optimization. If you are making a simple game and aren't pushing the hardware to its limit anyway then you might be better off with what you already have. See what works for you and decide.