When we started our component system, we created Awake and Start methods for our objects. However, they are not yet being called anywhere. We need to fix this before next weeks tutorial (where we begin our animation system) so the first part of this tutorial will focus on that. Also, we are currently responsible for maintaining, updating, and drawing our objects ourselves. We only have one object at the moment so it's not really a problem, but we do not want to have to manually call update and draw functions when we have 100+ objects. Therefore we will also create a structure to do just that, the ObjectCollection.

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.

When we started our component system, we created Awake and Start methods for our objects. However, they are not yet being called anywhere. We need to fix this before next weeks tutorial (where we begin our animation system) so the first part of this tutorial will focus on that. Also, we are currently responsible for maintaining, updating, and drawing our objects ourselves. We only have one object at the moment so it’s not really a problem, but we do not want to have to manually call update and draw functions when we have 100+ objects. Therefore we will also create a structure to do just that, the ObjectCollection.

There’s no background information required to understand the concepts in this weeks tutorial so let’s get coding straight away by creating our ObjectCollection.

ObjectCollection.hpp
#ifndef ObjectCollection_hpp
#define ObjectCollection_hpp

#include <memory>
#include <vector>

#include "Object.hpp"

class ObjectCollection
{
public:
    void Add(std::shared_ptr<Object> object);
    
    void Update(float deltaTime);
    void LateUpdate(float deltaTime);
    void Draw(Window& window);
    
    void ProcessNewObjects(); // 1
    
private:
    std::vector<std::shared_ptr<Object>> objects;
    std::vector<std::shared_ptr<Object>> newObjects; // 2
};

#endif /* ObjectCollection_hpp */

1. We separate the processing of new objects into its own method so it can be run at a convenient time (normally the beginning or end of a frame). This method will be responsible for moving objects added that frame from a temporary new object collection to the main object collection so they are updated, drawn etc.

2. This is used to temporarily store recently added objects so that we can choose when they are added to the game by invoking the ProcessNewObjects. This will become clearer shortly when we write the method.

The Update, LateUpdate, and Draw methods are straightforward. They will simply loop through all objects contained in the objects collection and call the methods of the same name within the objects.

ObjectCollection.cpp
void ObjectCollection::Update(float deltaTime)
{
    for(auto& o : objects)
    {
        o->Update(deltaTime);
    }
}

void ObjectCollection::LateUpdate(float deltaTime)
{
    for(auto& o : objects)
    {
        o->LateUpdate(deltaTime);
    }
}

void ObjectCollection::Draw(Window& window)
{
    for(auto& o : objects)
    {
        o->Draw(window);
    }
}

When we add an object we insert it into a new objects list. This list is processed by the ProcessNewObjects function.

ObjectCollection.cpp
void ObjectCollection::Add(std::shared_ptr<Object> object)
{
    newObjects.push_back(object);
}

void ObjectCollection::ProcessNewObjects()
{
    if (newObjects.size() > 0)
    {
        for (const auto& o : newObjects)
        {
            o->Awake();
        }
        
        for (const auto& o : newObjects)
        {
            o->Start();
        }
       
        objects.assign(newObjects.begin(), newObjects.end());

        newObjects.clear();
    }
}

We will call ProcessNewObjects at the beginning of the frame (although this can be any other convenient time). When we process new objects we need to call there Awake and Start methods, which resolves our first issue (the Awake and Start methods are now being called when an object is added to the game). Lastly, we copy the new objects to our objects list so we can update and draw them in next frame.

We’ll add the ObjectCollection to our game scene class. There may come a time when we need to add an object to a number of collections, if/when that happens we will look into how we can implement that; but until then each scene will have its own object collection, which will contain unique objects for that scene.

SceneGame.hpp
…
#include “ObjectCollection.hpp"

class SceneGame : public Scene
{
…
private:
…
    ObjectCollection objects;
};

Make sure you delete the reference to the player as we’ll be using our new object collection.

We’ll once again change our update and draw methods in our game scene. Rather than update and draw the player directly, we’ll update/draw our object collection. Now, whenever we add an object to our collection we no longer need to manually call its update/draw methods.

