The game basically has a player (PlayerCharacter
class) that can move around the world. It has a 2D world made of tiles managed by TileEngine
class. The player can shoot projectiles and can collide with the boundary of the map and any solid tiles.
Entity.h
:
#ifndef ENTITY_H
#define ENTITY_H
#include <cmath>
class Entity
{
public:
Entity();
Entity(float px, float py) : mPosX(px), mPosY(py) {}
~Entity();
//Getters
float GetPosX() { return mPosX; }
float GetPosY() { return mPosY; }
//Setters
void SetPosX(float val) { mPosX = val; }
void SetPosY(float val) { mPosY = val; }
protected:
float mPosX;
float mPosY;
};
#endif //ENTITY_H
Entity.cpp
:
#include "Entity.h"
Entity::Entity()
{
}
Entity::~Entity()
{
}
PlayerCharacter.h
:
#ifndef PLAYERCHARACTER_H
#define PLAYERCHARACTER_H
#include "Entity.h"
#include "WeaponStats.h"
#include "Projectile.h"
#include <SFML/Graphics.hpp>
#include <vector>
struct KeyState{
bool UpPressed;
bool DownPressed;
bool LeftPressed;
bool RightPressed;
bool LMBPressed;
};
class PlayerCharacter : public Entity
{
public:
//Constructor and Destructor
PlayerCharacter();
PlayerCharacter(float pPosX, float pPosY, float pSpeed = 0, float pHealth = 0);
PlayerCharacter(float pPosX, float pPosY, sf::Texture pTexture, float pSpeed = 0, float pHealth = 0);
~PlayerCharacter();
//Getters
float GetSpeed() { return mSpeed; }
float GetHealth() {return mHealth; }
sf::Sprite GetSprite() { return mSprite; }
sf::Texture GetTexture() { return mTexture; }
float GetDirection() { return mDirection; }
WeaponStats GetWeapon() { return mWeapon; }
std::vector<Projectile>& GetProjectiles() { return mProjectiles; }
//Setters
void SetSpeed(float val) { mSpeed = val; }
void SetHealth(float val);
void SetSprite(sf::Sprite val) { mSprite = val; }
void SetTexture(sf::Texture val);
void SetDiretion(float val) { mDirection = val; }
void SetWeapon(WeaponStats pWeapon) { mWeapon = pWeapon; }
void SetProjectiles(std::vector<Projectile>& pProjectiles) { mProjectiles = pProjectiles; }
//Member Functions
void Render(sf::RenderWindow* pTarget); //Draw the player sprite and the projectiles
void Update(float TimeStep, KeyState val, bool WillCollide); //Update the projectiles positions and the players sprite
void GenerateProjectile();
private:
void UpdateProjectiles(float TimeStep);
void RenderProjectiles(sf::RenderWindow* pTarget);
void UpdateSprite();
void Move(KeyState val, float TimeStep); //Move the player in the pressed directions
sf::Sprite mSprite;
sf::Texture mTexture;
float mSpeed;
float mHealth;
float mDirection;
WeaponStats mWeapon;
std::vector<Projectile> mProjectiles;
sf::Clock mWeaponClock;
};
#endif // PLAYERCHARACTER_H
PlayerCharacter.cpp
:
#include "PlayerCharacter.h"
PlayerCharacter::PlayerCharacter()
{
}
PlayerCharacter::~PlayerCharacter()
{
}
PlayerCharacter::PlayerCharacter(float pPosX, float pPosY, sf::Texture pTexture, float pSpeed, float pHealth)
{
SetPosX(pPosX);
SetPosY(pPosY);
SetSpeed(pSpeed);
SetHealth(pHealth);
SetTexture(pTexture);
}
PlayerCharacter::PlayerCharacter(float pPosX, float pPosY, float pSpeed, float pHealth)
{
SetPosX(pPosX);
SetPosY(pPosY);
SetSpeed(pSpeed);
SetHealth(pHealth);
}
void PlayerCharacter::SetHealth(float val)
{
mHealth = val;
if (mHealth < 0) //Health should not be less than zero
{
mHealth = 0;
}
}
void PlayerCharacter::SetTexture(sf::Texture val)
{
mTexture = val;
mSprite.setTexture(mTexture);
mSprite.setOrigin(mTexture.getSize().x / 2, mTexture.getSize().y / 2); //Set the origin to be the center of the texture so it rotates around the center
//mSprite.setOrigin(0, 0);
}
void PlayerCharacter::Move(KeyState var, float TimeStep)
{
if (var.UpPressed)
SetPosY(mPosY -= mSpeed * TimeStep);
if (var.DownPressed)
SetPosY(mPosY += mSpeed * TimeStep);
if (var.LeftPressed)
SetPosX(mPosX -= mSpeed * TimeStep);
if (var.RightPressed)
SetPosX(mPosX += mSpeed * TimeStep);
}
void PlayerCharacter::Render(sf::RenderWindow* pTarget)
{
pTarget->draw(mSprite);
RenderProjectiles(pTarget);
}
void PlayerCharacter::UpdateSprite()
{
mSprite.setPosition(mPosX, mPosY);
mSprite.setRotation(mDirection + 90);
}
void PlayerCharacter::UpdateProjectiles(float TimeStep)
{
for (unsigned int i = 0; i < mProjectiles.size(); i++)
{
mProjectiles[i].Update(TimeStep);
}
}
void PlayerCharacter::RenderProjectiles(sf::RenderWindow* pTarget)
{
for (unsigned int i = 0; i < mProjectiles.size(); i++)
{
mProjectiles[i].Render(pTarget);
}
}
void PlayerCharacter::Update(float TimeStep, KeyState val, bool WillCollide)
{
if (!WillCollide) //LevelEntityManager will pass a true of false wether it is allowed to move
Move(val, TimeStep);
UpdateSprite();
if (val.LMBPressed && mWeaponClock.getElapsedTime().asSeconds() > mWeapon.mFireRate)
{
GenerateProjectile();
mWeaponClock.restart();
}
UpdateProjectiles(TimeStep);
}
void PlayerCharacter::GenerateProjectile()
{
float RandDir = mDirection + (-1 + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(2)))) * mWeapon.mBulletSpred; //Random number between 0 and 1 * Spread
Projectile Temp(mPosX, mPosY, RandDir, mWeapon.mBulletSpeed, mWeapon.mDamage, mWeapon.mBulletTexture);
mProjectiles.push_back(Temp);
}
FunctionLib.h
:
#ifndef FUNCTIONLIB_H
#define FUNCTIONLIB_H
//required headers
#include "Entity.h"
#include "SFML/Graphics.hpp"
//required preprocessor macros
#define _PI 3.14159265
//member functions
float DistanceBetween(float px1, float py1, float px2, float py2);
float DistanceBetween(Entity* pEntity1, Entity* pEntity2);
float ToDegrees(float radians);
float ToRadians(float degrees);
float DirectionToPoint(float ax, float ay, float bx, float by); //returns in radians the angle from a(x, y) to b(x, y)
float DirectionToPoint(Entity* a, Entity* b); //returns in radians the angle from a to b
std::vector<sf::Vector2f> GenerateBoxFromSprite(sf::Sprite pSprite);
std::vector<sf::Vector2f> GenerateBoxFromDimentions(float px, float py, float width, float height);
#endif // FUNCTIONLIB_H
FunctionLib.cpp
:
#include "FunctionLib.h"
float DistanceBetween(float px1, float py1, float px2, float py2)
{
float DiffX = px1 - px2;
float DiffY = py1 - py2;
float DistSqr = DiffX * DiffX + DiffY * DiffY;
return sqrt(DistSqr);
}
float DistanceBetween(Entity* pEntity1, Entity* pEntity2)
{
float DiffX = pEntity1->GetPosX() - pEntity2->GetPosX();
float DiffY = pEntity1->GetPosY() - pEntity2->GetPosY();
float DistSqr = DiffX * DiffX + DiffY * DiffY;
return sqrt(DistSqr);
}
float ToDegrees(float radians)
{
return radians * (180 / _PI);
}
float ToRadians(float degrees)
{
return degrees * (_PI / 180);
}
float DirectionToPoint(float ax, float ay, float bx, float by)
{
float DiffX = bx - ax;
float DiffY = by - ay;
return atan2(DiffY, DiffX);
}
float DirectionToPoint(Entity* a, Entity* b)
{
float DiffX = b->GetPosX() - a->GetPosX();
float DiffY = b->GetPosY() - a->GetPosY();
return atan2(DiffY, DiffX);
}
std::vector<sf::Vector2f> GenerateBoxFromSprite(sf::Sprite pSprite)
{
sf::Vector2f P1((pSprite.getPosition().x - pSprite.getOrigin().x), (pSprite.getPosition().y - pSprite.getOrigin().y)); //Top right corner
sf::Vector2f P2((pSprite.getPosition().x + pSprite.getOrigin().x), (pSprite.getPosition().y - pSprite.getOrigin().y)); //Top left
sf::Vector2f P3((pSprite.getPosition().x - pSprite.getOrigin().x), (pSprite.getPosition().y + pSprite.getOrigin().y)); //Bottom Right
sf::Vector2f P4((pSprite.getPosition().x + pSprite.getOrigin().x), (pSprite.getPosition().y + pSprite.getOrigin().y)); //Bottom left
std::vector<sf::Vector2f> CornerPoints;
CornerPoints.push_back(P1);
CornerPoints.push_back(P2);
CornerPoints.push_back(P3);
CornerPoints.push_back(P4);
return CornerPoints;
}
std::vector<sf::Vector2f> GenerateBoxFromDimentions(float px, float py, float width, float height)
{
sf::Vector2f P1(px, py); //Top right corner
sf::Vector2f P2(px + width, py); //Top left
sf::Vector2f P3(px, py + height); //Bottom Right
sf::Vector2f P4(px + width, py + height); //Bottom left
std::vector<sf::Vector2f> CornerPoints;
CornerPoints.push_back(P1);
CornerPoints.push_back(P2);
CornerPoints.push_back(P3);
CornerPoints.push_back(P4);
return CornerPoints;
}
WeaponStats.h
:
#ifndef WEAPONSTATS_H_INCLUDED
#define WEAPONSTATS_H_INCLUDED
#include <SFML/Graphics.hpp>
struct WeaponStats
{
float mDamage;
float mBulletSpeed;
float mFireRate;
float mBulletSpred;
sf::Texture mBulletTexture;
};
enum WeaponTypes
{
RifleWeapon,
SMGWeapon,
ShotGunWeapon,
};
WeaponStats GetWeaponStat(WeaponTypes WeaponID);
#endif // WEAPONSTATS_H_INCLUDED
WeaponStats.cpp
:
#include "WeaponStats.h"
WeaponStats GetWeaponStat(WeaponTypes WeaponID)
{
WeaponStats WeaponIDStats;
sf::Texture WeaponIDTexture;
switch (WeaponID)
{
case RifleWeapon:
WeaponIDTexture.loadFromFile("Bullet.png");
WeaponIDStats.mBulletSpeed = 25;
WeaponIDStats.mDamage = 10;
WeaponIDStats.mFireRate = 0.1;
WeaponIDStats.mBulletSpred = 2;
break;
case SMGWeapon:
WeaponIDTexture.loadFromFile("Bullet.png");
WeaponIDStats.mBulletSpeed = 20;
WeaponIDStats.mDamage = 7;
WeaponIDStats.mFireRate = 0.05;
WeaponIDStats.mBulletSpred = 1;
break;
case ShotGunWeapon:
WeaponIDTexture.loadFromFile("Bullet.png");
WeaponIDStats.mBulletSpeed = 20;
WeaponIDStats.mDamage = 20;
WeaponIDStats.mFireRate = 2;
WeaponIDStats.mBulletSpred = 5;
break;
default:
break;
}
WeaponIDStats.mBulletTexture = WeaponIDTexture;
return WeaponIDStats;
}
Projectile.h
:
#ifndef PROJECTILE_H_INCLUDED
#define PROJECTILE_H_INCLUDED
#include "Entity.h"
#include "FunctionLib.h"
#include <cmath>
#include <SFML/Graphics.hpp>
class Projectile : public Entity
{
public:
//Construtor and Destructor
Projectile();
Projectile(float px, float py, float pDirection, float pVelocity, float pDamage, sf::Texture mSpriteTexture); //You can give it a direction and a speed
~Projectile();
//getters
float GetVelX() { return VelX; }
float GetVelY() { return VelY; }
float GetDamage() { return mDamage; }
sf::Sprite GetSprite() { return mSprite; }
float GetDirection() { return mDirection; }
//setters
void SetVelX(float pVelX) { VelX = pVelX; }
void SetVelY(float pVelY) { VelY = pVelY; }
void SetDamage(float pDamage) { mDamage = pDamage; }
void SetSprite(sf::Sprite pSprite) { mSprite = pSprite; }
void SetDirection(float pDirection);
void SetTexture(sf::Texture pTexture);
//public member functions
void Render(sf::RenderWindow* pTarget) {pTarget->draw(mSprite);} //draw mSprite to screen
void Update(float TimeStep);
private:
//private member functions
void UpdateSprite();
void CalculateVelocities(float pDirection, float pVelocity); //Based on a direction and a velocity calculate the velocity x and y
//private member variables
float mDirection;
float VelX, VelY;
float mDamage;
sf::Texture mSpriteTexture;
sf::Sprite mSprite;
};
#endif // PROJECTILE_H_INCLUDED
Projectile.cpp
:
#include "Projectile.h"
Projectile::Projectile()
{
}
Projectile::~Projectile()
{
}
Projectile::Projectile(float px, float py, float pDirection, float pVelocity, float pDamage, sf::Texture mSpriteTexture)
{
CalculateVelocities(pDirection, pVelocity);
SetDamage(pDamage);
SetTexture(mSpriteTexture);
SetDirection(pDirection);
SetPosX(px);
SetPosY(py);
}
void Projectile::Update(float TimeStep)
{
mPosX += VelX * TimeStep;
mPosY += VelY * TimeStep;
UpdateSprite();
}
void Projectile::SetDirection(float pDirection)
{
mDirection = pDirection;
mSprite.setRotation(mDirection + 90);
}
void Projectile::SetTexture(sf::Texture pTexture)
{
mSpriteTexture = pTexture;
mSprite.setTexture(mSpriteTexture);
mSprite.setOrigin(mSpriteTexture.getSize().x / 2, mSpriteTexture.getSize().y / 2);
}
void Projectile::UpdateSprite()
{
mSprite.setTexture(mSpriteTexture);
mSprite.setPosition(mPosX, mPosY);
}
void Projectile::CalculateVelocities(float pDirection, float pVelocity)
{
VelX = ToDegrees(cos(ToRadians(pDirection))) * pVelocity;
VelY = ToDegrees(sin(ToRadians(pDirection))) * pVelocity;
}
TileEngine.h
:
#include "SFML/Graphics.hpp"
struct Tile
{
sf::Sprite mTileSprite;
bool mSolidState;
};
class TileEngine
{
public:
TileEngine(); //Defult ctor
TileEngine(std::string pFileLocation); //Generate from a file
TileEngine(float pTileWidth, float pTileHeight, unsigned int pMapSizeX, unsigned int pMapSizeY, sf::Texture pTileSet, std::vector<std::vector<int> >& pTileIDVec, std::vector<std::vector<bool> >& pSolidStateVec, float pPosX = 0, float pPosY = 0); //Generate from paramiters
TileEngine(float pTileWidth, float pTileHeight, unsigned int pMapSizeX, unsigned int pMapSizeY, sf::Texture pTileSet, std::vector<std::vector<Tile> > pTiles, float pPosX = 0, float pPosY = 0); //Generate from already generated Tile vector
~TileEngine();
//Getters
float GetPosX() { return mPosX; }
float GetPosY() { return mPosY; }
float GetTileWidth() { return mTileWidth; }
float GetTileHeight() { return mTileHeight; }
float GetSizeX() { return mMapSizeX; }
float GetSizeY() { return mMapSizeY; }
std::vector<std::vector<Tile> >& GetTiles() { return mTiles; }
//Setters
void SetPosX(float pPosX) { mPosX = pPosX; }
void SetPosY(float pPosY) { mPosY = pPosY; }
void SetTiles(std::vector<std::vector<Tile> >& pTiles) { mTiles = pTiles; }
//Public member functions
void LoadFromFile(std::string pFileLocation);
void LoadFromParam(float pTileWidth, float pTileHeight, unsigned int pMapSizeX, unsigned int pMapSizeY, sf::Texture pTileSet, std::vector<std::vector<int> >& pTileIDVec, std::vector<std::vector<bool> >& pSolidStateVec, float pPosX = 0, float pPosY = 0);
void LoadFromTiles(float pTileWidth, float pTileHeight, unsigned int pMapSizeX, unsigned int pMapSizeY, sf::Texture pTileSet, std::vector<std::vector<Tile> >& pTiles, float pPosX = 0, float pPosY = 0);
void Render(sf::RenderWindow* pTarget);
bool CheckSolid(float px, float py);
private:
void UpdateTileSpritePos();
//Private member variables
float mPosX, mPosY; //Position x and y
float mTileWidth, mTileHeight; //Tile width and height in pixles
unsigned int mMapSizeX, mMapSizeY; //Map size, in tiles
sf::Texture mTileSet;
std::vector<std::vector<Tile> > mTiles;
};
#endif // TILEENGINE_H_INCLUDED
TileEngine.cpp
:
#include "TileEngine.h"
TileEngine::TileEngine()
{
}
TileEngine::~TileEngine()
{
}
TileEngine::TileEngine(std::string pFileLocation)
{
LoadFromFile(pFileLocation);
}
TileEngine::TileEngine(float pTileWidth, float pTileHeight, unsigned int pMapSizeX, unsigned int pMapSizeY, sf::Texture pTileSet, std::vector<std::vector<int> >& pTileIDVec, std::vector<std::vector<bool> >& pSolidStateVec, float pPosX, float pPosY)
{
LoadFromParam(pTileWidth, pTileHeight, pMapSizeX, pMapSizeY, pTileSet, pTileIDVec, pSolidStateVec, pPosX, pPosY);
}
TileEngine::TileEngine(float pTileWidth, float pTileHeight, unsigned int pMapSizeX, unsigned int pMapSizeY, sf::Texture pTileSet, std::vector<std::vector<Tile> > pTiles, float pPosX, float pPosY)
{
LoadFromTiles(pTileWidth, pTileHeight, pMapSizeX, pMapSizeY, pTileSet, pTiles, pPosX, pPosY);
}
void TileEngine::LoadFromFile(std::string pFileLocation)
{
//Load from File
}
void TileEngine::LoadFromParam(float pTileWidth, float pTileHeight, unsigned int pMapSizeX, unsigned int pMapSizeY, sf::Texture pTileSet, std::vector<std::vector<int> >& pTileIDVec, std::vector<std::vector<bool> >& pSolidStateVec, float pPosX, float pPosY)
{
mPosX = pPosX;
mPosY = pPosY;
mTileWidth = pTileWidth;
mTileHeight = pTileHeight;
mMapSizeX = pMapSizeX;
mMapSizeY = pMapSizeY;
mTileSet = pTileSet;
for (unsigned int i = 0; i < mMapSizeY; i++)
{
std::vector<Tile> Row;
for (unsigned int j = 0; j < mMapSizeX; j++)
{
Tile TempTile;
TempTile.mSolidState = pSolidStateVec[i][j];
TempTile.mTileSprite.setTexture(mTileSet);
TempTile.mTileSprite.setTextureRect(sf::IntRect((pTileIDVec[i][j] % static_cast<int>(mTileSet.getSize().x / mTileWidth)) * mTileWidth, static_cast<int>(pTileIDVec[i][j] / (mTileSet.getSize().x / mTileWidth)) * mTileHeight, mTileWidth, mTileHeight));
Row.push_back(TempTile);
}
mTiles.push_back(Row);
}
UpdateTileSpritePos();
}
void TileEngine::LoadFromTiles(float pTileWidth, float pTileHeight, unsigned int pMapSizeX, unsigned int pMapSizeY, sf::Texture pTileSet, std::vector<std::vector<Tile> >& pTiles, float pPosX, float pPosY)
{
mPosX = pPosX;
mPosY = pPosY;
mTileWidth = pTileWidth;
mTileHeight = pTileHeight;
mMapSizeX = pMapSizeX;
mMapSizeY = pMapSizeY;
mTiles = pTiles;
mTileSet = pTileSet;
}
void TileEngine::Render(sf::RenderWindow* pTarget)
{
for (unsigned int i = 0; i < mMapSizeY; i++)
{
for (unsigned int j = 0; j < mMapSizeX; j++)
{
pTarget->draw(mTiles[i][j].mTileSprite);
}
}
}
void TileEngine::UpdateTileSpritePos()
{
for (unsigned int i = 0; i < mMapSizeY; i++)
{
for (unsigned int j = 0; j < mMapSizeX; j++)
{
mTiles[i][j].mTileSprite.setPosition(mPosX + j * mTileWidth, mPosY + i * mTileHeight);
}
}
}
bool TileEngine::CheckSolid(float px, float py)
{
float RelX = px - mPosX;
float RelY = py - mPosY;
if (RelX < 0 || RelY < 0 || RelX > mMapSizeX * mTileWidth || RelY > mMapSizeY * mTileHeight) //If out of the world, colision = true
return true;
int TilesX = static_cast<int>(RelX / mTileWidth);
int TilesY = static_cast<int>(RelY / mTileHeight);
if (mTiles[TilesY][TilesX].mSolidState) //guarenteed not to throw out of range exeption because of previous if statement exclusing out of bounds values
return true;
return false;
}
LevelEntityManager.h
:
#ifndef LEVELENTITYMANAGER_H
#define LEVELENTITYMANAGER_H
#include "PlayerCharacter.h"
#include "TileEngine.h"
#include <SFML/Graphics.hpp>
class LevelEntityManager
{
public:
LevelEntityManager();
~LevelEntityManager();
sf::RenderWindow* GetTarget() { return mpTarget; }
TileEngine GetTileEngine() { return mTileEngine; }
PlayerCharacter GetPlayer() { return mPlayer; }
void SetTarget(sf::RenderWindow* val) { mpTarget = val; }
void SetTileEngine(TileEngine val) { mTileEngine = val; }
void SetPlayer(PlayerCharacter val) { mPlayer = val; }
void Render();
void Update(KeyState pKeyState);
private:
bool CheckTileSolidColision(std::vector<sf::Vector2f> CornerPoints);
sf::Vector2f GetPlayerNewPosition(PlayerCharacter pPlayer, KeyState pKeyState);
sf::RenderWindow* mpTarget;
TileEngine mTileEngine;
PlayerCharacter mPlayer;
sf::Clock mFrameClock;
float mFrameTime;
};
#endif // LEVELENTITYMANAGER_H
LevelEntityManager.cpp
:
#include "LevelEntityManager.h"
LevelEntityManager::LevelEntityManager()
{
//ctor
}
LevelEntityManager::~LevelEntityManager()
{
//dtor
}
void LevelEntityManager::Update(KeyState pKeyState)
{
mFrameTime = mFrameClock.restart().asSeconds();
sf::Vector2f NewPlayerPos = GetPlayerNewPosition(mPlayer, pKeyState);
sf::Vector2f NewPlayerSpritePos = sf::Vector2f(NewPlayerPos.x - mPlayer.GetSprite().getOrigin().x, NewPlayerPos.y - mPlayer.GetSprite().getOrigin().y);
mPlayer.SetDiretion(ToDegrees(DirectionToPoint(mPlayer.GetPosX(), mPlayer.GetPosY(), sf::Mouse::getPosition(*mpTarget).x, sf::Mouse::getPosition(*mpTarget).y)));
mPlayer.Update(mFrameTime, pKeyState, CheckTileSolidColision(GenerateBoxFromDimentions(NewPlayerSpritePos.x, NewPlayerSpritePos.y, mPlayer.GetTexture().getSize().x, mPlayer.GetTexture().getSize().y)));
}
sf::Vector2f LevelEntityManager::GetPlayerNewPosition(PlayerCharacter pPlayer, KeyState pKeyState)
{
float NewPlayerX = pPlayer.GetPosX();
float NewPlayerY = pPlayer.GetPosY();
if (pKeyState.UpPressed)
NewPlayerY -= pPlayer.GetSpeed() * mFrameTime;
if (pKeyState.DownPressed)
NewPlayerY += pPlayer.GetSpeed() * mFrameTime;
if (pKeyState.LeftPressed)
NewPlayerX -= pPlayer.GetSpeed() * mFrameTime;
if (pKeyState.RightPressed)
NewPlayerX += pPlayer.GetSpeed() * mFrameTime;
sf::Vector2f NewPlayerPos(NewPlayerX, NewPlayerY);
return NewPlayerPos;
}
void LevelEntityManager::Render()
{
mTileEngine.Render(mpTarget); //Order of rendering here is important!
mPlayer.Render(mpTarget);
}
bool LevelEntityManager::CheckTileSolidColision(std::vector<sf::Vector2f> CornerPoints)
{
for (int i = 0; i < CornerPoints.size(); i++)
{
if (mTileEngine.CheckSolid(CornerPoints[i].x, CornerPoints[i].y))
return true;
}
return false;
}
Main.cpp
:
#include "PlayerCharacter.h"
#include "FunctionLib.h"
#include "TileEngine.h"
#include "LevelEntityManager.h"
#include <stdlib.h> /* srand, rand */
#include <time.h> /* time */
void PollEvent();
void Render();
void Update();
void GenerateTestLevel();
unsigned int const MapWidth = 20;
unsigned int const MapHeight = 20;
sf::ContextSettings settings;
sf::RenderWindow window;
LevelEntityManager TestLevel;
KeyState KeysPressed;
int main()
{
srand (time(NULL));
settings.antialiasingLevel = 8;
window.create(sf::VideoMode(sf::VideoMode::getDesktopMode().width, sf::VideoMode::getDesktopMode().height), "Heist", sf::Style::Default, settings);
GenerateTestLevel();
PlayerCharacter MyPlayer(512, 512, 150, 100);
TileEngine MyEngine;
sf::Texture MyTexture;
sf::Texture TileSet;
MyTexture.loadFromFile("PlaceHolderPlayer.png");
TileSet.loadFromFile("TileSet.png");
MyPlayer.SetTexture(MyTexture);
MyPlayer.SetWeapon(GetWeaponStat(SMGWeapon));
std::vector<std::vector<int> > TileIDVec;
std::vector<std::vector<bool> > SolidStateVec;
for (unsigned int i = 0; i < MapHeight; i++)
{
std::vector<bool> boolRow;
std::vector<int> intRow;
for (unsigned int j = 0; j < MapWidth; j++)
{
intRow.push_back(rand() % 10); //random between 0 and 0 (always 0) and then +1 to always be 1
if (intRow[j] > 1)
intRow[j] = 1;
if (intRow[j] == 0)
boolRow.push_back(true); //random between 0 and 0
else
boolRow.push_back(false);
}
SolidStateVec.push_back(boolRow);
TileIDVec.push_back(intRow);
}
MyEngine.LoadFromParam(32, 32, MapWidth, MapHeight, TileSet, TileIDVec, SolidStateVec, 64, 64);
TestLevel.SetPlayer(MyPlayer);
TestLevel.SetTileEngine(MyEngine);
TestLevel.SetTarget(&window);
while (window.isOpen())
{
PollEvent();
Render();
}
}
void GenerateTestLevel()
{
}
void PollEvent()
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
Update();
}
void Update()
{
KeysPressed.LMBPressed = sf::Mouse::isButtonPressed(sf::Mouse::Left);
KeysPressed.LeftPressed = sf::Keyboard::isKeyPressed(sf::Keyboard::A);
KeysPressed.RightPressed = sf::Keyboard::isKeyPressed(sf::Keyboard::D);
KeysPressed.UpPressed = sf::Keyboard::isKeyPressed(sf::Keyboard::W);
KeysPressed.DownPressed = sf::Keyboard::isKeyPressed(sf::Keyboard::S);
TestLevel.Update(KeysPressed);
}
void Render()
{
window.clear();
TestLevel.Render();
window.display();
}
-
\$\begingroup\$ Wow, that's a wall of code to review :o Without reaing the code, it looks clean though. That's already a good point :) \$\endgroup\$Morwenn– Morwenn2015年08月13日 10:26:16 +00:00Commented Aug 13, 2015 at 10:26
1 Answer 1
Remove unused headers
For example, in Entity.h
you include #include <cmath>
but it's not used.
If you would only use it in your cpp, move the include there to save some compile time. If compile time is really important you can even predeclare in your header.
Use initializer lists for all constructors
For example, your zero-argument constructor in the Entity
class (the one defined in the .cpp) doesn't initialize it's arguments, and you might up reading garbage memory.
Mark const methods as such
For example all getters can be marked as const, as well as all other functions that do not logically alter the state of the object.
float GetPosX() const { return mPosX; }
CheckTileSolidColision(const std::vector<sf::Vector2f>& CornerPoints) const;
Use virtual destructor's for classes you've inherited from
virtual ~Entity();
If you don't, the destructor of the subclass may not get executed if you're working on the interface.
Be consistent in naming conventions
It seems that you use the m
prefix for members and p
prefix for parameters. That's probably a good idea but be consistent
Projectile::Projectile(float px, float py, float pDirection, float pVelocity, float pDamage, sf::Texture mSpriteTexture) // Change mSpriteTexture in pSpriteTexture
Another tip is to use lowerCamelCase for variable names.
Try to avoid useless comments
Comments such as //ctor
inside of a constructor don't say much - the syntax of the language and/or name of the function should be good enough to describe simple actions. Comments are very valuable, so feel free them on places where they are useful (in complex methods), but don't add any that add nothing.
Use nullptr over NULL
NULL is defined as a zero, which can also be considered as an integer by the compiler. This can get some weird issues when you have a pointer and integer overloaded function.
Pass arguments by const reference where possible
If an argument has a complicated copy constructor (e.g. if it has to copy memory (such as std::vector
) or has to do atomic operations (such as increasing a refcount))
bool CheckTileSolidColision(std::vector<sf::Vector2f> pCornerPoints); // Change the argument to const std::vector<sf::Vector2f>& pCornerPoints
Work directly on an object instead of a temporary
in LevelEntityManager::GetPlayerNewPosition
you use two temporary variables NewPlayerX
and NewPlayerY
just to copy them over into NewPlayerPos
. You can the following code and work on NewPlayerPos
directly:
sf::Vector2f NewPlayerPos(pPlayerPos);
if (pKeyState.UpPressed)
NewPlayerPos.y -= pPlayer.GetSpeed() * mFrameTime;
...
The same happens in your main.cpp
where you create a temporary vector boolRow
and then push them on SolidStateVec
. Why not add them directly to SolidStateVec
? You know the size beforehand, so you can call resize
on SolidStateVec
and acces the elements using the []
operator. (This way, the vector has to reallocate only once)
Use namespaces
You have some places with generic names, such as ToDegrees()
, which might collide later on when you include some math library. Use namespaces from the beginning to prevent issues later on.
Prefer the compiler over the preprocessor
If you can, prefer a const variable over a preprocessor define.
#define _PI 3.14159265
const float _PI = 3.14159265.f;
The compiler can complain if you double declare, is strongly typed, ...
Cache sizes of containers while iterating
Depending on the container, calling GetSize() and operator[] can be expensive. Cache the size up front or use a range based for.
const size_t cornerPointsSize = pCornerPoints.size();
for (int i = 0; i < cornerPointsSize; ++i)
{
const sf::Vector2f& currentPoint = CornerPoints[i];
if (mTileEngine.CheckSolid(currentPoint.x, currentPoint.y))
return true;
}
for (const sf::Vector2f& currentPoint : cornerPointsSize)
{
if (mTileEngine.CheckSolid(currentPoint.x, currentPoint.y))
return true;
}
Do nullptr checks
In some functions (e.g. Render(sf::RenderWindow* pTarget)
) you pass a pointer while you're never checking if that pointer is actually valid. If the pointer cannot be invalid, pass a reference instead. If the pointer can be invalid, get some logging (use some form of asserts).
Watch out for integer divisions
return degrees * (_PI / 180);
180 is an integer, not a float. So you'll get a whole devision instead of a floating point devision if _PI
is an integer as well. This is not the case, but it is dangerous so I recommend you change it to _PI / 180.0f
Clean up a little
Remove empty functions, don't pollute your main function. So either remove GenerateTestLevel()
or transfer some of the logic that's currently in main
.
Polling versus events
In your Update
function you're polling the state. This means that every single frame, you're requesting state. In this case, it's about a few keys but if you have support for multiple players and controllers, this method is going to get computationally expensive. Responding on events is a little more complex, but worth considering for larger projects. Have a look at events in sfml.
Use parenthesis
They're free, so why not use a few extra. Take a look at this piece of code:
RelY > mMapSizeY * mTileHeight
Operator >
has lower precedence than operator *
, so in this case it is correct, but it may become wrong if you use another operator (such as bitwise operators) and it is confusing to read. So just add them!
RelY > (mMapSizeY * mTileHeight)