In this tutorial we will re-implement the movement code that we previously used to move our sprite around the screen, but this time as a component. We’ll also create a transform component that will be attached to all of our games objects. This component will store the sprites position, rotation, and scale (although it will only store its position initially).

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.

In this tutorial we will re-implement the movement code that we previously used to move our sprite around the screen, but this time as a component. We’ll also create a transform component that will be attached to all of our games objects. This component will store the sprites position, rotation, and scale (although it will only store its position initially). If you missed the previous tutorial where we implemented our component system, you can find it here.

 

Implementation

Before we can move our sprite we need a method of storing and changing an objects position. To accomplish this we will write a transform class. This class will store our objects position and provide a method of retrieving and changing that position. Lets name it C_Transform (the initial ‘C_’ is used to show that it is a component).

C_Transform.hpp
#ifndef C_Transform_hpp
#define C_Transform_hpp

#include "Component.hpp"

class C_Transform : public Component
{
public:
    C_Transform(Object* owner);
 
    void SetPosition(float x, float y);
    void SetPosition(sf::Vector2f pos);
    
    void AddPosition(float x, float y);
    void AddPosition(sf::Vector2f pos);
    
    // Allows us to set x/y values separately.
    void SetX(float x);
    void SetY(float y);
    
    const sf::Vector2f& GetPosition() const;

private:
    sf::Vector2f position;
};

#endif /* C_Transform_hpp */

This is the minimum functionality that we require for the time being. In future we’ll also want to be able to rotate and scale our object, as well as add parent objects (parent objects will let us implement object hierarchies, such as those found with Unity and other game engines).

Object Hierarchy in Unity.
Object Hierarchy in Unity.

C_Transform.cpp
#include "C_Transform.hpp"

C_Transform::C_Transform(Object* owner) 
	: Component(owner), position(0.f, 0.f) { }

void C_Transform::SetPosition(float x, float y)
{
    position.x = x;
    position.y = y;
}

void C_Transform::SetPosition(const sf::Vector2f& pos)
{
    position = pos;
}

void C_Transform::AddPosition(float x, float y)
{
    position.x += x;
    position.y += y;
}

void C_Transform::AddPosition(sf::Vector2f pos)
{
    position += pos;
}

void C_Transform::SetX(float x)
{
    position.x = x;
}

void C_Transform::SetY(float y)
{
    position.y = y;
}

void C_Transform::AddX(float x)
{
    position.x += x;
}

void C_Transform::AddY(float y)
{
    position.y += y;
}

const sf::Vector2f& C_Transform::GetPosition() const
{
    return position;
}

The methods are straightforward. They are simple get and set methods for a vector2 that we will use to store our objects on-screen coordinates.

From this point forward, I want all objects to have a transform component and I do not want to manually add the component every time I create an object. To accomplish this, we’ll edit our Object class constructor.

Object.hpp
…
// Make sure to include our new class.
#include “C_Transform.hpp" 

class Object
{
public:
	// Add a constructor so we can instantiate our 
	// new transform variable.
    Object(); 
 …
    std::shared_ptr<C_Transform> transform; // 1
    
};

1. There are arguments for and against storing the transform component as a public variable. If you’re anything like me then you’ve probably been told a hundred times (mostly at university) to always hide your variables behind getters and setters/accessors and mutators. I’ve chosen to make this public because it will be accessed often and although the overhead is minuscule for adding the get method, I don’t want it. 

Object.cpp
Object::Object()
{
    transform = AddComponent<C_Transform>();
}

Not all objects we create will have a sprite component and therefore will not be visible to the player, so you may wonder why I would want them to have a position. Even if we’re not drawing an object, it can still be useful to know their position in the world; for example, we may have an off-screen object that instantiates projectiles. We don’t want it to be doing this the whole time the level is being played, so using its position we make sure that it only starts producing projectiles when the players character is within a certain distance.

Currently we can update an objects position through its transform but this will not change the position of the objects sprite (if present). We want to be able to change the transforms position (and in future scale/rotation) and have our sprite update its position automatically. To do this we’ll change our sprite class to make use of our new transform component.

C_Sprite.hpp
…
#include "C_Transform.hpp"

class C_Sprite : public Component
{
public:
…
	// We’ll use this to update our sprite based on our position.
    void LateUpdate(float deltaTime) override; 
};