SceneGame.cpp
void SceneGame::Update(float deltaTime)
{
    objects.Update(deltaTime);
}

void SceneGame::LateUpdate(float deltaTime)
{
    objects.LateUpdate(deltaTime);
}

void SceneGame::Draw(Window& window)
{
    objects.Draw(window);
}

We’ll change the OnCreate function so that it creates a new player object and adds it to our collection.

SceneGame.cpp
void SceneGame::OnCreate()
{
	// We now need to temporarily store a reference to the player as 
	// its no longer a class variable.
    std::shared_ptr<Object> player = std::make_shared<Object>(); 
    
	// Add the components.
    auto sprite = player->AddComponent<C_Sprite>(); 
    sprite->SetTextureAllocator(&textureAllocator);
    sprite->Load(workingDir.Get() + "viking.png");
    
    auto movement = player->AddComponent<C_KeyboardMovement>();
    movement->SetInput(&input);
    
	// Don’t forget to add the player to the object collection.
    objects.Add(player); 
}

If you run the game you’ll notice that the player is not being drawn. This is because we are adding the object to the collection but we are not calling the ProcessNewObjects function that moves the object from the new objects collection to the actual collection that is updated and drawn. We’ll process the new objects at the start of a new frame before we do anything else.

SceneGame.cpp
void SceneGame::Update(float deltaTime)
{
	// Add the below line to process our new objects at the 
	// beginning of each frame.
    objects.ProcessNewObjects(); 
    
    objects.Update(deltaTime);
}

Now when we run the game the Viking is back again!

So we can now add objects but what happens when we want to remove an object? We will need a way to flag an object as ready for removal and then create a similar function as ProcessNewObjects but for processing removals.

Creating a removal flag can be accomplished with a simple bool and some accessors/mutators in our Object class.

Object.hpp
class Object
{
public:
…

    bool IsQueuedForRemoval();
    void QueueForRemoval();
    
private:
…
    bool queuedForRemoval;
};

Object.cpp
Object::Object() : queuedForRemoval(false) // Set queuedForRemoval to false
{
    transform = AddComponent<C_Transform>();
}

void Object::QueueForRemoval()
{
    queuedForRemoval = true;
}

bool Object::IsQueuedForRemoval()
{
    return  queuedForRemoval;
}

This is a flag that will be used to determine if an object should be removed from a collection. Let’s create a new method in ObjectCollection that will process removals.

ObjectCollection.hpp
class ObjectCollection
{
public:
…
    void ProcessRemovals();
};

ObjectCollection.cpp
void ObjectCollection::ProcessRemovals()
{
    auto objIterator = objects.begin();
    while (objIterator != objects.end())
    {
        auto obj = **objIterator;
        
        if (obj.IsQueuedForRemoval())
        {
            objIterator = objects.erase(objIterator);
        }
        else
        {
            ++objIterator;
        }
    }
}

This loops through all of our current objects and if the removal flag is set it is removed from the collection. We need to call this method in our game scene at a suitable moment. Let’s do this just before we process new objects in the Update method.

SceneGame.cpp
void SceneGame::Update(float deltaTime)
{
    objects.ProcessRemovals(); // Processes any object removals.
    objects.ProcessNewObjects();
    
    objects.Update(deltaTime);
}

With this implemented, if we want to remove an object from the game, it is as simple as calling the QueueForRemoval method on that object (we’ll demo this in a later tutorial). It should be noted that if any of our classes keeps its own reference to an object it will no longer be drawn or updated by the collection but because we are using smart pointers you will still be able to access the various components of the object and even manually update/draw the object if need be.

By creating a class responsible for objects, we can: have different collections for different scenes, which will allow us to: easily draw/update different objects depending on the scene; manage when objects are added and removed from a scene; and have a centralised place for storing objects.

It’s also worth briefly mentioning that currently we are creating new objects and erasing them as need be. This is very rarely the best way to do this and in future tutorials, we will definitely be looking into implementing object pooling. You can see an example of an object pool that I wrote in Unity here.

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 🙂