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.

We recently wrote a feature that enables us to call a function on a specific animation frame. This week we’ll take advantage of this system for our projectile attack. At the moment we are spawning projectiles as soon as we press the projectile attack key, which doesn’t look quite right. What we really want is for the projectile to spawn at the correct moment in the animation which is when the player’s character releases the bowstring.

Before we write the animation action I want to make one small change to C_Animation. As I was writing the code for this tutorial I noticed that I’ve used a map to store the animations. A map will store the entries in a specific order (sorted using caparison function ‘Compare’). As we don’t need the entries sorted we can swap to an unordered_map as they are generally faster for insertions and retrievals.

As unordered_maps store elements using a hash table we may need to provide a hash class as the third template parameter. Depending on the compiler you use this step may not be necessary. The easiest way to check if it is necessary for you is to change the definition in C_Animation from map to unordered_map like so:
C_Animation.hpp
class C_Animation : public Component
{
…
    
private:
    std::unordered_map<AnimationState, AnimationList> animations;
…
};
If you can compile without errors then you don’t need to follow the next steps and can jump directly to where we edit how we create the animations in SceneGame.
However, if you receive the compilation error: “Implicit instantiation of undefined template” then you need to provide your own hash for the enum class. We created one in a previous tutorial but it’s located in the C_ProjectileAttack header, as this class now requires it we’ll move it into its own file.
C_ProjectileAttack.hpp
#ifndef C_ProjectileAttack_hpp
#define C_ProjectileAttack_hpp

…

// Remove the EnumClassHash as we’ll be moving it to its own file.
/*
struct EnumClassHash
{
template 
std::size_t operator()(T t) const
{
return static_cast(t);
}
};
*/

…

#endif /* C_ProjectileAttack_hpp */
Create a new class called EnumClassHash and add the code we just removed from C_ProjectileAttack.
EnumClassHash.hpp
#ifndef EnumClassHash_hpp
#define EnumClassHash_hpp

struct EnumClassHash
{
template
std::size_t operator()(T t) const
{
return static_cast(t);
}
};

#endif /* EnumClassHash_hpp */

C_ProjectileAttack still needs access to this class so include our new file.

C_ProjectileAttack.hpp
#include “EnumClassHash.hpp"
…

Include the new file in C_Animation and then use it as the third parameter in the AnimationList (which I’ve also changed to use an unordered_map instead of a map) and the animations collection.

C_Animation.hpp
#include <unordered_map>
#include "EnumClassHash.hpp"

…

using AnimationList = std::unordered_map<FacingDirection, std::shared_ptr, EnumClassHash>;

class C_Animation : public Component
{
…

private:
std::unordered_map<AnimationState, AnimationList, EnumClassHash> animations;
…
};
As we’ve changed the definition of animations we also need to edit SceneGame where we actually create the animations. Change them to use an unordered_map and if necessary add the EnumClassHash as the third template parameter.
SceneGame.cpp
void SceneGame::OnCreate()
{
…

std::unordered_map<FacingDirection, std::shared_ptr, EnumClassHash> idleAnimations;
…

std::unordered_map<FacingDirection, std::shared_ptr, EnumClassHash> walkingAnimations;
…

std::unordered_map<FacingDirection, std::shared_ptr, EnumClassHash> projectileAnimations;
…
}

With that done we can start on the goal for this week: spawning the projectiles as an animation action. The Animation class already has the functionality for adding frame actions but we don’t interact directly with it, instead we’ll write a function in the animation component that will. This way any component can retrieve the animation component and add an action.

C_Animation.hpp
class C_Animation : public Component
{
public:
…

void AddAnimationAction(AnimationState state, FacingDirection dir, int frame, AnimationAction action);

…
};
To set an animation action we need to know the animation state (Idle, Walk, or Projectile), direction (Up, Down, Left, or Right), and the frame that we want the animation to run on. The frame is an integer between 0 and one less than the animations frame count. For example, the players projectile animation has 10 animation frames so we would expect the frame integer to be between 0 and 9 as the frame count starts at 0. The last argument is the action to be run on that frame.
The definition of the function looks like this:
C_Animation.hpp
void C_Animation::AddAnimationAction(AnimationState state, FacingDirection dir, int frame, AnimationAction action)
{
	auto animationList = animations.find(state);

	if(animationList != animations.end())
	{
		auto animation = animationList->second.find(dir);

		if(animation != animationList->second.end())
		{
			animation->second->AddFrameAction(frame, action);
		}
	}
}
We first attempt to find the state and then the direction for that state before we can call the AddFrameAction for that specific animation. If the entity does not have an animation for that state or direction then no action is added.
That should be it for setting up animation actions, we’ll test it by adding four actions to shoot a projectile in the four directions. Do this in the Start function of C_ProjectileAttack.
C_ProjectileAttack.cpp
void C_ProjectileAttack::Start()
{
…
	animation->AddAnimationAction(
	AnimationState::Projectile, // 1
	FacingDirection::Up, // 2
	9, // 3
	std::bind(&C_ProjectileAttack::SpawnProjectile, this)); // 4

	animation->AddAnimationAction(AnimationState::Projectile, FacingDirection::Left, 9, std::bind(&C_ProjectileAttack::SpawnProjectile, this));
	animation->AddAnimationAction(AnimationState::Projectile, FacingDirection::Down, 9, std::bind(&C_ProjectileAttack::SpawnProjectile, this));
	animation->AddAnimationAction(AnimationState::Projectile, FacingDirection::Right, 9, std::bind(&C_ProjectileAttack::SpawnProjectile, this));
}

I’ve numbered the first call to the AddAnimationAction so we can discuss it in more detail.

  1. We want to add an action to the projectile animation for this entity so we pass that as the first argument.
  2. In this instance, we are adding an action for the Up direction (we add actions for the other 3 directions directly after).
  3. The animation has 10 frames and we want to want the action to be run on the last frame so, and remembering that the count starts at 0, we pass 9 in as the third parameter.
  4. The fourth argument is the AnimationAction. It’s the function we want to run on the specified frame. If we look at the definition of AnimationAction:
using AnimationAction = std::function<void(void)>;
We can see that it is a type alias for std::function. std::function is a function wrapper, it can store and invoke any callable object. In this case, the callable object is the function we want to be called on a specific frame. We use std::bind to create a callable object from the function. We pass the function as the first parameter and a list of the callable object’s parameters. A function that is non-static has an implicit first parameter, which is the object that owns the function (C_ProjectileAttack in this case), so we need to pass a reference to C_ProjectileAttack as the second parameter.
Because we spawn the projectiles during the animation we can remove the direct call to SpawnProjectile in the Update function of C_ProjectileAttack.
C_ProjectileAttack.cpp
void C_ProjectileAttack::Update(float deltaTime)
{
	if(owner->context->input->IsKeyDown(Input::Key::E))
	{
		// We call SpawnProjectile during the animation
		// so no longer need to call it directly.
		//SpawnProjectile();
		animation->SetAnimationState(AnimationState::Projectile);
	}
	else if (owner->context->input->IsKeyUp(Input::Key::E))
	{
		animation->SetAnimationState(AnimationState::Idle);
	}
}
If you run the game and shoot an arrow using the ‘e’ key, a projectile should now be spawned at the end of the animation rather than the beginning. Make sure you test it in all four directions.
Shooting arrow in 4 directions using animation actions.
Arrows spawned on last frame of animation using action frames.
You may have noticed that the arrows disappear when they collide with the fence in the gif above. This is something we’ll add over the next few weeks as we improve and add to our collision system by implementing a way for objects to communicate when they collide.
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 🙂