Chapter 20: Game Objects and Components

In this chapter, we will be doing all the coding related to the Entity-Component pattern we discussed at the beginning of the previous chapter. This means we will code the base Component class, which all the other components will be derived from. We will also put our new knowledge of smart pointers to good use so that we don't have to concern ourselves with keeping track of the memory we allocate for these components. We will also code the GameObject class in this chapter.

We will cover the following topics in this chapter:

  • Preparing to code the components
  • Coding the Component base class
  • Coding the collider components
  • Coding the graphics components
  • Coding the update components
  • Coding the GameObject class

Let's discuss the components a bit more before we start coding. Please note that, in this chapter, I will try and reinforce how the Entity-Component system fits together and how all the components compose a game object. I will not be explaining each and every line or even block of logic or SFML-related code that we have seen many times already. It is up to you to study these details.

Preparing to code the components

As you work through this chapter, there will be lots of errors, and some of them won't seem logical. For example, you will get errors saying that a class doesn't exist when it is one of the classes you have already coded. The reason for this is that, when a class has an error in it, other classes can't reliably use it without getting errors as well. It is because of the interconnected nature of all the classes that we will not get rid of all the errors and have executable code again until near the end of the next chapter. It would have been possible to add code in smaller chunks to the various classes and the project would have been error-free more frequently. Doing things that gradually, however, would have meant constantly dipping in and out of classes. When you are building your own projects, this is sometimes a good way to do it, but I thought the most instructive thing to do for this project would be to help you get it built as quickly as possible.

Coding the Component base class

Create a new header file in the Header Files/GameObjects filter called Component.h and add the following code:

#pragma once

#include "GameObjectSharer.h"

#include <string>

using namespace std;

class GameObject;

class Component {

public:

    virtual string getType() = 0;

    virtual string getSpecificType() = 0;

    virtual void disableComponent() = 0;

    virtual void enableComponent() = 0;

    virtual bool enabled() = 0;

    virtual void start(GameObjectSharer* gos, GameObject* self) = 0;

};

This is the base class of every component in every game object. The pure virtual functions mean that a component can never be instantiated and must always be inherited from first. Functions allow the type and specific type of a component to be accessed. Component types include collider, graphics, transform, and update, but more types could be added in line with the requirements of the game. Specific types include standard graphics, invader update, player update, and more besides.

There are two functions that allow the component to be enabled and disabled. This is useful because a component can then be tested for whether it is currently enabled before it is used. For example, you could call the enabled function to test whether a component's update component was enabled before calling its update function or that a graphics component is enabled before calling its draw function.

The start function is probably the most interesting function because it has a new class type as one of its parameters. The GameObjectSharer class will give access to all the game objects after they have been instantiated with all their components. This will give every component in every game object the opportunity to query details and even obtain a pointer to a specific piece of data in another game object. As an example, all an invader's update components will need to know the location of the player's transform component so that it knows when to fire a bullet. Absolutely any part of any object can be accessed in the start function. The point is that each specific component will decide what they need and there is no requirement during the critical game loop to start querying for the details of another game object.

The GameObject that the component is contained in is also passed to the start function so that any component can find out more about itself as well. For example, a graphics component needs to know about the transform component so that it knows where to draw itself. As a second example, the update components of the invaders and the player's ship will need a pointer to their own collider component so that they can update its location whenever they move.

We will see more use cases for the start function as we progress.

Create a new source file in the Source Files/GameObjects filter called Component.cpp and add the following code:

/*********************************

******THIS IS AN INTERFACE********

*********************************/

As the Component class can never be instantiated, I have put the preceding comments in Component.cpp as a reminder.

Coding the collider components

The Space Invaders ++ game will only have one simple type of collider. It will be a rectangular box around the object, just like those we had in the Zombie Apocalypse and Pong games. However, it is easily conceivable that you might need other types of collider; perhaps a circle-shaped collider or a non-encompassing collider such as those we used for the head, feet, and sides of Thomas and Bob back in the Thomas Was Late game.

For this reason, there will be a base ColliderComponent class (that inherits from Component) which will handle the basic functionality of all the colliders, as well as RectColliderComponent, which will add the specific functionality of an all-encompassing rectangle-shaped collider. New collider types can then be added as required for the game being developed.

What follows is the base class to the specific collider, ColliderComponent.

Coding the ColliderComponent class

Create a new header file in the Header Files/GameObjects filter called ColliderComponent.h and add the following code:

#pragma once

#include "Component.h"

#include <iostream>

class ColliderComponent : public Component

{

private:

    string m_Type = "collider";

    bool m_Enabled = false;

public:

    /****************************************************

    *****************************************************

    From Component interface

    *****************************************************

    *****************************************************/

    string Component::getType() {

        return m_Type;

    }

    void Component::disableComponent() {

        m_Enabled = false;

    }

    void Component::enableComponent() {

        m_Enabled = true;

    }

    bool Component::enabled() {

        return m_Enabled;

    }

   void Component::start(GameObjectSharer* gos, GameObject* self)

   {

        

    }

};

The ColliderComponent class inherits from the Component class. In the preceding code, you can see that the m_Type member variable is initialized to "collider" and that m_Enabled is initialized to false.

In the public section, the code overrides the pure virtual functions of the Component class. Study them to become familiar with them because they work in a very similar way in all the component classes. The getType function returns m_Type. The disableComponent function sets m_Enabled to false. The enableComponent function sets m_Enabled to true. The enabled function returns the value of m_Enabled. The start function has no code but will be overridden by many of the more specific component-based classes.

Create a new source file in the Source Files/GameObjects filter called ColliderComponent.cpp and add the following code:

/*

All Functionality in ColliderComponent.h

*/

I added the preceding comments to ColliderComponent.cpp to remind myself that all the functionality is in the header file.

Coding the RectColliderComponent class

Create a new header file in the Header Files/GameObjects filter called RectColliderComponent.h and add the following code:

#pragma once

#include "ColliderComponent.h"

#include <SFML/Graphics.hpp>

using namespace sf;

class RectColliderComponent : public ColliderComponent

{

private:

    string m_SpecificType = "rect";

    FloatRect m_Collider;

    string m_Tag = "";

public:

    RectColliderComponent(string name);

    string getColliderTag();

    void setOrMoveCollider(

        float x, float y, float width, float height);

        

    FloatRect& getColliderRectF();

    /****************************************************

    *****************************************************

    From Component interface base class

    *****************************************************

    *****************************************************/

    string getSpecificType() {

        return m_SpecificType;

    }

    

    void Component::start(

        GameObjectSharer* gos, GameObject* self) {}

};

The RectColliderComponent class inherits from the ColliderComponent class. It has a m_SpecificType variable initialized to "rect". It is now possible to query any RectColliderComponent instance in a vector of generic Component instances and determine that it has a type of  "collider" and a specific type of  "rect". All component-based classes will have this functionality because of the pure virtual functions of the Component class.

There is also a FloatRect instance called m_Collider that will store the coordinates of this collider.

In the public section, we can view the constructor. Notice that it receives a string. The value that's passed in will be text that identifies the type of game object this RectColliderComponent is attached to, such as an invader, a bullet, or the player's ship. It will then be possible to determine what type of objects have collided with each other.

There are three more functions before the overridden functions; make a note of their names and parameters and then we will discuss them in a moment when we code their definitions.

Note that the getSpecificType function definition returns m_SpecificType.

