Firefly

C++ Game [2D] (2018)
Top-Down Shooter

What Is Firefly?


Firefly is a top-down physics based 2D game where you play as a ship in an unfamiliar star system and must fight against pirates as a bounty hunter. My inspiration was a game jam I did back in college where in the Unreal Engine 4 I initially made the game in 2 days and have ever since wanted to remake it in C++.

Tell Me More!


This game is perhaps my most colourful and prettiest project I've ever worked on taking inspiration from FTL, some album covers and other things. I never really get tired of looking at gameplay of this game. This coupled with the fast action arcade gameplay loop probably makes this my best game project to date in a way. While it may lack some things, I may even come back to this in future and remake it in Helios or a future game engine of mine.

What I Learned/Achieved


This project allowed me to use the vector mathematics I learned during my first year of university within a game. The gravity/velocity system as well as 2D collision using circles in a dynamic star system gives the game a unique feel and playstyle. Additionally, the graphics which utilise parallax to give a sense of depth by making the background move slower than the player shows how a simple 2D graphics library can make a graphically rich game.

What Could Be Improved?


The game uses the S2D framework (Staffordshire university internal framework) which I have adapted to have complex graphical effects like parallax scrolling and smooth animations. However, this framework has some major problems like for instance the mouse buttons causing a stutter whenever clicked. This issue really makes the game look tacky and I wish there was a way to fix it without switching to another framework.

Conclusion


Overall, this is probably my most rounded and complete project yet. Love it.

Code Sample


Context: Enemy1 is the normal enemy within the game. It simply flys towards the player + a lead offset in order to catch up. Once it gets close enough, it begins firing its gun.

Enemy1.h

			
#pragma once

#include "KinematicActor.h"
#include "Projectile1.h"
#include "Player.h"
#include "Explosion.h"

class Enemy1 : public KinematicActor
{
public:
	S2D::Texture2D *srcTexture1;
	S2D::Rect *srcRect1;
	Player* playerRef = nullptr;
	float health = 1;

private:
	float deltaTime2;
	float perHitReduction = .7;
	int shootTimer = 0;
	const int fireRate = 700;

public:
	Enemy1();
	virtual ~Enemy1() override;
	virtual void LoadContent() override;
	virtual void Tick(int deltaTime) override;
	virtual void Draw(Camera &worldCamera) override;

	virtual void Collision(int deltaTime, std::vector<Actor*> collidingActors);
};
			
			

Enemy1.cpp

			
#include "Enemy1.h"

Enemy1::Enemy1()
{
	SetActorCollisionType(CollisionType::BOUNDING_CIRCLE);
	SetActorCollisionResponse(CollisionResponse::HIT);
	SetZSortIndex(0);

	SetActorScale(.1);
	SetActorBoundingCircleRadius(240);
}

Enemy1::~Enemy1()
{
	delete srcTexture1;
	delete srcRect1;
}

void Enemy1::LoadContent()
{
	KinematicActor::LoadContent();

	srcTexture1 = new S2D::Texture2D;
	srcTexture1->Load("Textures/fighter_alien2TMP.png", false);
	srcRect1 = new S2D::Rect(0.0f, 0.0f, 768, 768);
}

void Enemy1::Tick(int deltaTime)
{
	KinematicActor::Tick(deltaTime);

	deltaTime2 = (float)deltaTime / 1000;
	shootTimer += deltaTime;

	AddToActorPosition(velocity * deltaTime  * GetActorScale() / 0.1);

	if (playerRef != nullptr)
	{
		float distanceToPlayer = VectorDistance(GetActorPosition() - playerRef->GetActorPosition());
		if (distanceToPlayer <= 2000)
		{
			SetActorRotation(LookAtAngle(GetActorPosition(), playerRef->GetActorPosition()
				+ playerRef->velocity * deltaTime * distanceToPlayer * .02) - 90);
			velocity += RotateVector(S2D::Vector2(0,-1), LookAtAngle(GetActorPosition(), playerRef->GetActorPosition()) - 90)
				* S2D::MathHelper::Clamp(VectorDistance(playerRef->GetActorPosition() - GetActorPosition()) * .001, 0, .5)
				* deltaTime2 * 10;

			if (shootTimer >= fireRate + (rand() % 300) - 150)
			{
				Projectile1* curProj = SpawnObject<Projectile1>();
				curProj->SetActorPosition(GetActorPosition());
				curProj->SetActorRotation(GetActorRotation());
				curProj->velocity = velocity;
				curProj->owner = this;
				shootTimer = 0;
			}
		}
		else if (distanceToPlayer >= 5000)
		{
			DestroyObject(this);
		}
	}
}

void Enemy1::Draw(Camera &worldCamera)
{
	KinematicActor::Draw(worldCamera);

	DrawSprite(srcTexture1, GetActorPosition(), worldCamera, srcRect1, GetActorScale(), GetActorRotation(),
		&S2D::Color(.8, .5, 1, 1));
}

void Enemy1::Collision(int deltaTime, std::vector<Actor*> collidingActors)
{
	KinematicActor::Collision(deltaTime, collidingActors);

	S2D::Vector2 hitDirection = S2D::Vector2(0, 0);
	std::vector<Actor*> hitResponseActors;
	for (auto& e : collidingActors)
		if (e->GetActorCollisionResponse() == CollisionResponse::HIT && !dynamic_cast<Player*>(e))
		{
			hitResponseActors.push_back(e);
			hitDirection += e->GetActorPosition();
		}

	if (hitResponseActors.size() > 0)
	{
		hitDirection = hitDirection / hitResponseActors.size();
		hitDirection -= GetActorPosition();

		hitDirection = hitDirection / sqrt(pow(hitDirection.X, 2) + pow(hitDirection.Y, 2));

		velocity *= hitDirection * perHitReduction * -1;
		AddToActorPosition((hitDirection * -1) * deltaTime / 2);
	}

	for (auto& e : collidingActors)
		if (Projectile1 *tmp = dynamic_cast<Projectile1*>(e))
		{
			health -= .5;
			if (health <= 0)
			{
				if (Player *tmp2 = dynamic_cast<Player*>(tmp->owner))
				{
					tmp2->playerScore += 10;
				}
				Explosion *tmp2 = SpawnObject<Explosion>();
				tmp2->SetActorPosition(GetActorPosition());
				DestroyObject(this);
			}
		}
}
			
			

Download

Back
GitHub LinkedIn
© 2023 Arrien Bidmead