Overview
This week I'm building on last week's post about Entity-Component-System (ECS) for game development. Composition over inheritence is the key takeaway. Composition enables building an object as a collection of behaviors, instead of inheriting behaviors from a single parent.
Inheritance is a core component of object-oriented programming and is very commonly taught as part of a computer science curriculum. A classic example is defining an "Animal" class, then a "Bird" class which inherits from Animal, and then you can make specific birds like "Sparrow", "Eagle", etc. as needed. Bird defines a "fly" method, then each type of bird can implement the specific differences in flight.
Inheritance solves the "is a" problem. A sparrow is a bird, which is an animal.
Problems with Inheritance
Inheritance requires very clean concepts. So, what happens when you want to implement a penguin that can't fly? You pass the bird class to some external method, and it calls "fly", what happens? Do you throw an exception? Do nothing? The penguin flaps his wings a few times and looks forlornly at the sky?
The reverse problem also exists: how do you define a helicopter that needs a "fly" method? It's not a bird, but do you try to make it one anyway so it can inherit "fly", or do you re-implement "fly"? What if you need to apply gravity to all flying objects?
Composition
Composition is the idea of separating out behaviors which can be grouped to form a new thing. It uses the "has" or "can" relationship instead of "is". A bird has wings. An airplane has wings. A sparrow can fly. A helicopter can fly.
To find every flying object, you can simply identify anything that "has fly". This can be implemented with interfaces, but with ECS it is implemented with components. It becomes very easy to do something which demonstrates all 3 parts of ECS, the entity, component, and system, like this:
//Entity
auto helicopter = createEntity();
auto sparrow = createEntity();
//Component
struct Flying{
vector3 position;
vector3 velocity;
}
sparrow.add_component();
helicopter.add_component();
//System
for(auto& flying_entity : ecs.query()){
flying_entity.velocity.y -= 9.81 * Time::deltaTime;
flying_entity.position += flying_entity.velocity * Time::deltaTime;
}
Game Dev with ECS
ECS makes game development particularly easy with composition. You can create a "Health" component, and make every attack from the player damage anything with Health, then it doesn't matter whether you've applied that component to an enemy, a door, some of the walls, etc. If you are doing a physics game then you can make a "mass" component with a gravity system that pulls everything with mass downward. For rendering you can find every component with a mesh and a world position and pass it to the renderer to be drawn each frame. The possibilities are literally endless, and the best part is that it's easy to keep clean separation between unrelated components.
If I create a new component and a new system to interact with that component, I already know that no other system is touching that component. Each component is usually limited to very few systems that interact with it.
Back to Home