Create a new source file in the Source Files/GameObjects filter called RectColliderComponent.cpp and add the following code:

#include "RectColliderComponent.h"

RectColliderComponent::RectColliderComponent(string name) {

    m_Tag = "" + name;

}

string RectColliderComponent::getColliderTag() {

    return m_Tag;

}

void RectColliderComponent::setOrMoveCollider(

    float x, float y, float width, float height) {

        

    m_Collider.left = x;

    m_Collider.top = y;

    m_Collider.width = width;

    m_Collider.height = height;

}

FloatRect& RectColliderComponent::getColliderRectF() {

    return m_Collider;

}

In the constructor, the passed-in string value is assigned to the m_Tag variable and the getColliderTag function makes that value available via the instance of the class.

The setOrMoveCollider function positions m_Collider at the coordinates passed in as arguments.

The getColliderRectF function returns a reference to m_Collider. This is ideal for carrying out a collision test with another collider using the intersects function of the FloatRect class.

Our colliders are now complete and we can move on to the graphics.

Coding the graphics components

The Space Invaders ++ game will only have one specific type of graphics component. It is called StandardGraphicsComponent. As with the collider components, we will implement a base GraphicsComponent class to make it easy to add other graphics-related components, should we wish. For example, in the classic arcade version of Space Invaders, the invaders flapped their arms up and down with two frames of animation. Once you see how StandardGraphicsComponent works, you will be able to easily code another class (perhaps AnimatedGraphicsComponent) that draws itself with a different Sprite instance every half a second or so. You could also have a graphics component that has a shader (perhaps ShaderGraphicsComponent) for fast and cool effects. There are more possibilities besides these.

Coding the GraphicsComponent class

Create a new header file in the Header Files/GameObjects filter called GraphicsComponent.h and add the following code:

#pragma once

#include "Component.h"

#include "TransformComponent.h"

#include <string>

#include <SFML/Graphics.hpp>

#include "GameObjectSharer.h"

#include <iostream>

using namespace sf;

using namespace std;

class GraphicsComponent : public Component {

private:

    string m_Type = "graphics";

    bool m_Enabled = false;

public:

    virtual void draw(

        RenderWindow& window,

        shared_ptr<TransformComponent> t) = 0;

    virtual void initializeGraphics(

        string bitmapName,

        Vector2f objectSize) = 0;

    /****************************************************

    *****************************************************

    From Component interface

    *****************************************************

    *****************************************************/

    string Component::getType() {

        return m_Type;

    }

    void Component::disableComponent() {

        m_Enabled = false;

    }

    void Component::enableComponent() {

        m_Enabled = true;

    }

    bool Component::enabled() {

        return m_Enabled;

    }

    void Component::start(

        GameObjectSharer* gos, GameObject* self) {}

};

Most of the preceding code implements the Component class's pure virtual functions. What's new to the GraphicsComponent class is the draw function, which has two parameters. The first parameter is a reference to the RenderWindow instance so that the component can draw itself, while the second is a shared smart pointer to the TransformComponent instance of the GameObject so that vital data such as position and scale can be accessed each frame of the game.

What's also new in the GraphicsComponent class is the initializeGraphics function, which also has two parameters. The first is a string value that represents the file name of the graphics file to be used, while the second is a Vector2f instance that will represent the size of the object in the game world.

Both preceding functions are pure virtual, which makes the GraphicsComponent class abstract. Any class that inherits from GraphicsComponent will need to implement these functions. In the next section, we will see how StandardGraphicsComponent does so.

Create a new source file in the Source Files/GameObjects filter called GraphicsComponent.cpp and add the following code:

/*

All Functionality in GraphicsComponent.h

*/

The preceding comment is a reminder that the code is all within the related header file.

Coding the StandardGraphicsComponent class

Create a new header file in the Header Files/GameObjects filter called StandardGraphicsComponent.h and add the following code:

#pragma once

#include "Component.h"

#include "GraphicsComponent.h"

#include <string>

class Component;

class StandardGraphicsComponent : public GraphicsComponent {

private:

    sf::Sprite m_Sprite;

    string m_SpecificType = "standard";

public:

    /****************************************************

    *****************************************************

    From Component interface base class

    *****************************************************

    *****************************************************/

    string Component::getSpecificType() {

        return m_SpecificType;

    }

    

    void Component::start(

        GameObjectSharer* gos, GameObject* self) {

    }

    /****************************************************

    *****************************************************

    From GraphicsComponent

    *****************************************************

    *****************************************************/

    void draw(

        RenderWindow& window,

        shared_ptr<TransformComponent> t) override;

    void initializeGraphics(

        string bitmapName,

        Vector2f objectSize) override;

};

The StandardGraphicsComponent class has a Sprite member. It doesn't need a Texture instance because that will be obtained each frame from the BitmapStore class. This class also overrides the required functions from both the Component and GraphicsComponent classes.

Let's code the implementation of the two pure virtual functions, draw and initializeGraphics.

Create a new source file in the Source Files/GameObjects filter called StandardGraphicsComponent.cpp and add the following code:

#include "StandardGraphicsComponent.h"

#include "BitmapStore.h"

#include <iostream>

void StandardGraphicsComponent::initializeGraphics(

    string bitmapName,

    Vector2f objectSize)

{

    BitmapStore::addBitmap("graphics/" + bitmapName + ".png");

    m_Sprite.setTexture(BitmapStore::getBitmap(

        "graphics/" + bitmapName + ".png"));

    auto textureSize = m_Sprite.getTexture()->getSize();

    m_Sprite.setScale(float(objectSize.x) / textureSize.x,

        float(objectSize.y) / textureSize.y);    

    m_Sprite.setColor(sf::Color(0, 255, 0));

}

void StandardGraphicsComponent::draw(

    RenderWindow& window,

    shared_ptr<TransformComponent> t)

{

    m_Sprite.setPosition(t->getLocation());

    window.draw(m_Sprite);

}

In the initializeGraphics function, the addBitmap function of the BitmapStore class is called and the file path of the image, along with the size of the object in the game world, is passed in.

Next, the Texture instance that was just added to the BitmapStore class is retrieved and set as the image for the Sprite. Following on, two functions, getTexture and getSize, are chained together to get the size of the texture.

The next line of code uses the setScale function to make the Sprite the same size as the texture, which in turn was set to the size of this object in the game world.

The setColor function then applies a green tint to the Sprite. This gives it a bit more of a retro feel.

In the draw function, the Sprite is moved into position using setPosition and the getLocation function of TransformComponent. We'll code the TransformComponent class next.

The final line of code draws the Sprite to RenderWindow.

Coding the TransformComponent class

Create a new header file in the Header Files/GameObjects filter called TransformComponent.h and add the following code:

#pragma once

#include "Component.h"

#include<SFML/Graphics.hpp>

using namespace sf;

class Component;

class TransformComponent : public Component {

private:

    const string m_Type = "transform";

    Vector2f m_Location;

    float m_Height;

    float m_Width;

public:

    TransformComponent(

        float width, float height, Vector2f location);

    Vector2f& getLocation();

    Vector2f getSize();

    /****************************************************

    *****************************************************

    From Component interface

    *****************************************************

    *****************************************************/

    string Component::getType()

    {

        return m_Type;

    }

    string Component::getSpecificType()

    {

        // Only one type of Transform so just return m_Type

        return m_Type;

    }

    void Component::disableComponent(){}

