On 6/28/2019 at 4:39 AM, Hodgman said:
OOP Inheritance view of ECS -- (n.b. don't ever do this )
class IComponent {};
class Entity { vector<IComponent> components; }
class HitpointComponent : public IComponent { int hp; }
Entity e;
e.components.push_back(new HitpointComponent());
Just to be awkward, I'm gonna enter a counterpoint to this, aimed more at the original poster than at Hodgman who doesn't need my advice.
There are some good reasons to say "don't ever do this" regarding code like the above, but both the Unreal Engine and the Unity Engine both work pretty much this exact way. They don't meet the more rigorous definition of what people mean by an 'ECS' - they're definitely component based systems but in a looser way. However, architectures like these have successfully supported shipping thousands of games. They work.
Short story - until recently, the 'pure' form of ECS was mostly the domain of idealistic bloggers and tinkering hobbyists, much-discussed on forums but rarely used in real world game code. The first big example of this changing was when the Overwatch team started discussing their technology and said it was based on an ECS system. You can (and should) watch the video on this (see bottom of this post). But what's this, at 4 minutes in?
Looks like each entity has a list of pointers to subclasses of a base component! Exactly what everyone says you shouldn't do!
Okay, fine. But what does the actual game logic look like?
So we iterate through one component, and use a Sibling function to get related components. In other words, their game logic explicitly relies on these connections between components as handled by the aggregation under an Entity. (Also, that's a member function on a Component, in addition to the Create member in the diagram above. So much for the idea that these are plain data objects.)
We don't know how they implemented the Sibling function but it doesn't matter that much - both the implementation and the gameplay code that uses it isn't going to end up looking much different from:
-
Unity - where you use GetComponent<ThatComponent>()
-
Unreal - where you might use GetOwner()->FindComponentByClass<ThatComponent>()
This approach is ugly but it works. That's why people keep coming back to it. You can't get away from the need to operate on multiple components at the same time to handle game functionality, and all the different approaches are really just different facades over this fact.
So what you need to consider is this:
-
What is my gameplay code going to look like, once I move past the toy examples where systems only operate on 1 or 2 components? What do I want it to look like?
-
How can I have safe, efficient, and convenient access to components, especially when entities and components may be getting deleted at runtime? Am I perhaps going to lose more performance on array-shuffling/complex indexing/tuple-building than I gain from any other aspect of the architecture? Do I know enough about my game to understand the tradeoffs?
-
Do I have enough knowledge and motivation to solve this problem properly, or should I maybe just focus on whatever is easiest to make a game?