C_Sprite.cpp
#include “Object.hpp" // 1
…
void C_Sprite::LateUpdate(float deltaTime)
{
    sprite.setPosition(owner->transform->GetPosition()); // 2
}

1. If we want to use the functionality of the Object class, we have to include the class in our sprite implementation. As it was forward declared, we do not currently have any knowledge of its inner workings (also known as methods/data) so we need to include the header in our implementation file.

2.  This sets the sprites position based on our newly created transform. Now whenever we change the position in the transform class our sprites position will also be updated. We perform this in LateUpdate as any modifications to an objects position should happen during Update, so we can be relatively sure that we are retrieving the correct position for this frame. In future we may want to change our sprite component (and any class that uses the objects position) to only update its position when required (i.e. a change in position has occurred) rather than every frame.

To see this in action lets create a movement component for our character, called C_KeyboardMovement.

C_KeyboardMovement.hpp
#ifndef C_KeyboardMovement_hpp
#define C_KeyboardMovement_hpp

#include "Component.hpp"
#include "Input.hpp"

class C_KeyboardMovement : public Component
{
public:
    C_KeyboardMovement(Object * owner);
    
    void SetInput(Input* input);
    void SetMovementSpeed(int moveSpeed);
    
    void Update(float deltaTime) override;
    
private:
    int moveSpeed; 
    Input* input;
};

#endif /* C_KeyboardMovement_hpp */

You may have noticed that the class has a moveSpeed variable. Generally we don’t want out keyboard controller class to care/know about the players move speed and once we look into creating our platform physics we will change this but it’ll do for now.

C_KeyboardMovement.cpp
#include "C_KeyboardMovement.hpp"
#include "Object.hpp"

C_KeyboardMovement::C_KeyboardMovement(Object* owner) 
	: Component(owner), moveSpeed(100) {}

void C_KeyboardMovement::SetInput(Input* input)
{
    this->input = input;
}

void C_KeyboardMovement::SetMovementSpeed(int moveSpeed)
{
    this->moveSpeed = moveSpeed;
}

void C_KeyboardMovement::Update(float deltaTime)
{
    if(input == nullptr)
    {
        return;
    }
    
    int xMove = 0;
    if(input->IsKeyPressed(Input::Key::Left))
    {
        xMove = -moveSpeed;
    }
    else if(input->IsKeyPressed(Input::Key::Right))
    {
        xMove = moveSpeed;
    }
    
    int yMove = 0;
    if(input->IsKeyPressed(Input::Key::Up))
    {
        yMove = -moveSpeed;
    }
    else if(input->IsKeyPressed(Input::Key::Down))
    {
        yMove = moveSpeed;
    }
    
    float xFrameMove = xMove * deltaTime;
    float yFrameMove = yMove * deltaTime;
    
    owner->transform->AddPosition(xFrameMove, yFrameMove);
}

Nothing is really new here, we are simply re-adding our movement code from previous tutorials that we recently removed into our new component.

With our movement code done (for now anyway) we need to add the new component to our player object in SceneGame.

SceneGame.hpp
…
#include “C_KeyboardMovement.hpp"

class SceneGame : public Scene
{
public:
…
   void LateUpdate(float deltaTime) override; 
};

Our game scene now overrides late update as we want to call our player objects late update (to ensure our sprites retrieves our transforms position).

SceneGame.cpp
void SceneGame::OnCreate()
{
…
    auto movement = player->AddComponent<C_KeyboardMovement>();
	
	// Don’t forget to set the input otherwise the character will not move:
    movement->SetInput(&input); 
}

void SceneGame::Update(float deltaTime)
{
    player->Update(deltaTime); 
}

void SceneGame::LateUpdate(float deltaTime)
{
    player->LateUpdate(deltaTime);
}

And there you have it: we can, once again,  move our character; but this time we are using our new component system. We’ll take a break from writing components next week and instead we’ll look at implementing a resource management system to handle our textures, audio, and font files.

There are a few reasons (mostly performance based) that we will probably, at some point in the future, update our component system so that it is more inline with the Entity Component System model (which was briefly discussed in the previous tutorial, found here). If you’re interested, Unity is currently making a transition to an ECS model.

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 🙂