The problems of inheritance
While inheritance as an OOP approach of sharing the common behaviour between classes is not necessarily bad on it’s own - it may prove to be quite problematic when handling a more complex and big code base.
It is also an easy way to jump into some gotchas which put light on tightly coupled and not well-though-out areas in our code.
But to explain my point further I’ll use an example of a simple and naive approach with the usage of class hierarchies.
Let’s say we have a simple shoot-em-up game where our player shoots different kinds of bullets depending on the current weapon he has.
We could achieve it by implementing an abstract
Bullet class - for handling all the bullet movement and collisions with enemies - along with explicit classes for things like
ExplodingBullet or even some kind of
It will work fine until we find ourselves trying to implement some kind of
HomingExplodingBullet, because why the heck not.
You may see where it’s heading - we already have a
HomingBullet and an
ExplodingBullet so we cannot for example inherit from both of them1.
So the question is - what now? We may copy and combine code from both the classes and create a third one with combined behaviour but that just stinks, and violates the DRY (Don’t Repeat Yourself) principle.
We could resolve the issue in other ways, including some object creation pattern - for example the Builder pattern - to compose the behaviour of our game objects in some modular way, but what I find to be best here is to use the Component pattern.
Say hi to components
Component pattern introduces a different approach to defining and creating our game objects with a heavy reliance on composition.
Game objects here are simply containers for components.
What defines our game object to be a chair, a sword or an orc is not the class now, but the list of components that are inside our game object.
It may look a bit overly complex at first, or maybe even useless but it gives us a simple but powerful tool to apply good programming principles and even share common behaviour between objects with ease.
The simplest example of such game object could be defined as follows:
We have a list of
Component objects which is more or less an abstract class defining the common interface for components such as the
GameObject handles the process of adding the components so we cannot directly operate on the collection, and now calling
Update on it propagates the message to it’s components which handle all the heavy lifting.
Given this code, we would now create our game objects and then simply add all the components we need.
This may prove to be tiresome so some mechanism to simplify it would be a nice addition.
- We could go the way of writing schematics for object construction using for example XML, and then creating them during the game/level load.
- We could create specific game objects inheriting from the base class and adding components in their constructors.
- We could implement custom attribute for our classes that define given object’s components and then our base
GameObjectclass would also contain the logic to handle initializing them, which cleans up the messy adding and instantiating inside specific game objects’ constructors. This approach may be known to developers using the Unity engine, where we have the
These of course are just some of the possible solutions which should give you idea about how you want to approach it. When it comes to me, I have chosen to go with the third option and implement a custom attribute (which allowed me to actually learn how those are defined), similar to how Unity does it.
The implementation is actually quite straightforward.
Firstly, let’s define the
RequireComponent for containing information about what components we want our game object to have.
The attribute usage will be restricted to Classes, applying it anywhere else doesn’t make much sense. We also allow multiple instances of the attribute to present on the class.
Inside, we have a simple constructor that accepts
params Type as it’s only argument.
Having such information about our desired components we will need to instantiate them using reflection.
The code for that is called inside the constructor of our
GameObject class. What we do is simply loop through all of the
RequireComponent attributes (we allow multiple of them, remember?). Next, we loop through all of the types of components we declared to be required for the game object and instantiate them one by one.
This gives us a simple and effective way to work with the components pattern and use them with ease in our projects.
Wrapping it up
There is of course a big room for improvement. For example in the Unity engine, we can put the
RequireComponent attributes on Components too, allowing for some relations between components to be present.
I have already implemented such mechanism inside the
Component base class, and I also implemented methods for getting Component(s) of given type from the game object.
One more difference though is a condition check to not instantiate duplicates - we check if our game object already has a component of such type.
If we would by some chance want duplicated components - that would need to be handled manually.
It’s also worth to note that our game objects may be something more than just component containers. We may put some basic, universal data in them, such as the position in the world space or an abstract method declaration for rendering (unless of course some game objects would be not renderable?).
That’s it for this post. I hope you enjoyed it and maybe learned something new along the way - just like I finally had a chance to implement a custom
Attribute subclass in C#.
In the next post I’ll quickly showcase how the components are already put to use to handle input on a simple test game object representing the player and how the input is actually handled in code.
I know there are languages with multiple inheritance like C++, but for the sake of simplicity let’s say we do not have it (which is a more common case). Also, the multi-inheritance approach is even not recommended and may probably bite us in the ass in the future when things get even more hairy - for example when we try to combine behaviour of three or four already implemented classes. ↩