    void Component::enableComponent(){}

    bool Component::enabled()

    {

        return false;

    }

    void Component::start(GameObjectSharer* gos, GameObject* self)    {}

};

This class has a Vector2f to store the position of the object in the game world, a float to store the height, and another float to store the width.

In the public section, there is a constructor we will use to set up the instances of this class, as well as two functions, getLocation and getSize, we'll use to share the location and size of the object. We  used these functions already when we coded the StandardGraphicsComponent class.

The remaining code in the TransformComponent.h file is the implementation of the Component class.

Create a new source file in the Source Files/GameObjects filter called TransformComponent.cpp and add the following code:

#include "TransformComponent.h"

TransformComponent::TransformComponent(

    float width, float height, Vector2f location)

{

    m_Height = height;

    m_Width = width;

    m_Location = location;

}

Vector2f& TransformComponent::getLocation()

{

    return m_Location;

}

Vector2f TransformComponent::getSize()

{

    return Vector2f(m_Width, m_Height);

}

Implementing the three functions of this class is straightforward. The constructor receives a size and location and initializes the appropriate member variables. The getLocation and getSize functions return this data when it is requested. Notice that the values are returned by reference, so they will be modifiable by the calling code.

Next, we will code all update-related components.

Coding update components

As you might expect by now, we will code an UpdateComponent class that will inherit from the Component class. It will have all the functionality that every UpdateComponent will need and then we will code classes derived from UpdateComponent. These will contain functionality specific to individual objects in the game. For this game, we will have BulletUpdateComponent, InvaderUpdateComponent, and PlayerUpdateComponent. When you work on your own project and you want an object in the game that behaves in a specific unique manner, just code a new update-based component for it and you'll be good-to-go. Update-based components define behavior.

Coding the UpdateComponent class

Create a new header file in the Header Files/GameObjects filter called UpdateComponent.h and add the following code:

#pragma once

#include "Component.h"

class UpdateComponent : public Component

{

private:

    string m_Type = "update";

    bool m_Enabled = false;

public:

    virtual void update(float fps) = 0;

    

    /****************************************************

    *****************************************************

    From Component interface

    *****************************************************

    *****************************************************/

    string Component::getType() {

        return m_Type;

    }

    void Component::disableComponent() {

        m_Enabled = false;

    }

    void Component::enableComponent() {

        m_Enabled = true;

    }

    bool Component::enabled() {

        return m_Enabled;

    }

    void Component::start(

        GameObjectSharer* gos, GameObject* self) {

    }

};

UpdateComponent only brings one piece of functionality: the update function. This function is pure virtual so it must be implemented by any class that aspires to be a usable instance of UpdateComponent.

Create a new source file in the Source Files/GameObjects filter called UpdateComponent.cpp and add the following code:

/*

All Functionality in UpdateComponent.h

*/

This is a helpful comment to remind us that this class has all its code in the related header file.

Coding the BulletUpdateComponent class

Create a new header file in the Header Files/GameObjects filter called BulletUpdateComponent.h and add the following code:

#pragma once

#include "UpdateComponent.h"

#include "TransformComponent.h"

#include "GameObjectSharer.h"

#include "RectColliderComponent.h"

#include "GameObject.h"

class BulletUpdateComponent : public UpdateComponent

{

private:

    string m_SpecificType = "bullet";

    shared_ptr<TransformComponent> m_TC;

    shared_ptr<RectColliderComponent> m_RCC;

    float m_Speed = 75.0f;

    

    int m_AlienBulletSpeedModifier;

    int m_ModifierRandomComponent = 5;

    int m_MinimumAdditionalModifier = 5;

    bool m_MovingUp = true;

public:

    bool m_BelongsToPlayer = false;

    bool m_IsSpawned = false;

    void spawnForPlayer(Vector2f spawnPosition);

    void spawnForInvader(Vector2f spawnPosition);

    void deSpawn();

    bool isMovingUp();

    /****************************************************

    *****************************************************

    From Component interface base class

    *****************************************************

    *****************************************************/

    string Component::getSpecificType() {

        return m_SpecificType;

    }

    

    void Component::start(

        GameObjectSharer* gos, GameObject* self) {        

        // Where is this specific invader

        m_TC = static_pointer_cast<TransformComponent>(

            self->getComponentByTypeAndSpecificType(

                "transform", "transform"));

        m_RCC = static_pointer_cast<RectColliderComponent>(

            self->getComponentByTypeAndSpecificType(

                "collider", "rect"));

    }

    /****************************************************

    *****************************************************

    From UpdateComponent

    *****************************************************

    *****************************************************/

    void update(float fps) override;

};

If you want to understand the behavior/logic of a bullet, you will need to spend some time learning the names and types of the member variables as I won't be explaining precisely how a bullet behaves; we have covered these topics many times. I will, however, point out that there are variables to cover basics such as movement, variables to help randomize the speed of each bullet within a certain range, and Booleans that identify whether the bullet belongs to the player or an invader.

The key thing which you don't yet know but will have to learn here is that each BulletUpdateComponent instance will hold a shared pointer to the owning game object's TransformComponent instance and a shared pointer to the owning game object's RectColliderComponent instance.

Now, look closely at the overridden start function. In the start function, the aforementioned shared pointers are initialized. The code achieves this by using the getComponentByTypeAndSpecificType function of the owning game object (self), which is a pointer to the owning game object. We will code the GameObject class, including this function, in a later section.

Create a new source file in the Source Files/GameObjects filter called BulletUpdate.cpp and add the following code:

#include "BulletUpdateComponent.h"

#include "WorldState.h"

void BulletUpdateComponent::spawnForPlayer(

    Vector2f spawnPosition)

{

    m_MovingUp = true;

    m_BelongsToPlayer = true;

    m_IsSpawned = true;

    

    m_TC->getLocation().x = spawnPosition.x;

    // Tweak the y location based on the height of the bullet

    // The x location is already tweaked to the center of the player

    m_TC->getLocation().y = spawnPosition.y - m_TC->getSize().y;

    // Update the collider

    m_RCC->setOrMoveCollider(m_TC->getLocation().x,

        m_TC->getLocation().y,

        m_TC->getSize().x, m_TC->getSize().y);

}

void BulletUpdateComponent::spawnForInvader(

    Vector2f spawnPosition)

{

    m_MovingUp = false;

    m_BelongsToPlayer = false;

    m_IsSpawned = true;

    srand((int)time(0));

    m_AlienBulletSpeedModifier = (

        ((rand() % m_ModifierRandomComponent)))  

        + m_MinimumAdditionalModifier;    

    m_TC->getLocation().x = spawnPosition.x;

    // Tweak the y location based on the height of the bullet

    // The x location already tweaked to the center of the invader

    m_TC->getLocation().y = spawnPosition.y;

    // Update the collider

    m_RCC->setOrMoveCollider(

        m_TC->getLocation().x, m_TC->

        getLocation().y, m_TC->getSize().x, m_TC->getSize().y);

}

void BulletUpdateComponent::deSpawn()

{

    m_IsSpawned = false;

}

bool BulletUpdateComponent::isMovingUp()

{

    return m_MovingUp;

}

void BulletUpdateComponent::update(float fps)

