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.
Last week we started our animation system by animating our Viking character, however, there were a number of major limitations. Firstly we only have the one animation: idle; in our final game, our character is definitely going to need to be able to do more than just stand around! And secondly, it does not matter which direction we were moving we always faced right (the direction the sprites are facing in the sprite sheet). So in this tutorial, we will fix these issues. To do that we need to:
- Create the concept of a facing direction.
- Track which direction the character is currently facing and flip the sprites if necessary.
- Add a new walking animation.
- Have some way of switching between idle and walking depending on the characters movement speed.
We’ll start by implementing the facing direction. To do this we’ll create a new enum called FacingDirection, and a couple of methods to get and set this direction, in our Animation class. We’ll also pass an animations direction in its constructor. This does mean that all sprites for a certain animation need to be facing in the same direction, which is generally the case but its something we can change in future if we need to.
enum class FacingDirection
{
None,
Left,
Right
};
class Animation
{
public:
Animation(FacingDirection direction);
…
void SetDirection(FacingDirection dir);
FacingDirection GetDirection() const;
private:
…
FacingDirection direction;
};
Animation::Animation(FacingDirection direction)
: frames(0), currentFrameIndex(0), currentFrameTime(0.f),
direction(direction) { }
void Animation::SetDirection(FacingDirection dir)
{
// Makes sure we do not flip the sprite
// unless its a new direction.
if(direction != dir)
{
direction = dir;
for(auto& f : frames)
{
f.x += f.width; // 1
f.width *= -1;
}
}
}
FacingDirection Animation::GetDirection() const
{
return direction;
}
- To flip a sprite we multiply its width by 1 which inverts the number (if it was positive it becomes negative and if it was negative it becomes positive). For example:
We want to flip a frame with a position of 0 and a width of 32.
We first add the width of the sprite to the x position so that the sprites position is now set to the top right rather that the top left.
However, this means that the sprite’s width is wrong. It will either be drawing the next sprite or if there isn’t one be trying to draw something that isn’t there. To rectify this we need to invert its width so that it points in the opposite direction. 32 * -1 = -32.
This has the same effect as scaling the image by -1 on the x-axis and flips the sprite! Job done. And when we want to flip the sprite back, the process works in reverse:
- 32 + -32 = 0
- -32 * -1 = 32
We’ll now need a way to set the direction for the current animation in our component so that anyone with a reference to our component can flip our sprites.
class C_Animation : public Component
{
public:
…
void SetAnimationDirection(FacingDirection dir);
};
void C_Animation::SetAnimationDirection(FacingDirection dir)
{
if(currentAnimation.first != AnimationState::None)
{
currentAnimation.second->SetDirection(dir);
}
}
As we need to pass a direction to the animation constructor we need to change the OnCreate method in SceneGame where we create our idle animation. We pass the direction of the character as they appear in the sprite sheet; for example, as our Viking is facing right in the sprite sheet, we pass in FacingDirection::Right.
void SceneGame::OnCreate()
{
…
// We pass in the animations direction when creating a new animation.
// The character in the sprites faces right so we set that
// as the initial direction.
std::shared_ptr<Animation> idleAnimation =
std::make_shared<Animation>(FacingDirection::Right);
…
}
When we look into the characters physics, we’ll implement a different method of determining the direction of the character but for now, we can simply set the direction based on the keys being pressed by the player i.e. we assume the character is moving right if they are pressing right on the keyboard. We’ll see in a later tutorial why this is not always the best way of doing things. Also ideally we do not want our keyboard component to know anything about our animation system so in future we’ll look at implementing an event system to allow for inter-system communication.
#include "C_Animation.hpp"
class C_KeyboardMovement : public Component
{
public:
…
void Awake() override;
private:
…
// We need to store a reference to the
// animation component for now.
std::shared_ptr<C_Animation> animation;
};
void C_KeyboardMovement::Awake()
{
animation = owner->GetComponent<C_Animation>();
}
We need to change the keyboard Update method to set the direction based on the key that is currently being pressed.
void C_KeyboardMovement::Update(float deltaTime)
{
…
int xMove = 0;
if(input->IsKeyPressed(Input::Key::Left))
{
xMove = -moveSpeed;
animation->SetAnimationDirection(FacingDirection::Left); // New line
}
else if(input->IsKeyPressed(Input::Key::Right))
{
xMove = moveSpeed;
animation->SetAnimationDirection(FacingDirection::Right); // New line
}
…
}
Now when you run the game and use the arrow keys to move our little Viking around the screen, he will face the direction you are moving.
We still only have the one animation though, let’s rectify that by creating a walk animation.
void SceneGame::OnCreate()
{
…
// Create the animation.
std::shared_ptr<Animation> walkAnimation =
std::make_shared<Animation>(FacingDirection::Right);
const float walkAnimFrameSeconds = 0.15f;
// Create the frames.
walkAnimation->AddFrame(vikingTextureID, 600, 290,
frameWidth, frameHeight, walkAnimFrameSeconds);
walkAnimation->AddFrame(vikingTextureID, 800, 290,
frameWidth, frameHeight, walkAnimFrameSeconds);
walkAnimation->AddFrame(vikingTextureID, 0, 435,
frameWidth, frameHeight, walkAnimFrameSeconds);
walkAnimation->AddFrame(vikingTextureID, 200, 435,
frameWidth, frameHeight, walkAnimFrameSeconds);
walkAnimation->AddFrame(vikingTextureID, 400, 435,
frameWidth, frameHeight, walkAnimFrameSeconds);
// Add animation to our Viking.
animation->AddAnimation(AnimationState::Walk, walkAnimation);
…
}
This is pretty much the same process as the idle animation, except there’s one additional frame, and of course, the frames point to different sprites in the sprite sheet.
If you run the game now you will be disappointed as the character will still stay in its idle state. This is because the first animation we add becomes the initial animation (i.e. idle) and we have not provided any method to switch between the two. We’ll do just that in the keyboard movement component after we update the characters position. Again this is not the ideal way to accomplish this and will be something we change when we look into player physics.
void C_KeyboardMovement::Update(float deltaTime)
{
…
if(xMove == 0 && yMove == 0)
{
animation->SetAnimationState(AnimationState::Idle);
}
else
{
animation->SetAnimationState(AnimationState::Walk);
}
}
With that done our character will now change its animation depending on his movement speed.
As a side note, I also doubled the players move speed (100 to 200) in C_KeyboardMovement to make him a bit nipper traversing the screen.
C_KeyboardMovement::C_KeyboardMovement(Object* owner)
: Component(owner), moveSpeed(200) {}
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 🙂