If you’re enjoying this tutorial, please consider supporting it by purchasing a book through one of the links on my site, such as this one ->
This is part of an ongoing series where we write a complete 2D game engine in C++ and SFML. A new tutorial is released every Monday. You can find the complete list of tutorials here and download the source code from the projects GitHub page.
If you’ve been following the tutorials up to this point, you’ll have a sprite that you can move around the screen using the keyboard. While this is a good start; we will, in the not too distant future, want to add additional functionality to our player object. At the very least we will want our player to have health, physics, a bounding box for collisions, and a way to animate its sprites. This is where the component system comes in, the focus of todays and next weeks tutorial.
The component design pattern helps to decouple systems as they are written and maintained separately. Instead of creating a huge class/inheritance hierarchy containing all the systems that represent a player; we create individual, flexible components, and then mix and match these to create the desired object (e.g. player, projectile, enemy etc.). To do this we will write separate classes that encapsulate the behaviour we want to implement, we call this functionality ‘components’.
If you’ve used the Unity engine before then you have almost certainly used components. Most classes you write are attached to a GameObject as a component. The screenshot below shows the default Unity camera object with its various components.
As a side note, the way we implement components differs somewhat from an Entity Component System (ECS) that you may have heard of/implemented before. An ECS consists of three main systems:
- Entities: this normally provides a method of uniquely identifying a collection of components and systems as belonging to one object. We’ll implement this shortly as an ‘Object’ class.
- Components: in an ECS model these are usually simple data objects that do not contain any complex logic. In our model, our components can be as complex as we like.
- Systems: this is usually where all the logic and complexity is situated. It will perform actions using the data found in the components. In our implementation, we do not write separate systems as our components will contain all the logic they need.
Before we create our components lets start on our Object class. This is similar to the ‘Entity’ class in the ECS model and it will contain and maintain a list of our components for each object.
These methods will soon loop through all of the components added to our object and call the relevant Update and Draw functions. But before we can implement them we need to create our Component class, which will become the base class of all future components.
1. As our Object requires a reference to Component and Component requires a reference to Object; we forward declare Object.
Now we have our Component class, lets write the code to add, and retrieve components in our Object class.
1. As AddComponent and GetComponent are template functions, i’ve implemented them in the header. These functions will work with any class that inherits from Component and when we implement our first components it will become clear how we use these functions.
2. Both template functions make use of dynamic casting. This will cast a superclass to a subclass. As we are using smart pointers, if the cast is invalid it will return an empty shared ptr.
Currently we only allow a user to add a component of a certain type once. In future we may want to be more flexible and allow the user to add and retrieve multiple objects of the same type.
With our component data structure implemented, we can write our Awake, Start, Update, LateUpdate, and Draw methods of our Object class.
We loop through our components in reverse order as the component vector can be changed in our update or draw calls with components adding/removing other components. If we looped in the normal way (start to end), removing a component will cause our index to point to what it thinks is the last component but is in fact memory address space it no longer owns.
To see how these systems work we first need to create a component. Lets start by creating a simple sprite component. It will, as the name suggests, draw a sprite to the screen. We’ll call the class C_Sprite. I prepend any component class with ‘C_’ to differentiate them, but you do not need to follow this convention.
Now with our component system mostly complete, and one component ready to test, lets make some changes to our SceneGame class to re-create our player character using the new component system.
Include our new classes:
We no longer need a reference to our viking sprite as we have a component to do that so replace the lines:
With our new player object:
We now need to update our OnCreate method to remove the references to our recently deleted texture and add code to instantiate our new player object and add our first component.
This is how to add components to objects. When adding a component it returns a reference to the newly created component, in this example, once we have the reference to the sprite component, we pass it a file path so we can load a sprite.
As we no longer have references to the sprite in the SceneGame class we need to delete the movement code in our Update method.
And draw our player in the draw method:
Now if you run the game you’ll have the sprite being drawn but you can’t move it (as we’ve removed the movement code). So while it may seem like a step backwards, we have in fact improved our code and prepared it for future expansion. In the next tutorial we will re-implement the movement code in our new component system.
As always, if you have any suggestions for what you would like covered or are having any trouble implementing a feature, then let me know in the comments and I’ll get back to you as soon as I can. Thank you for reading 🙂