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 added the final touches to our projectile animation however there is still something missing: the ability to shoot projectiles. The player will expect at least one arrow to be released at a certain point in the animation. The arrow will be its own object separate from that of the player and it will need to collide and interact with other objects in different ways (hurting other characters but not a solid wall for example).Before we can start coding we should find an image for our arrow. I adapted one from here but you can use any sprite you like. Once downloaded make sure you add it to your project.I’ve created a sprite sheet consisting of four images. Each image shows an arrow pointing in a different direction. In future, we can use the one image and rotate it to suit our needs but this will do for now. The sprites I used are included in this week’s resources/LPC/Weapons folder in the Github repo.


The arrow sprite sheet.

We’ll use the previously written C_ProjectileAttack class to spawn the projectiles. To spawn the projectile as a new object we need access to the object collection (to add the projectile to), the working directory (to retrieve the directory for our new projectile sprite), and texture allocator (to load the new sprite and add it to the pool of existing textures to be used within the game). We’ll add references and setters for these classes.

C_ProjectileAttack.hpp
#include “ObjectCollection.hpp”
#include "WorkingDirectory.hpp"


class C_ProjectileAttack : public Component
{
public:

void SetObjectCollection(ObjectCollection* objects);
void SetWorkingDirectory(WorkingDirectory* workingDirectory);
void SetTextureAllocator(ResourceAllocator<sf::Texture>* textureAllocator);



private:

ObjectCollection* objects;
WorkingDirectory* workingDirectory;
ResourceAllocator<sf::Texture>* textureAllocator;

};

C_ProjectileAttack.hpp
void 
C_ProjectileAttack::SetObjectCollection(ObjectCollection* objects)
{
this->objects = objects;
}

void
C_ProjectileAttack::SetWorkingDirectory(WorkingDirectory* workingDirectory)
{
this->workingDirectory = workingDirectory;
}

void
C_ProjectileAttack::SetTextureAllocator(ResourceAllocator<sf::Texture>* textureAllocator)
{
this->textureAllocator = textureAllocator;
}

Having all of these separate set functions makes me think that we should spend some time creating a shared repository of useful classes. This repository would include the working directory, texture allocator, and object collection at the very least, and would be shared with every component at creation so we no longer need to worry about injecting a component’s dependencies. This is something we’ll look into next week.The mutator functions need to be called when we add the component to our player in SceneGame.

SceneGame.cpp
void SceneGame::OnCreate()
{
std::shared_ptr<Object> player = std::make_shared<Object>();



auto projectileAttack = player->AddComponent<C_ProjectileAttack>();
// Inject the necessary dependencies to create a new projectile.
projectileAttack->SetInput(&input);
projectileAttack->SetObjectCollection(&objects);
projectileAttack->SetWorkingDirectory(&workingDir);
projectileAttack->SetTextureAllocator(&textureAllocator);

}

Back to the projectile attack component. As we now have access to the texture allocator we’ll write a Start function, which is called at the beginning of the next frame, to add the texture to our resource allocator and store its id.

C_ProjectileAttack.hpp
public:


void Start() override;

private:


int projectileTextureID;
};

C_ProjectileAttack.cpp
void C_ProjectileAttack::Start()
{
projectileTextureID =
textureAllocator->Add(workingDirectory->Get()
+ "LPC/Weapons/arrow.png");
}
We can use this texture id when spawning a projectile. Talking about spawning projectiles let’s create a new function to do just that.
C_ProjectileAttack.hpp
class C_ProjectileAttack : public Component
{


private:
void SpawnProjectile();


};

C_ProjectileAttack.cpp
void C_ProjectileAttack::SpawnProjectile()
{
// Create new object.
std::shared_ptr<Object> projectile = std::make_shared<Object>();

// Set objects position
projectile->transform->SetPosition(owner->transform->GetPosition());

// Add sprite component to object
auto projSprite = projectile->AddComponent<C_Sprite>();
projSprite->SetTextureAllocator(textureAllocator);
projSprite->Load(projectileTextureID);
projSprite->SetDrawLayer(DrawLayer::Entities);
projSprite->SetSortOrder(100);

// Add object to collection
objects->Add(projectile);
}

The function:

  1. Creates a new object.
  2. Sets it position equal to the player’s position using the transform component that is automatically added to all new objects.
  3. Adds a sprite component and loads the arrow texture.
  4. Adds the newly created object to the object collection so it is updated and drawn the window.

Most of the objects in the game will follow these steps, with more or possibly even fewer components added depending on the objects complexity. As this object only has a sprite component (for now) it won’t move or collide with other objects yet.When we create frame actions in a future tutorial we’ll want this function to be called during a specific animation frame (where the player is just about to release the bow string) but for now, we’ll call it whenever we press the attack button.

C_ProjectileAttack.cpp
void C_ProjectileAttack::Update(float deltaTime)
{
if(input->IsKeyDown(Input::Key::E))
{
// Call Spawn Projectile whenever we press the attack button.
SpawnProjectile();
animation->SetAnimationState(AnimationState::Projectile);
}
else if (input->IsKeyUp(Input::Key::E))
{
animation->SetAnimationState(AnimationState::Idle);
}
}

Now if you run the game and press the projectile attack button (e), you’ll get something like this:
The texture we use contains four sprites for each direction.
The texture we use contains four sprites for each direction. We don’t want to show all four sprites every time we fire an arrow, so we need to be able to select a sprite from within the sprite sheet depending on which direction we are facing. We’ll look at accomplishing this over the next few weeks. However next week we’ll take a break from projectiles and look at creating a shared repository of useful classes that every component has access to. 

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 🙂