{

    if (m_IsSpawned)

    {    

        if (m_MovingUp)

        {

            m_TC->getLocation().y -= m_Speed * fps;

        }

        else

        {

            m_TC->getLocation().y += m_Speed /

                m_AlienBulletSpeedModifier * fps;

        }

        if (m_TC->getLocation().y > WorldState::WORLD_HEIGHT

            || m_TC->getLocation().y < -2)

        {

            deSpawn();

        }

        // Update the collider

        m_RCC->setOrMoveCollider(m_TC->getLocation().x,

            m_TC->getLocation().y,

            m_TC->getSize().x, m_TC->getSize().y);

    }

}

The first two functions are unique to the BulletUpdateComponent class; they are spawnForPlayer and spawnForInvader. Both of these functions prepare the member variables, transform component and collider component for action. Each one does so in a slightly different way. For example, for a player-owned bullet, it is prepared to move up the screen from the top of the player's ship, while a bullet is prepared for an invader to move down the screen from the underside of an invader. The key thing to notice is that all this is achievable via the shared pointers to the transform component and the collider component. Also, note that the m_IsSpawned Boolean is set to true, making this update component's update function ready to call each frame of the game.

In the update function, the bullet is moved up or down the screen at the appropriate speed. It is tested to see if it has disappeared off the top or bottom of the screen, and the collider is updated to wrap around the current location so that we can test for collisions.

This is the same logic we have seen throughout this book; what's new is the shared pointers we are using to communicate with the other components that make up this game object.

The bullets just need to be spawned and tested for collisions; we will see how to do that in the next two chapters. Now, we will code the behavior of the invaders.

Coding the InvaderUpdateComponent class

Create a new header file in the Header Files/GameObjects filter called InvaderUpdateComponent.h and add the following code:

#pragma once

#include "UpdateComponent.h"

#include "TransformComponent.h"

#include "GameObjectSharer.h"

#include "RectColliderComponent.h"

#include "GameObject.h"

class BulletSpawner;

class InvaderUpdateComponent : public UpdateComponent

{

private:

    string m_SpecificType = "invader";

    shared_ptr<TransformComponent> m_TC;

    shared_ptr < RectColliderComponent> m_RCC;

    shared_ptr < TransformComponent> m_PlayerTC;

    shared_ptr < RectColliderComponent> m_PlayerRCC;

    BulletSpawner* m_BulletSpawner;

    float m_Speed = 10.0f;

    bool m_MovingRight = true;

    float m_TimeSinceLastShot;

    float m_TimeBetweenShots = 5.0f;

    float m_AccuracyModifier;

    float m_SpeedModifier = 0.05;

    int m_RandSeed;

public:

    void dropDownAndReverse();

    bool isMovingRight();

    void initializeBulletSpawner(BulletSpawner*

        bulletSpawner, int randSeed);

    /****************************************************

    *****************************************************

    From Component interface base class

    *****************************************************

    *****************************************************/

    string Component::getSpecificType() {

        return m_SpecificType;

    }

    void Component::start(GameObjectSharer* gos,

        GameObject* self) {

            

        // Where is the player?

        m_PlayerTC = static_pointer_cast<TransformComponent>(

            gos->findFirstObjectWithTag("Player")

            .getComponentByTypeAndSpecificType(

                "transform", "transform"));

        m_PlayerRCC = static_pointer_cast<RectColliderComponent>(

            gos->findFirstObjectWithTag("Player")

            .getComponentByTypeAndSpecificType(

                "collider", "rect"));

        // Where is this specific invader

        m_TC = static_pointer_cast<TransformComponent>(

            self->getComponentByTypeAndSpecificType(

                "transform", "transform"));

        m_RCC = static_pointer_cast<RectColliderComponent>(

            self->getComponentByTypeAndSpecificType(

                "collider", "rect"));

    }

    /****************************************************

    *****************************************************

    From UpdateComponent

    *****************************************************

    *****************************************************/

    void update(float fps) override;    

};

In the class declaration, we can see all the features that we need in order to code the behavior of an invader. There is a pointer to the transform component so that the invader can move, as well as a pointer to the collider component so that it can update its location and be collided with:

shared_ptr<TransformComponent> m_TC;

shared_ptr < RectColliderComponent> m_RCC;

There are pointers to the player's transform and collider so that an invader can query the position of the player and make decisions about when to shoot bullets:

shared_ptr < TransformComponent> m_PlayerTC;

shared_ptr < RectColliderComponent> m_PlayerRCC;

Next, there is a BulletSpawner instance, which we will code in the next chapter. The BulletSpawner class will allow an invader or the player to spawn a bullet.

What follows is a whole bunch of variables that we will use to control the speed, direction, rate of fire, the precision with which the invader aims, and the speed of bullets that are fired. Familiarize yourself with them as they will be used in fairly in-depth logic in the function definitions:

float m_Speed = 10.0f;

bool m_MovingRight = true;

float m_TimeSinceLastShot;

float m_TimeBetweenShots = 5.0f;

float m_AccuracyModifier;

float m_SpeedModifier = 0.05;

int m_RandSeed;

Next, we can see three new public functions that different parts of the system can call to make the invaders move down a little and head in the other direction, test the direction of travel, and pass in a pointer to the aforementioned BulletSpawner class, respectively:

void dropDownAndReverse();

bool isMovingRight();

void initializeBulletSpawner(BulletSpawner*

        bulletSpawner, int randSeed);

Be sure to study the start function where the smart pointers to the invader and the player are initialized. Now, we will code the function definitions.

Create a new source file in the Source Files/GameObjects filter called InvaderUpdate.cpp and add the following code:

#include "InvaderUpdateComponent.h"

#include "BulletSpawner.h"

#include "WorldState.h"

#include "SoundEngine.h"

void InvaderUpdateComponent::update(float fps)

{

    if (m_MovingRight)

    {

        m_TC->getLocation().x += m_Speed * fps;

    }

    else

    {

        m_TC->getLocation().x -= m_Speed * fps;

    }

    // Update the collider

    m_RCC->setOrMoveCollider(m_TC->getLocation().x,

        m_TC->getLocation().y, m_TC->getSize().x, m_TC-

      >getSize().y);

    m_TimeSinceLastShot += fps;

    

    // Is the middle of the invader above the

   // player +- 1 world units

    if ((m_TC->getLocation().x + (m_TC->getSize().x / 2)) >

        (m_PlayerTC->getLocation().x - m_AccuracyModifier) &&

        (m_TC->getLocation().x + (m_TC->getSize().x / 2)) <

        (m_PlayerTC->getLocation().x +

        (m_PlayerTC->getSize().x + m_AccuracyModifier)))

    {

        // Has the invader waited long enough since the last shot

        if (m_TimeSinceLastShot > m_TimeBetweenShots)

        {

            SoundEngine::playShoot();

            Vector2f spawnLocation;

            spawnLocation.x = m_TC->getLocation().x +

                m_TC->getSize().x / 2;

            spawnLocation.y = m_TC->getLocation().y +

                m_TC->getSize().y;

            m_BulletSpawner->spawnBullet(spawnLocation, false);

            srand(m_RandSeed);

            int mTimeBetweenShots = (((rand() % 10))+1) /

                WorldState::WAVE_NUMBER;

            m_TimeSinceLastShot = 0;            

        }

    }

}

void InvaderUpdateComponent::dropDownAndReverse()

{

    m_MovingRight = !m_MovingRight;

    m_TC->getLocation().y += m_TC->getSize().y;

    m_Speed += (WorldState::WAVE_NUMBER) +

        (WorldState::NUM_INVADERS_AT_START

       - WorldState::NUM_INVADERS)

        * m_SpeedModifier;

}

