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.

As I wrote the code for last weeks tutorial (Animation in Four Directions) I noticed a couple of issues with the current code:

  1. When we have an animation consisting of one frame (i.e. our player’s idle animation) we are requesting a new frame each update step. Ideally, we only want to process the frame once.
  2. When we move the player diagonally no animation is played.

Luckily for us, both issues are easily fixed so we’ll also have time to update the camera component. With the platform game it was ok for the camera to only follow us on the x-axis but now we can move in four directions we want to provide the camera with the same functionality. With that done we’ll no longer be able to walk off the top or bottom of the screen and get lost in the abyss. As I’ve numbered the issues let’s start this week by fixing issue number one. Currently our Animation::UpdateFrame looks like this:

Animation.cpp
bool Animation::UpdateFrame(float deltaTime)
{
if(frames.size() > 0)
{
currentFrameTime += deltaTime;

if(currentFrameTime >=
frames[currentFrameIndex].displayTimeSeconds)
{
currentFrameTime = 0.f;
IncrementFrame();
return true;
}
}

return false;
}

We check if the current time for each frame has exceeded the frames display time and if it has then we increment the frame and return true. When we return true the animation component has to fetch the new frame. So how does it work when we only have one frame? Not great. When we created the idle animation we set the display time to 0 so it just keeps returning true. This is a waste of resources as the animation component keeps fetching new frames. A quick way to fix this is just to check if the numbers of frames are greater than 1 instead of 0.

Animation.cpp
bool Animation::UpdateFrame(float deltaTime)
{
// Now we check if frame size is greater than 1 instead of 0
if(frames.size() > 1)
{
currentFrameTime += deltaTime;

if(currentFrameTime >=
frames[currentFrameIndex].displayTimeSeconds)
{
currentFrameTime = 0.f;
IncrementFrame();
return true;
}
}

return false;
}

This almost fixes the problem however now it won’t display our idle animation at all as there is only the one frame it never returns true. We need to return true at least once when we transition to the animation so that we display the frame and then we no longer want to return true again. We can accomplish this with a simple bool.

Animation.hpp
class Animation
{

private:

bool releaseFirstFrame;
};

We want this bool to be true at the beginning of an animations life so initialise it in the constructor and when we reset the animation.

Animation.cpp
Animation::Animation() : frames(0), currentFrameIndex(0), 
currentFrameTime(0.f), releaseFirstFrame(true) { }

void Animation::Reset()
{
currentFrameIndex = 0;
currentFrameTime = 0.f;
releaseFirstFrame = true;
}

In the UpdateFrame function, we first check if this bool is true and if it is we return true, which lets the animation component know that it should pull the first frames data.

Animation.cpp
bool Animation::UpdateFrame(float deltaTime)
{
//TODO: A bit cumbersome. Is there another way to do this?
if(releaseFirstFrame)
{
releaseFirstFrame = false;
return true;
}

if(frames.size() > 1)
{
currentFrameTime += deltaTime;

if(currentFrameTime >=
frames[currentFrameIndex].displayTimeSeconds)
{
currentFrameTime = 0.f;
IncrementFrame();
return true;
}
}

return false;
}

I’ve left myself a todo so as this may not be the most elegant way to accomplish what we want (but it does indeed accomplish what we want).

If you run the game you won’t notice anything different but don’t be disheartened because the game is now that bit more efficient.

Onto fix number two: no animation is played when we move diagonally. The reason for this is pretty simple, when you press a key it sets an animation state and to move diagonally you need to hold two keys (i.e. left and up), which means that each frame the controller is telling the animation component to set the state first to up (as an example) and then to left. When we transition to a new animation it is reset so the controller is resetting the animation each frame and nothing is being played.

This fix for this is to separate the logic for checking for key presses and calculating the animation state.

C_KeyboardMovement.cpp
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);

if(xMove == 0 && yMove == 0)
{
animation->SetAnimationState(AnimationState::Idle);
}
else
{
animation->SetAnimationState(AnimationState::Walk);

if(abs(xMove) > abs(yMove))
{
if(xMove < 0)
{
animation->SetAnimationDirection(FacingDirection::Left);
}
else
{
animation->SetAnimationDirection(FacingDirection::Right);
}
}
else
{
if(yMove < 0)
{
animation->SetAnimationDirection(FacingDirection::Up);
}
else
{
animation->SetAnimationDirection(FacingDirection::Down);
}
}
}
}

Doing it this way ensures we are only setting one animation state each frame and it no longer fluctuates between competing states. Now when you run the game the character will continue to animate even when moving diagonally.That’s it for bug fixes. The last change I want to make this week is to update the camera component so that it follows the player on both axes.

C_Camera.cpp
void C_Camera::LateUpdate(float deltaTime)
{
if(window)
{
sf::View view = window->GetView();

const sf::Vector2f& playerPos = owner->transform->GetPosition();
view.setCenter(playerPos.x, playerPos.y);

window->SetView(view);
}
}

A small change but now we can move around the environment and see where we are going, which is handy. There’s not much to see at the moment but that will change next week when I introduce a few new maps.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 🙂