In the classic book on object-oriented design patterns, the Gang of Four stated an overarching principle: "favor composition over inheritance". Managing a large type hierarchy has many significant downsides, which can be circumvented by implementing complex objects as an aggregation of components. This approach has recently been catching on, as several new programming languages such as Go and Rust, eschew inheritance altogether. Also, many game studios are beginning to adopt a composition-based design pattern known as the entity-component model.
In an entity-component system, an object is represented by a unique ID and a container of components. One of the myriad benefits of this model is efficiency. Consider the typical case where you have a parent class, of which all game objects derive from, which defines a common element such as velocity. Every game object must then manage velocity on every game update, even if the object is stationary. The alternative is to implement velocity in the subclasses, which defeats one of the main purposes of inheritance (code reuse). Using an entity-component system, velocity is implemented as a component. An instance is registered with the ID of all non-stationary objects. Problem solved.
C++ is an extremely versatile programming language, which means there are quite a few ways one could implement this model. In the main game class, Breakout3D, I created a System (a map of components of a single type) for each type of component in the game. Some (not all) of the components I used were:
- PositionComponent
This component simply contains a vector specifying the location of the object in world space. Most components depend either directly or indirectly on this component.
- VelocityComponent
This moves the objects position component once every game update. An object without this component is stationary.
- RenderComponent
A render component contains the mesh and material properties of an object, which is then used to render each object with OpenGL. An object without this component is invisible.
- BoundingBoxComponent
This component allows other components to collide with this object. If a collision occurs, each object with a collision component will have its OnCollision method called.
- LifetimeComponent
This component contains a counter that is decremented every game loop. When it reaches zero, the object is removed from the system.
See the
code for more information.