bool InvaderUpdateComponent::isMovingRight()

{

    return m_MovingRight;

}

void InvaderUpdateComponent::initializeBulletSpawner(

    BulletSpawner* bulletSpawner, int randSeed)

{

    m_BulletSpawner = bulletSpawner;

    m_RandSeed = randSeed;

    srand(m_RandSeed);

    m_TimeBetweenShots = (rand() % 15 + m_RandSeed);

    m_AccuracyModifier = (rand() % 2);

    m_AccuracyModifier += 0 + static_cast <float> (

        rand()) / (static_cast <float> (RAND_MAX / (10)));

}

That was a lot of code. Actually, there's no C++ code in there that we haven't seen before. It is all just logic to control the behavior of an invader. Let's get an overview of what it all does, with parts of the code reprinted for convenience.

Explaining the update function

The first if and else blocks move the invader right or left each frame, as appropriate:

void InvaderUpdateComponent::update(float fps)

{

    if (m_MovingRight)

    {

        m_TC->getLocation().x += m_Speed * fps;

    }

    else

    {

        m_TC->getLocation().x -= m_Speed * fps;

    }

Next, the collider is updated to the new position:

    // Update the collider

    m_RCC->setOrMoveCollider(m_TC->getLocation().x,

        m_TC->getLocation().y, m_TC->getSize().x, m_TC

      ->getSize().y);

This code tracks how long it's been since this invader last fired a shot and then tests to see if the player is one world unit to the left or right of the invader (+ or – for the random accuracy modifier, so that each invader is a little bit different):

   m_TimeSinceLastShot += fps;

    

    // Is the middle of the invader above the

   // player +- 1 world units

    if ((m_TC->getLocation().x + (m_TC->getSize().x / 2)) >

        (m_PlayerTC->getLocation().x - m_AccuracyModifier) &&

        (m_TC->getLocation().x + (m_TC->getSize().x / 2)) <

        (m_PlayerTC->getLocation().x +

        (m_PlayerTC->getSize().x + m_AccuracyModifier)))

    {

Inside the preceding if test, another test makes sure that the invader has waited long enough since the last shot it took. If it has, then a shot is taken. A sound is played, a spawn location for the bullet is calculated, the spawnBullet function of the BulletSpawner instance is called, and a new random time to wait before another shot can be taken is calculated:

        // Has the invader waited long enough since the last shot

        if (m_TimeSinceLastShot > m_TimeBetweenShots)

        {

            SoundEngine::playShoot();

            Vector2f spawnLocation;

            spawnLocation.x = m_TC->getLocation().x +

                m_TC->getSize().x / 2;

            spawnLocation.y = m_TC->getLocation().y +

                m_TC->getSize().y;

            m_BulletSpawner->spawnBullet(spawnLocation, false);

            srand(m_RandSeed);

            int mTimeBetweenShots = (((rand() % 10))+1) /

                WorldState::WAVE_NUMBER;

            m_TimeSinceLastShot = 0;            

        }

    }

}

The details of the BulletSpawner class will be revealed in the next chapter, but as a glimpse into the future, it will be an abstract class with one function called spawnBullet and will be inherited from by the GameScreen class.

Explaining the dropDownAndReverse function

In the dropDownAndReverse function, the direction is reversed and the vertical location is increased by the height of an invader. In addition, the speed of the invader is increased relative to how many waves the player has cleared and how many invaders remain to be destroyed. The more waves that are cleared and the fewer invaders remaining, the faster the invaders will move:

void InvaderUpdateComponent::dropDownAndReverse()

{

    m_MovingRight = !m_MovingRight;

    m_TC->getLocation().y += m_TC->getSize().y;

    m_Speed += (WorldState::WAVE_NUMBER) +

        (WorldState::NUM_INVADERS_AT_START

      - WorldState::NUM_INVADERS)

        * m_SpeedModifier;

}

The next function is simple but included for the sake of completeness.

Explaining the isMovingRight function

This code simply provides access to the current direction of travel:

bool InvaderUpdateComponent::isMovingRight()

{

    return m_MovingRight;

}

It will be used to test whether to look out for collisions with the left of the screen (when moving left) or the right of the screen (when moving right) and will allow the collision to trigger a call to the dropDownAndReverse function.

Explaining the initializeBulletSpawner function

I have already mentioned that the BulletSpawner class is abstract and will be implemented by the GameScreen class. When the GameScreen class' initialize function is called, this initializeBulletSpawner function will be called on each of the invaders. As you can see, the first parameter is a pointer to a BulletSpawner instance. This gives every InvaderUpdateComponent the ability to call the spawnBullet function:

void InvaderUpdateComponent::initializeBulletSpawner(

    BulletSpawner* bulletSpawner, int randSeed)

{

    m_BulletSpawner = bulletSpawner;

    m_RandSeed = randSeed;

    srand(m_RandSeed);

    m_TimeBetweenShots = (rand() % 15 + m_RandSeed);

    m_AccuracyModifier = (rand() % 2);

    m_AccuracyModifier += 0 + static_cast <float> (

        rand()) / (static_cast <float> (RAND_MAX / (10)));

}

The rest of the code in the initializeBulletSpawner function sets up the random values that make each invader behave slightly differently from the others.

Coding the PlayerUpdateComponent class

Create a new header file in the Header Files/GameObjects filter called PlayerUpdateComponent.h and add the following code:

#pragma once

#include "UpdateComponent.h"

#include "TransformComponent.h"

#include "GameObjectSharer.h"

#include "RectColliderComponent.h"

#include "GameObject.h"

class PlayerUpdateComponent : public UpdateComponent

{

private:

    string m_SpecificType = "player";

    shared_ptr<TransformComponent> m_TC;

    shared_ptr<RectColliderComponent> m_RCC;

    float m_Speed = 50.0f;

    float m_XExtent = 0;

    float m_YExtent = 0;

    bool m_IsHoldingLeft = false;

    bool m_IsHoldingRight = false;

    bool m_IsHoldingUp = false;

    bool m_IsHoldingDown = false;

public:

    void updateShipTravelWithController(float x, float y);

    void moveLeft();

    void moveRight();

    void moveUp();

    void moveDown();

    void stopLeft();

    void stopRight();

    void stopUp();

    void stopDown();

    /****************************************************

    *****************************************************

    From Component interface base class

    *****************************************************

    *****************************************************/

    string Component::getSpecificType() {

        return m_SpecificType;

    }

    void Component::start(GameObjectSharer* gos, GameObject* self) {        

        m_TC = static_pointer_cast<TransformComponent>(self->

            getComponentByTypeAndSpecificType(

                "transform", "transform"));

        m_RCC = static_pointer_cast<RectColliderComponent>(self->

            getComponentByTypeAndSpecificType(

                "collider", "rect"));        

    }

    /****************************************************

    *****************************************************

    From UpdateComponent

    *****************************************************

    *****************************************************/

    void update(float fps) override;

};

In the PlayerUpdateComponent class, we have all the Boolean variables needed to keep track of whether the player is holding down a keyboard key, as well as functions that can toggle these Boolean values. We haven't seen anything like the m_XExtent and M_YExtent float type variables before and we will explain them when we look at their usage in the function definitions.

Note, just like the BulletUpdateComponent and the InvaderUpdateComponent classes, that we have shared pointers to this game object's transform and collider components. These shared pointers, as we are coming to expect, are initialized in the start function.

Create a new source file in the Source Files/GameObjects filter called PlayerUpdate.cpp and add the following code:

#include "PlayerUpdateComponent.h"

#include "WorldState.h"

void PlayerUpdateComponent::update(float fps)

{

    if (sf::Joystick::isConnected(0))

    {

        m_TC->getLocation().x += ((m_Speed / 100)

            * m_XExtent) * fps;

        m_TC->getLocation().y += ((m_Speed / 100)

            * m_YExtent) * fps;        

    }

    // Left and right

    if (m_IsHoldingLeft)

    {

        m_TC->getLocation().x -= m_Speed * fps;

    }

    else if (m_IsHoldingRight)

    {

        m_TC->getLocation().x += m_Speed * fps;

    }

    // Up and down

    if (m_IsHoldingUp)

    {

        m_TC->getLocation().y -= m_Speed * fps;

    }

    else if (m_IsHoldingDown)

    {

        m_TC->getLocation().y += m_Speed * fps;

    }

    

    // Update the collider

    m_RCC->setOrMoveCollider(m_TC->getLocation().x,

        m_TC->getLocation().y, m_TC->getSize().x,

        m_TC->getSize().y);

    

    // Make sure the ship doesn't go outside the allowed area

    if (m_TC->getLocation().x >

        WorldState::WORLD_WIDTH - m_TC->getSize().x)

    {

        m_TC->getLocation().x =

            WorldState::WORLD_WIDTH - m_TC->getSize().x;

    }

    else if (m_TC->getLocation().x < 0)

    {

        m_TC->getLocation().x = 0;

    }

    if (m_TC->getLocation().y >

        WorldState::WORLD_HEIGHT - m_TC->getSize().y)

    {

        m_TC->getLocation().y =

            WorldState::WORLD_HEIGHT - m_TC->getSize().y;

    }

    else if (m_TC->getLocation().y <

        WorldState::WORLD_HEIGHT / 2)

    {

        m_TC->getLocation().y =

            WorldState::WORLD_HEIGHT / 2;

    }

}    

void PlayerUpdateComponent::

    updateShipTravelWithController(float x, float y)

{

    m_XExtent = x;

    m_YExtent = y;

}

void PlayerUpdateComponent::moveLeft()

{

    m_IsHoldingLeft = true;

    stopRight();

}

void PlayerUpdateComponent::moveRight()

{

    m_IsHoldingRight = true;

    stopLeft();

}

void PlayerUpdateComponent::moveUp()

{

    m_IsHoldingUp = true;

    stopDown();

}

void PlayerUpdateComponent::moveDown()

{

    m_IsHoldingDown = true;

    stopUp();

}

void PlayerUpdateComponent::stopLeft()

{

    m_IsHoldingLeft = false;

}

void PlayerUpdateComponent::stopRight()

{

    m_IsHoldingRight = false;

}

void PlayerUpdateComponent::stopUp()

{

    m_IsHoldingUp = false;

}

void PlayerUpdateComponent::stopDown()

{

    m_IsHoldingDown = false;

}

In the first if block of the update function, the condition is sf::Joystick::isConnected(0). This condition returns true when the player has a gamepad plugged in to a USB port. Inside the if block, the location of both the horizontal and vertical positions of the transform component are altered:

…((m_Speed / 100) * m_YExtent) * fps;

The preceding code divides the target speed by 100 before multiplying it by m_YExtent. The m_XExtent and m_YExtent variables will be updated each frame to hold values that represent the extent to which the player has moved their gamepad thumbstick in a horizontal and vertical direction. The range of values is from -100 to 100, and so the preceding code has the effect of moving the transform component at full speed in any direction when the thumbstick is positioned at any of its full extents or a fraction of that speed when it is partially positioned between the center (not moving at all) and its full extent. This means that the player will have finer control over the speed of the ship should they opt to use a gamepad instead of the keyboard.

We will see more details about the operation of the gamepad in Chapter 22, Using Game Objects and Building a Game.

The rest of the update function responds to the Boolean variables, which represent the keyboard keys that the player is holding down or has released.

After the gamepad and keyboard handling, the collider component is moved into the new position and a series of if blocks ensures the player ship can't move outside of the screen or above the half-way-up point on the screen.

The next function is the updateShipTravelWithController function; when a controller is plugged in, it will update the extent to which the thumbstick is moved or at rest for each frame.

The remaining functions update the Boolean values that indicate whether keyboard keys are being used to move the ship. Notice that the update component does not handle firing a bullet. We could have handled it from here, and some games might have a good reason to do so. In this game, it was slightly more direct to handle shooting a bullet from the GameInputHandler class. The GameInputHandler class, as we will see in Chapter 22, Using Game Objects and Building a Game, will call all the functions that let the PlayerUpdateComponent class know what is happening with the gamepad and keyboard. We coded the basics of keyboard responses in the GameInputHandler class in the previous chapter.

Now, let's code the GameObject class, which will hold all the various component instances.

Coding the GameObject class

I am going to go through the code in this class in quite a lot of detail because it is key to how all the other classes work. I think you will benefit, however, from seeing the code in its entirety and studying it first. With this in mind, create a new header file in the Header Files/GameObjects filter called GameObject.h and add the following code:

#pragma once

#include <SFML/Graphics.hpp>

#include <vector>

#include <string>

#include "Component.h"

#include "GraphicsComponent.h"

#include "GameObjectSharer.h"

#include "UpdateComponent.h"

class GameObject {

private:

    vector<shared_ptr<Component>> m_Components;

    string m_Tag;

    bool m_Active = false;

    int m_NumberUpdateComponents = 0;

    bool m_HasUpdateComponent = false;

    int m_FirstUpdateComponentLocation = -1;

    int m_GraphicsComponentLocation = -1;

    bool m_HasGraphicsComponent = false;

    int m_TransformComponentLocation = -1;

    int m_NumberRectColliderComponents = 0;

    int m_FirstRectColliderComponentLocation = -1;

    bool m_HasCollider = false;

public:

    void update(float fps);

    void draw(RenderWindow& window);

    void addComponent(shared_ptr<Component> component);

    void setActive();

    void setInactive();

    bool isActive();

    void setTag(String tag);

    string getTag();

    void start(GameObjectSharer* gos);

    // Slow only use in init and start

    shared_ptr<Component> getComponentByTypeAndSpecificType(

        string type, string specificType);

    FloatRect& getEncompassingRectCollider();

    bool hasCollider();

    bool hasUpdateComponent();

    string getEncompassingRectColliderTag();

    shared_ptr<GraphicsComponent> getGraphicsComponent();

    shared_ptr<TransformComponent> getTransformComponent();

    shared_ptr<UpdateComponent> getFirstUpdateComponent();

};

In the preceding code, be sure to closely examine the variables, types, function names, and their parameters.

Create a new source file in the Source Files/GameObjects filter called GameObject.cpp and then study and add the following code:

#include "DevelopState.h"

#include "GameObject.h"

#include <iostream>

#include "UpdateComponent.h"

#include "RectColliderComponent.h"

void GameObject::update(float fps)

{

    if (m_Active && m_HasUpdateComponent)

    {

        for (int i = m_FirstUpdateComponentLocation; i <

            m_FirstUpdateComponentLocation +

            m_NumberUpdateComponents; i++)

        {

            shared_ptr<UpdateComponent> tempUpdate =

                static_pointer_cast<UpdateComponent>(

             m_Components[i]);

            if (tempUpdate->enabled())

            {

                tempUpdate->update(fps);

            }

        }

    }

}

void GameObject::draw(RenderWindow& window)

{

    if (m_Active && m_HasGraphicsComponent)

    {

        if (m_Components[m_GraphicsComponentLocation]->enabled())

        {

            getGraphicsComponent()->draw(window,

                getTransformComponent());

        }

    }

}

shared_ptr<GraphicsComponent> GameObject::getGraphicsComponent()

{

    return static_pointer_cast<GraphicsComponent>(

        m_Components[m_GraphicsComponentLocation]);

}

shared_ptr<TransformComponent> GameObject::getTransformComponent()

{

    return static_pointer_cast<TransformComponent>(

        m_Components[m_TransformComponentLocation]);

}

void GameObject::addComponent(shared_ptr<Component> component)

{

    m_Components.push_back(component);

    component->enableComponent();

    

   if (component->getType() == "update")

    {

        m_HasUpdateComponent = true;

        m_NumberUpdateComponents++;

        if (m_NumberUpdateComponents == 1)

        {

            m_FirstUpdateComponentLocation =

                m_Components.size() - 1;

        }

    }

    else if (component->getType() == "graphics")

    {

        // No iteration in the draw method required

        m_HasGraphicsComponent = true;

        m_GraphicsComponentLocation = m_Components.size() - 1;

    }

    else if (component->getType() == "transform")

    {

        // Remember where the Transform component is

        m_TransformComponentLocation = m_Components.size() - 1;

    }

    else if (component->getType() == "collider" &&

        component->getSpecificType() == "rect")

    {

        // Remember where the collider component(s) is

        m_HasCollider = true;

        m_NumberRectColliderComponents++;

        if (m_NumberRectColliderComponents == 1)

        {

            m_FirstRectColliderComponentLocation =

                m_Components.size() - 1;

        }

    }    

}

void GameObject::setActive()

{

    m_Active = true;

}

void GameObject::setInactive()

{

    m_Active = false;

}

bool GameObject::isActive()

{

    return m_Active;

}

void GameObject::setTag(String tag)

{

    m_Tag = "" + tag;

}

std::string GameObject::getTag()

{

    return m_Tag;

}

void GameObject::start(GameObjectSharer* gos)

{

    auto it = m_Components.begin();

    auto end = m_Components.end();

    for (it;

        it != end;

        ++it)

    {

        (*it)->start(gos, this);

    }

}

// Slow - only use in start function

shared_ptr<Component> GameObject::

   getComponentByTypeAndSpecificType(

    string type, string specificType) {

    auto it = m_Components.begin();

    auto end = m_Components.end();

    for (it;

        it != end;

        ++it)

    {

        if ((*it)->getType() == type)

        {

            if ((*it)->getSpecificType() == specificType)

            {

                return  (*it);

            }

        }

    }

    #ifdef debuggingErrors        

        cout <<

            "GameObject.cpp::getComponentByTypeAndSpecificType-"

            << "COMPONENT NOT FOUND ERROR!"

            << endl;

    #endif

        return m_Components[0];

}

FloatRect& GameObject::getEncompassingRectCollider()

{

    if (m_HasCollider)

    {

        return (static_pointer_cast<RectColliderComponent>(

            m_Components[m_FirstRectColliderComponentLocation]))

            ->getColliderRectF();

    }

}

string GameObject::getEncompassingRectColliderTag()

{

    return static_pointer_cast<RectColliderComponent>(

        m_Components[m_FirstRectColliderComponentLocation])->

        getColliderTag();

}

shared_ptr<UpdateComponent> GameObject::getFirstUpdateComponent()

{

    return static_pointer_cast<UpdateComponent>(

        m_Components[m_FirstUpdateComponentLocation]);

}

bool GameObject::hasCollider()

{

    return m_HasCollider;

}

bool GameObject::hasUpdateComponent()

{

    return m_HasUpdateComponent;

}

Tip

Be sure to study the preceding code before moving on. The explanations that follow assume that you have a basic awareness of variable names and types, as well as function names, parameters, and return types.

Explaining the GameObject class

Let's go through the GameObject class one function at a time and reprint the code to make it easy to discuss.

Explaining the update function

The update function is called once for each frame of the game loop for each game object. Like most of our other projects, the current frame rate is required. Inside the update function, a test is done to see if this GameObject instance is active and has an update component. A game object does not have to have an update component, although it is true that all the game objects in this project do.

Next, the update function loops through all the components it has, starting from m_FirstUpdateComponent through to m_FirstUpdateComponent + m_NumberUpdateComponents. This code implies that a game object can have multiple update components. This is so that you can design game objects with layers of behavior. This layering of behavior is discussed further in Chapter 22, Using Game Objects and Building a Game. All the game objects in this project have just one update component, so you could simplify (and speed up) the logic in the update function, but I suggest leaving it as it is until you have read Chapter 22, Using Game Objects and Building a Game.

It is because a component could be one of many types that we create a temporary update-related component (tempUpdate), cast the component from the vector of components to UpdateComponent, and call the update function. It doesn't matter about the specific derivation of the UpdateComponent class; it will have the update function implemented, so the UpdateComponent type is specific enough:

void GameObject::update(float fps)

{

    if (m_Active && m_HasUpdateComponent)

    {

        for (int i = m_FirstUpdateComponentLocation; i <

            m_FirstUpdateComponentLocation +

            m_NumberUpdateComponents; i++)

        {

            shared_ptr<UpdateComponent> tempUpdate =

                static_pointer_cast<UpdateComponent>(

             m_Components[i]);

            if (tempUpdate->enabled())

            {

                tempUpdate->update(fps);

            }

        }

    }

}

When we get to the addComponent function in a later section, we will see how we can initialize the various control variables, such as m_FirstUpdateComponentLocation and m_NumberOfUpdateComponents.

Explaining the draw function

The draw function checks whether the game object is active and that it has a graphics component. If it does, then a check is done to see if the graphics component is enabled. If all these tests succeed, then the draw function is called:

void GameObject::draw(RenderWindow& window)

{

    if (m_Active && m_HasGraphicsComponent)

    {

        if (m_Components[m_GraphicsComponentLocation]->enabled())

        {

            getGraphicsComponent()->draw(window,

                getTransformComponent());

        }

    }

}

The structure of the draw function implies that not every game object has to draw itself. I mentioned in Chapter 19, Game Programming Design Patterns – Starting the Space Invaders ++ Game, that you might want game objects that can never be seen to act as invisible trigger regions (with no graphics component) that respond when the player passes over them or game objects that remain invisible temporarily (temporarily disabled but with a graphics component). In this project, all game objects have a permanently enabled graphics component.

Explaining the getGraphicsComponent function

This function returns a shared pointer to the graphics component:

shared_ptr<GraphicsComponent> GameObject::getGraphicsComponent()

{

    return static_pointer_cast<GraphicsComponent>(

        m_Components[m_GraphicsComponentLocation]);

}

The getGraphicsComponent function gives any code that has an instance of the contained game object access to the graphics component.

Explaining the getTransformComponent function

This function returns a shared pointer to the transform component:

shared_ptr<TransformComponent> GameObject::getTransformComponent()

{

    return static_pointer_cast<TransformComponent>(

        m_Components[m_TransformComponentLocation]);

}

The getTransformComponent function gives any code that has an instance of the contained game object access to the transform component.

Explaining the addComponent function

The addComponent function will be used by a factory pattern class we will code in the next chapter. The function receives a shared pointer to a Component instance. The first thing that happens inside the function is that the Component instance is added to the m_Components vector. Next, the component is enabled using the enabled function.

What follows is a series of if and else if statements that deal with each possible type of component. When the type of a component is identified, the various control variables are initialized to enable the logic in the rest of the class to work correctly.

For example, if an update component is detected, then the m_HasUpdateComponent, m_NumberUpdateComponents, and m_FirstUpdateComponentLocation variables are initialized.

As another example, if a collider component is detected along with the rect specific type, then the m_HasCollider, m_NumberRectColliderComponents, and m_FirstRectColliderComponent variables are initialized:

void GameObject::addComponent(shared_ptr<Component> component)

{

    m_Components.push_back(component);

    component->enableComponent();

    

   if (component->getType() == "update")

    {

        m_HasUpdateComponent = true;

        m_NumberUpdateComponents++;

        if (m_NumberUpdateComponents == 1)

        {

            m_FirstUpdateComponentLocation =

                m_Components.size() - 1;

        }

    }

    else if (component->getType() == "graphics")

    {

        // No iteration in the draw method required

        m_HasGraphicsComponent = true;

        m_GraphicsComponentLocation = m_Components.size() - 1;

    }

    else if (component->getType() == "transform")

    {

        // Remember where the Transform component is

        m_TransformComponentLocation = m_Components.size() - 1;

    }

    else if (component->getType() == "collider" &&

        component->getSpecificType() == "rect")

    {

        // Remember where the collider component(s) is

        m_HasCollider = true;

        m_NumberRectColliderComponents++;

        if (m_NumberRectColliderComponents == 1)

        {

            m_FirstRectColliderComponentLocation =

                m_Components.size() - 1;

        }

    }    

}

Note that the GameObject class plays no part in configuring or setting up the actual components themselves. It is all handled in the factory pattern class we will code in the next chapter.

Explaining the getter and setter functions

The following code is a series of very simple getters and setters:

void GameObject::setActive()

{

    m_Active = true;

}

void GameObject::setInactive()

{

    m_Active = false;

}

bool GameObject::isActive()

{

    return m_Active;

}

void GameObject::setTag(String tag)

{

    m_Tag = "" + tag;

}

std::string GameObject::getTag()

{

    return m_Tag;

}

The preceding getters and setters provide information about a game object, such as whether it is active and what its tag is. They also allow you to set the tag and tell us whether or not the game object is active.

Explaining the start function

The start function is an important one. As we saw when we coded all the components, the start function gives access to any component in any game object the components of any other game object. The start function is called once all the GameObject instances have been composed from all their components. In the next chapter, we will see how this happens, as well as when the start function is called on every GameObject instance. As we can see, in the start function, it loops through every component and shares a new class instance, a GameObjectSharer instance. This GameObjectSharer class will be coded in the next chapter and will give access to any component from any class. We saw how the invaders need to know where the player is and how the GameObjectSharer parameter is used when we coded the various components. When start is called on each component, the this pointer is also passed in to give each component easy access to its contained GameObject instance:

void GameObject::start(GameObjectSharer* gos)

{

    auto it = m_Components.begin();

    auto end = m_Components.end();

    for (it;

        it != end;

        ++it)

    {

        (*it)->start(gos, this);

    }

}

Let's move on to the getComponentByTypeAndSpecificType function.

Explaining the getComponentByTypeAndSpecificType function

The getComponentByTypeAndSpecificType function has a nested for loop that looks for a match of a component type to the first string parameter and then looks for a match of the specific component type in the second string parameter. It returns a shared pointer to a base class Component instance. This implies that the calling code needs to know exactly what derived Component type is being returned so that it can cast it to the required type. This shouldn't be a problem because, of course, they are requesting both a type and a specific type:

// Slow only use in start

shared_ptr<Component> GameObject::getComponentByTypeAndSpecificType(

    string type, string specificType) {

    auto it = m_Components.begin();

    auto end = m_Components.end();

    for (it;

        it != end;

        ++it)

    {

        if ((*it)->getType() == type)

        {

            if ((*it)->getSpecificType() == specificType)

            {

                return  (*it);

            }

        }

    }

    #ifdef debuggingErrors        

        cout <<

            "GameObject.cpp::getComponentByTypeAndSpecificType-"

            << "COMPONENT NOT FOUND ERROR!"

            << endl;

    #endif

        return m_Components[0];

}

The code in this function is quite slow and is therefore intended for use outside of the main game loop. At the end of this function, the code writes an error message to the console if debuggingErrors has been defined. The reason for this is because, if execution reaches this point, it means that no matching component was found, and the game will crash. The output to the console should make the error easy to find. The cause of the crash would be that the function was called for an invalid type or specific type.

Explaining the getEncompassingRectCollider function

The getEncompassingRectCollider function checks whether the game object has a collider and, if it has, returns it to the calling code:

FloatRect& GameObject::getEncompassingRectCollider()

{

    if (m_HasCollider)

    {

        return (static_pointer_cast<RectColliderComponent>(

            m_Components[m_FirstRectColliderComponentLocation]))

            ->getColliderRectF();

    }

}

It is worth noting that, if you extend this project to handle more than one type of collider, then this code would need adapting too.

Explaining the getEncompassingRectColliderTag function

This simple function returns the tag of the collider. This will be useful for determining what type of object is being tested for collision:

string GameObject::getEncompassingRectColliderTag()

{

    return static_pointer_cast<RectColliderComponent>(

        m_Components[m_FirstRectColliderComponentLocation])->

        getColliderTag();

}

We have just a few more functions to discuss.

Explaining the getFirstUpdateComponent function

getFirstUpdateComponent uses the m_FirstUpdateComponent variable to locate the update component and then returns it to the calling code:

shared_ptr<UpdateComponent> GameObject::getFirstUpdateComponent()

{

    return static_pointer_cast<UpdateComponent>(

        m_Components[m_FirstUpdateComponentLocation]);

}

Now we're just going to go over a couple of getters, and then we are done.

Explaining the final getter functions

These two remaining functions return a Boolean (each) to tell the calling code whether the game object has a collider and/or an update component:

bool GameObject::hasCollider()

{

    return m_HasCollider;

}

bool GameObject::hasUpdateComponent()

{

    return m_HasUpdateComponent;

}

We have coded the GameObject class in full. We can now look at putting it (and all the components it will be composed of) to work.

Summary

In this chapter, we have completed all the code that will draw our game objects to the screen, control their behavior, and let them interact with other classes through collisions. The most important thing to take away from this chapter is not how any of the specific component-based classes work but how flexible the Entity-Component system is. If you want a game object that behaves in a certain way, create a new update component. If it needs to know about other objects in the game, get a pointer to the appropriate component in the start function. If it needs to be drawn in a fancy manner, perhaps with a shader or an animation, code a graphics component that performs the actions in the draw function. If you need multiple colliders, like we did for Thomas and Bob in the Thomas Was Late project, this is no problem: code a new collider-based component.

In the next chapter, we will code the file input and output system, as well as the class that will be the factory that builds all the game objects and composes them with components.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.31.156