Missile Command code review request

Started by
14 comments, last by BHXSpecter 9 years, 7 months ago

I recently finished programming a clone to the popular game Missile Command as part of the "getting started" article on gamedev.net. This project took me about two weeks to complete whilst working on it every now and then. The code contains over 1500 lines of C++ and SFML. This time I took a different design approach to my previous projects in order to simplify the code and make it more readable... hopefully.

Links to my other projects in this series:

Breakout

Snake

Pong

Video of my Missile Command clone:

Download:

https://www.dropbox.com/s/07lq4wc5fsp1djb/Missile%20Command.zip

(Please tell me if the game doesn't work)

Features:

Crappy UI

Highscores

Explosions

Screenshots:

j1hCiNy.png

8QaWa3D.png

sA2uS5R.png

7IJDoBn.png

Files:

U3GL4T7.png

Last but not least: A huge chunk of code.

main.cpp


#include "init.h"

int main(){
	Missile_Command game;

	game.run();

	return 0;
}

init.h


#pragma once

// C++
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <sstream>

// SFML
#include <SFML/Graphics.hpp>
#include <SFML/Audio.hpp>
#include <SFML/Network.hpp>

// Framework
#include "SoundEffect.h"
#include "Entity.h"
#include "Button.h"
#include "Math.h"
#include "Collision_Detection.h"
#include "Windows_Stuff.h"

// Game
#include "Missiles.h"
#include "Missile_Command.h"

// Globals
const float SECOND = 1000;
const float BLOCKSIZE = 20;
const int GRIDSIZE_X = 50;
const int GRIDSIZE_Y = 30;
const int SCRW = (int)BLOCKSIZE * GRIDSIZE_X;
const int SCRH = (int)BLOCKSIZE * GRIDSIZE_Y;
const double INTERVAL = 16.6; // 1000 / INTERVAL = FPS -> ~60
const sf::Color CLEARCOLOR(sf::Color(0, 0, 0));

Missile_Command.h


#pragma once
struct Particle;

enum class GameState{
	Home,
	Settings,
	About,
	Highscores,
	Playing,
	Paused,
	GameOver,
	Win
};

class Missile_Command{
	sf::RenderWindow _window;
	sf::Event _event;
	GameState _gameState;
	sf::Clock _clock;

	sf::Font _font;
	sf::Text _aboutText;
	sf::Text _volumeText;
	sf::Text _volumeInfoText;
	sf::Text _scoreText;
	sf::Text _inGameInfoText;
	sf::Text _gameOverText;
	sf::Text _winText;
	sf::Text _highscoreNamesText;
	sf::Text _highscoreScoresText;
	sf::Text _ammunitionText;

	sf::Texture _bgTex;
	sf::Sprite _bgSprite1;
	sf::Sprite _bgSprite2;

	sf::Vector2f _wallSize;
	sf::Vector2f _windowCenter;
	sf::Vector2f _playerCenterLoc;
	sf::Vector2f _missileSize;
	std::vector<sf::Vector2f> _enemyVel;

	int _score;
	int _wave;
	int _ammunition;
	int _enemyCount; // wave limit
	int _enemiesSpawned; // amount of enemies spawned in this wave
	float _explosionExpandVel;
	float _explosionFadeVel;
	size_t _updates;
	double _skippedTime;
	double _secondSkipped;
	float _canonAngle;
	float _missileSpeed;
	float _enemySpeed;
	float _bgMoveVel;
	float _bgOriginMoveVel;
	float _floorHeight;
	float _playerSize;
	float _volume;
	std::string _playerName;
	std::vector<std::string> _highscores;

	sf::Color _playerColor;
	sf::Color _baseColor;
	sf::Color _buttonColor;
	sf::Color _buttonHoverColor;
	sf::Color _missileColor;
	sf::Color _baseExplodeColor;
	sf::Color _baseFadeColor;
	sf::Color _enemyExplodeColor;
	sf::Color _enemyFadeColor;

	sf::RectangleShape colBounds;
	Missiles _missiles;
	EntityR _canon;
	EntityR _bunkerBase;
	EntityC _bunkerTop;
	EntityC _playerFloor;
	std::vector<EntityR> _floor;
	std::vector<EntityC> _bases;
	std::vector<EntityC> _enemies;
	Particles _menuEffect;
	Particles _trails; // comet trails

	SoundEffect _shootSound;
	SoundEffect _explodeSound;
	SoundEffect _loseSound;
	SoundEffect _waveWinSound;
	std::vector<SoundEffect> _hoverSounds;

	ui::Button _quit;
	ui::Button _start;
	ui::Button _settings;
	ui::Button _about;
	ui::Button _highscoresBtn;
	ui::Button _back;
	ui::Button _continue;
	ui::Button _pauseQuit;
	ui::Button _home;
	ui::Button _volumeDown;
	ui::Button _volumeUp;
	ui::Button _lastHovered; // used to detect the button that was last hovered over

	void init();
	void initFloor();
	void initPlayer();
	void initEnemy();
	void initButtons();
	void initText();
	void initSounds();

	void doEvents();
	void reset();
	// Load menus
	void loadHome();
	void loadSettings();
	void loadAbout();
	void loadHighscores();
	void loadPlaying();
	void loadPaused();
	void loadGameOver();
	void loadWin();

	void update();
	void updateBG();
	void updateMenuEffect();
	void updateEnemies();

	void render();
	void renderHome();
	void renderSettings();
	void renderAbout();
	void renderHighScores();
	void renderPlaying();
	void renderPause();
	void renderGameOver();
	void renderWin();
	void renderMenuEffect();
	void renderBG();

	void doHover(ui::Button & button);
	void setVolume();
	void spawnEnemy();
	void removeEnemyAt(size_t index);
	void doCollisions();
	void removeBaseAt(size_t index);
	void nextWave();
	std::vector<std::string> split(std::string string, char splitParam);
	void getHighscores();
	bool addHighscore();
	void writeHighscores();
	void sortHighscores();
public:
	Missile_Command();
	~Missile_Command();

	void run();
};

Missile_Command.cpp (~800 Lines)


#include "init.h"


Missile_Command::Missile_Command(){
	srand((size_t)time(0));
	init();
}


Missile_Command::~Missile_Command(){
	// Destroy
}

void Missile_Command::init(){
	_window.create(sf::VideoMode(SCRW, SCRH), "Missile Command");

	_missileSize = sf::Vector2f(4, 4);
	_explosionExpandVel = 0.8f;
	_explosionFadeVel = 2.2f;
	_missileSpeed = 12.0f;
	_skippedTime = 0;
	_updates = 0;
	_secondSkipped = 0;
	_bgOriginMoveVel = 1.0f;
	_bgMoveVel = _bgOriginMoveVel;
	_enemySpeed = 1.0f;
	_volume = 30;
	_baseExplodeColor = sf::Color(0, 100, 255);
	_baseFadeColor = sf::Color(200, 50, 10);
	_enemyExplodeColor = sf::Color::Yellow;
	_enemyFadeColor = sf::Color::Magenta;
	_playerColor = sf::Color::White;
	_baseColor = sf::Color(56, 78, 90, 200);
	_buttonColor = sf::Color(255, 255, 255, 128);
	_buttonHoverColor = sf::Color((sf::Uint8)(_buttonColor.r / 1.5), (sf::Uint8)(_buttonColor.g / 1.5), (sf::Uint8)(_buttonColor.b / 1.5, 128));
	_missileColor = sf::Color(255, 165, 0);
	_floorHeight = 50;
	_wallSize = sf::Vector2f(_floorHeight, _floorHeight * 2.0f);
	_playerSize = _floorHeight;
	_windowCenter = (sf::Vector2f)_window.getSize() / 2.0f;
	if (!_bgTex.loadFromFile("sprites/bg.png"))
		std::exit(-4);
	_bgSprite1.setTexture(_bgTex);
	_bgSprite2.setTexture(_bgTex);
	_bgSprite1.setPosition(0, 0);
	_bgSprite2.setPosition(_bgSprite1.getPosition().x - _bgTex.getSize().x, 0);

	initFloor();
	initButtons();
	initText();
	initSounds();
	initPlayer();

	_playerCenterLoc = _bunkerTop.getPosition() + sf::Vector2f(_bunkerTop.getRadius(), _bunkerTop.getRadius());
	reset();
	_gameState = GameState::Home; // Temporary -> Home
}

void Missile_Command::initFloor(){
	_floor.resize(3);
	_floor[0] = EntityR(sf::Vector2f((float)_window.getSize().x, _floorHeight), sf::Vector2f(0.0f, (float)_window.getSize().y - _floorHeight), _playerColor); // Main floor
	_floor[1] = EntityR(_wallSize, sf::Vector2f(0, _window.getSize().y - (_floorHeight + _wallSize.y)), _playerColor); // Left wall
	_floor[2] = EntityR(_wallSize, (sf::Vector2f)_window.getSize() - sf::Vector2f(_wallSize.x, _floorHeight + _wallSize.y), _playerColor); // Right wall
	_playerFloor = EntityC(_playerSize * 2.0f, 9, sf::Vector2f(_windowCenter.x - _playerSize * 2.0f, _window.getSize().y - _floorHeight * 2.0f), _playerColor);
}

void Missile_Command::initPlayer(){
	_bunkerBase = EntityR(sf::Vector2f(_playerSize, _playerSize / 2.1f), sf::Vector2f(_windowCenter.x - _playerSize / 2.0f, _playerFloor.getPosition().y - _playerSize / 4.2f), _playerColor);
	_bunkerTop = EntityC(_playerSize / 2.0f, 40, sf::Vector2f(_windowCenter.x - _playerSize / 2.0f, _playerFloor.getPosition().y - _playerSize / 1.5f), _playerColor);
	_canon = EntityR(sf::Vector2f(_playerSize / 5.0f, _playerSize / 1.2f), sf::Vector2f(_windowCenter.x, _bunkerTop.getPosition().y + _bunkerTop.getRadius()), _playerColor);
	_canon.setOrigin(sf::Vector2f(_canon.getSize().x / 2.0f, _canon.getSize().y));

	// Init bases
	_bases.resize(6);
	_bases[0] = EntityC(50, 10, sf::Vector2f(100, 520), _baseColor);
	_bases[1] = EntityC(50, 10, sf::Vector2f(200, 520), _baseColor);
	_bases[2] = EntityC(50, 10, sf::Vector2f(300, 520), _baseColor);
	_bases[3] = EntityC(50, 10, sf::Vector2f(600, 520), _baseColor);
	_bases[4] = EntityC(50, 10, sf::Vector2f(700, 520), _baseColor);
	_bases[5] = EntityC(50, 10, sf::Vector2f(800, 520), _baseColor);
}

void Missile_Command::initEnemy(){
	_enemies.clear();
	_enemyVel.clear();
}

void Missile_Command::initButtons(){
	_start = ui::Button("Start", 50, sf::Vector2f(15, 0), sf::Color::Black, _buttonColor, "fonts/segoeuil.ttf");
	_settings = ui::Button("Settings", 50, sf::Vector2f(15, 80), sf::Color::Black, _buttonColor, "fonts/segoeuil.ttf");
	_about = ui::Button("About", 50, sf::Vector2f(15, 160), sf::Color::Black, _buttonColor, "fonts/segoeuil.ttf");
	_highscoresBtn = ui::Button("Highscores", 50, sf::Vector2f(15, 240), sf::Color::Black, _buttonColor, "fonts/segoeuil.ttf");
	_quit = ui::Button("Quit", 50, sf::Vector2f(15, 320), sf::Color::Black, _buttonColor, "fonts/segoeuil.ttf");
	_back = ui::Button("Back", 50, sf::Vector2f(15.0f, SCRH - 90.0f), sf::Color::Black, _buttonColor, "fonts/segoeuil.ttf");
	_continue = ui::Button("Continue", 50, sf::Vector2f(400, 260), sf::Color::Black, _buttonColor, "fonts/segoeuil.ttf");
	_pauseQuit = ui::Button("Home", 50, sf::Vector2f(250, 260), sf::Color::Black, _buttonColor, "fonts/segoeuil.ttf");
	_volumeDown = ui::Button("<", 50, sf::Vector2f(15, 40), sf::Color::Black, _buttonColor, "fonts/segoeuil.ttf");
	_volumeUp = ui::Button(">", 50, sf::Vector2f(130, 40), sf::Color::Black, _buttonColor, "fonts/segoeuil.ttf");
}

void Missile_Command::initText(){
	_font.loadFromFile("fonts/segoeuil.ttf");

	_aboutText.setFont(_font);
	_volumeText.setFont(_font);
	_volumeInfoText.setFont(_font);
	_scoreText.setFont(_font);
	_inGameInfoText.setFont(_font);
	_gameOverText.setFont(_font);
	_winText.setFont(_font);
	_highscoreNamesText.setFont(_font);
	_highscoreScoresText.setFont(_font);
	_ammunitionText.setFont(_font);

	_aboutText.setString("Game is controlled solely through mouse input.\n\n	Programming: Gaius Baltar\n	Sound effects: Gaius Baltar");
	_volumeText.setString(std::to_string(_volume));
	_volumeText.setCharacterSize(50);
	_volumeText.setPosition(sf::Vector2f(65, 50));
	_volumeInfoText.setString("Volume:");
	_volumeInfoText.setCharacterSize(50);
	_volumeInfoText.setPosition(sf::Vector2f(15, 0));
	_scoreText.setCharacterSize(50);
	_scoreText.setPosition(sf::Vector2f(250, 180));
	_inGameInfoText.setCharacterSize(20);
	_gameOverText.setCharacterSize(50);
	_gameOverText.setPosition(sf::Vector2f(250, 100));
	_gameOverText.setString("You lose...");
	_winText.setCharacterSize(50);
	_winText.setPosition(sf::Vector2f(250, 100));
	_winText.setString("You Win!");
	_highscoreNamesText.setCharacterSize(30);
	_highscoreNamesText.setPosition(sf::Vector2f(50, 50));
	_highscoreScoresText.setCharacterSize(30);
	_highscoreScoresText.setPosition(sf::Vector2f(500, 50));
	_ammunitionText.setCharacterSize(50);
	_ammunitionText.setPosition(sf::Vector2f(475, 510));
	_ammunitionText.setColor(sf::Color::Black);
}

void Missile_Command::initSounds(){
	_shootSound = SoundEffect("sounds/shoot.wav", _volume);
	_explodeSound = SoundEffect("sounds/explode.wav", _volume);
	_loseSound = SoundEffect("sounds/lose.wav", _volume);
	_waveWinSound = SoundEffect("sounds/win.wav", _volume);
	_hoverSounds.resize(5);
	for (size_t i = 0; i < _hoverSounds.size(); i++){
		_hoverSounds[i] = SoundEffect("sounds/hover" + std::to_string(i + 1) + ".wav", _volume);
	}
}

void Missile_Command::doEvents(){
	while (_window.pollEvent(_event)){
		if (_event.type == sf::Event::Closed){
			_window.close();
		}
		switch (_gameState){
		case GameState::Home:
			if (_event.type == sf::Event::KeyPressed && _event.key.code == sf::Keyboard::Escape || _quit.isPressed(_event)){
				_window.close();
			}

			if (_start.isPressed(_event))
				loadPlaying();
			if (_settings.isPressed(_event))
				loadSettings();
			if (_about.isPressed(_event))
				loadAbout();
			if (_highscoresBtn.isPressed(_event))
				loadHighscores();
			if (_event.type == sf::Event::MouseMoved){
				doHover(_quit);
				doHover(_start);
				doHover(_settings);
				doHover(_about);
				doHover(_highscoresBtn);
			}
			break;
		case GameState::Settings:
			if (_event.type == sf::Event::KeyPressed && _event.key.code == sf::Keyboard::Escape){
				loadHome();
			}
			if (_volumeDown.isPressed(_event) && _volume > 0){
				_volume -= 1;
				_volumeText.setString(std::to_string((int)_volume));
				setVolume();
			}
			if (_volumeUp.isPressed(_event) && _volume < 100){
				_volume += 1;
				_volumeText.setString(std::to_string((int)_volume));
				setVolume();
			}
			if (_back.isPressed(_event))
				loadHome();
			if (_event.type == sf::Event::MouseMoved){
				doHover(_back);
				doHover(_volumeDown);
				doHover(_volumeUp);
			}
			break;
		case GameState::About:
			if (_event.type == sf::Event::KeyPressed && _event.key.code == sf::Keyboard::Escape){
				loadHome();
			}
			if (_back.isPressed(_event))
				loadHome();
			if (_event.type == sf::Event::MouseMoved){
				doHover(_back);
			}
			break;
		case GameState::Highscores:
			if (_event.type == sf::Event::KeyPressed && _event.key.code == sf::Keyboard::Escape){
				loadHome();
			}
			if (_back.isPressed(_event))
				loadHome();
			if (_event.type == sf::Event::MouseMoved){
				doHover(_back);
			}
			break;
		case GameState::Playing:
			if (_event.type == sf::Event::KeyPressed){
				if (_event.key.code == sf::Keyboard::Space){
					_missiles.explodeAtPosition(_playerFloor.getPosition() + sf::Vector2f(_playerFloor.getRadius(), _playerFloor.getRadius()), 0.5f, 7.0f, sf::Color(155, 115, 0), sf::Color(100, 0, 0));
				}
				if (_event.key.code == sf::Keyboard::Escape)
					loadPaused();
			}
			if (_event.type == sf::Event::MouseButtonPressed){
				if (_event.mouseButton.button == sf::Mouse::Left && _ammunition > 0){
					_shootSound.play();
					_canonAngle = (float)mth::getAngleInRadians(_playerCenterLoc, (sf::Vector2f)sf::Mouse::getPosition(_window));
					_missiles.fireMissile(mth::getVelocity(_canonAngle, _missileSpeed), (sf::Vector2f)sf::Mouse::getPosition(_window));
					_ammunition--;
				}
			}
			break;
		case GameState::Paused:
			if (_event.type == sf::Event::KeyPressed && _event.key.code == sf::Keyboard::Escape){
				loadPlaying();
			}
			if (_continue.isPressed(_event))
				loadPlaying();
			if (_pauseQuit.isPressed(_event))
				loadHome();
			if (_event.type == sf::Event::MouseMoved){
				doHover(_continue);
				doHover(_pauseQuit);
			}
			break;
		case GameState::GameOver:
			if (_event.type == sf::Event::KeyPressed && _event.key.code == sf::Keyboard::Escape){
				loadHome();
			}
			if (_pauseQuit.isPressed(_event))
				loadHome();
			if (_continue.isPressed(_event)){
				reset();
				loadPlaying();
			}
			if (_event.type == sf::Event::MouseMoved){
				doHover(_pauseQuit);
				doHover(_continue);
			}
			break;
		case GameState::Win:
			if (_event.type == sf::Event::KeyPressed && _event.key.code == sf::Keyboard::Escape){
				loadPlaying();
			}
			if (_pauseQuit.isPressed(_event))
				loadHome();
			if (_continue.isPressed(_event)){
				loadPlaying();
			}
			if (_event.type == sf::Event::MouseMoved){
				doHover(_pauseQuit);
				doHover(_continue);
			}
			break;
		default:
			break;
		}
	}
}

void Missile_Command::reset(){
	_wave = 0;
	nextWave();
	_ammunition = 40;
	_score = 0;
	_missiles = Missiles(_missileSize, _playerCenterLoc, _missileColor, sf::Color::Red, _explosionExpandVel, _explosionFadeVel);
	initPlayer();
	initEnemy();
	_menuEffect.clear();
	_trails.clear();

}

void Missile_Command::loadHome(){
	_gameState = GameState::Home;
	_bgMoveVel = _bgOriginMoveVel;
	reset();
}

void Missile_Command::loadSettings(){
	_gameState = GameState::Settings;
	_volumeText.setString(std::to_string((int)_volume));
	_bgMoveVel = _bgOriginMoveVel;
}

void Missile_Command::loadAbout(){
	_gameState = GameState::About;
	_bgMoveVel = _bgOriginMoveVel;
}

void Missile_Command::loadHighscores(){
	_gameState = GameState::Highscores;
	_bgMoveVel = _bgOriginMoveVel;
	getHighscores();
	sortHighscores();
	std::string namesList;
	std::string scoresList;
	for (size_t i = 0; i < _highscores.size() - 1; i += 2){
		namesList += std::to_string((i+2)/2) + ". " + _highscores[i] + "\n";
		scoresList += _highscores[i + 1] + "\n";
	}
	_highscoreNamesText.setString(namesList);
	_highscoreScoresText.setString(scoresList);
}

void Missile_Command::loadPlaying(){
	_gameState = GameState::Playing;
	_menuEffect.clear();
	_bgMoveVel = _bgOriginMoveVel / 3.0f;
}

void Missile_Command::loadPaused(){
	_continue.setString("Continue");
	_gameState = GameState::Paused;
	_bgMoveVel = _bgOriginMoveVel * 2.0f;
}

void Missile_Command::loadGameOver(){
	_gameState = GameState::GameOver;
	_continue.setString("Restart");
	_bgMoveVel = _bgOriginMoveVel * 2.0f;
	_enemies.clear();
	_enemyVel.clear();
	_missiles.clear();
	_score += _ammunition * 10;
	_score += _bases.size() * 100;
	_scoreText.setString("Score: " + std::to_string(_score));
	if (_playerName.size() <= 0){
		std::cout << "\n\n\nPlease enter your name for the highscores list:\n\n\n";
		std::getline(std::cin, _playerName);
	}
	addHighscore();
}

void Missile_Command::loadWin(){
	_gameState = GameState::Win;
	_continue.setString("Continue");
	_bgMoveVel = _bgOriginMoveVel * 2.0f;
	_enemies.clear();
	_enemyVel.clear();
	_missiles.clear();
	_score += _ammunition * 10;
	_score += _bases.size() * 100;
	_ammunition = 40;
	_scoreText.setString("Score: " + std::to_string(_score));
	nextWave();
}

void Missile_Command::update(){
	switch (_gameState){
	case GameState::Home:
		updateBG();
		updateMenuEffect();
		break;
	case GameState::Settings:
		updateBG();
		updateMenuEffect();
		break;
	case GameState::About:
		updateBG();
		updateMenuEffect();
		break;
	case GameState::Highscores:
		updateBG();
		updateMenuEffect();
		break;
	case GameState::Playing:
		updateBG();
		_canon.setRotation(90 + (float)mth::getAngleInDegrees(_playerCenterLoc, (sf::Vector2f)sf::Mouse::getPosition(_window)));
		_inGameInfoText.setString("Score: " + std::to_string(_score) + "	Wave: " + std::to_string(_wave) + "	Enemies remaining: " + std::to_string(_enemyCount - _enemiesSpawned));
		_ammunitionText.setString(std::to_string(_ammunition));
		_missiles.update(_explodeSound);
		updateEnemies();
		doCollisions();
		if (rand() % 1000 <= _wave * 10 && _enemiesSpawned < _enemyCount){
			spawnEnemy();
			_enemiesSpawned++;
		}
		if (_bases.size() <= 0){
			_loseSound.play();
			loadGameOver();
		}
		if (_enemies.size() <= 0 && _enemiesSpawned >= _enemyCount){
			_waveWinSound.play();
			loadWin();
		}
		if (_wave > 10){
			_waveWinSound.play();
			loadGameOver();
			loadHighscores();
		}
		break;
	case GameState::Paused:
		_bgMoveVel = _bgOriginMoveVel * 2.0f;
		updateBG();
		updateMenuEffect();
		break;
	case GameState::GameOver:
		_bgMoveVel = _bgOriginMoveVel * 2.0f;
		updateBG();
		updateMenuEffect();
		break;
	case GameState::Win:
		_bgMoveVel = _bgOriginMoveVel * 2.0f;
		updateBG();
		updateMenuEffect();
		break;
	}
}

void Missile_Command::updateBG(){
	_bgSprite1.setPosition(_bgSprite1.getPosition() + sf::Vector2f(_bgMoveVel, 0));
	_bgSprite2.setPosition(_bgSprite2.getPosition() + sf::Vector2f(_bgMoveVel, 0));
	if (_bgSprite1.getPosition().x > _window.getSize().x)
		_bgSprite1.setPosition(_bgSprite2.getPosition().x - _bgTex.getSize().x, 0);
	if (_bgSprite2.getPosition().x > _window.getSize().x)
		_bgSprite2.setPosition(_bgSprite1.getPosition().x - _bgTex.getSize().x, 0);
}

void Missile_Command::updateMenuEffect(){
	if (rand() % 1000 <= 100){
		_menuEffect.append(sf::Vector2f((float)(rand() % SCRW), (float)(rand() % SCRH)), 500, sf::Color(rand() % 256, rand() % 256, rand() % 256, 20));
	}
	if (_menuEffect.particles.size() > 1000)
		_menuEffect.clear();
}

void Missile_Command::updateEnemies(){
	for (size_t i = 0; i < _enemies.size(); i++){
		_enemies[i].setPosition(_enemies[i].getPosition() + _enemyVel[i]);
		if (rand() % 1000 < 200){
			_trails.append(_enemies[i].getPosition() + sf::Vector2f((float)(rand() % 10), 0.0f), 500, sf::Color(200, rand() % 100 + 150, rand() % 100 + 150));
		}
	}

	for (size_t i = 0; i < _trails.life.size(); i++){
		_trails.life[i]--;
		if (_trails.life[i] <= 0){
			_trails.removeAt(i);
		}
	}
}

void Missile_Command::render(){
	_window.clear(CLEARCOLOR);
	switch (_gameState){
	case GameState::Home:
		renderBG();
		renderMenuEffect();
		renderHome();
		break;
	case GameState::Settings:
		renderBG();
		renderMenuEffect();
		renderSettings();
		break;
	case GameState::About:
		renderBG();
		renderMenuEffect();
		renderAbout();
		break;
	case GameState::Highscores:
		renderBG();
		renderMenuEffect();
		renderHighScores();
		break;
	case GameState::Playing:
		renderBG();
		_window.draw(colBounds);
		renderPlaying();
		break;
	case GameState::Paused:
		renderBG();
		renderMenuEffect();
		renderPlaying();
		renderPause();
		break;
	case GameState::GameOver:
		renderBG();
		renderMenuEffect();
		renderPlaying();
		renderGameOver();
		break;
	case GameState::Win:
		renderBG();
		renderMenuEffect();
		renderPlaying();
		renderWin();
		break;
	}
	_window.display();
}

void Missile_Command::renderHome(){
	_window.draw(_start);
	_window.draw(_settings);
	_window.draw(_about);
	_window.draw(_highscoresBtn);
	_window.draw(_quit);
}

void Missile_Command::renderSettings(){
	_window.draw(_back);
	_window.draw(_volumeDown);
	_window.draw(_volumeUp);
	_window.draw(_volumeText);
	_window.draw(_volumeInfoText);
}

void Missile_Command::renderAbout(){
	_window.draw(_aboutText);
	_window.draw(_back);
}

void Missile_Command::renderHighScores(){
	_window.draw(_back);
	_window.draw(_highscoreNamesText);
	_window.draw(_highscoreScoresText);
}

void Missile_Command::renderPlaying(){
	for (auto item : _enemies)
		_window.draw(item);
	if (_trails.particles.size() > 0)
		_window.draw(&_trails.particles[0], _trails.particles.size(), sf::PrimitiveType::Points);
	_window.draw(_missiles);
	for (auto item : _bases)
		_window.draw(item);
	for (auto item : _floor)
		_window.draw(item);
	_window.draw(_bunkerBase);
	_window.draw(_bunkerTop);
	_window.draw(_canon);
	_window.draw(_playerFloor);
	_window.draw(_ammunitionText);
	_window.draw(_inGameInfoText);
}

void Missile_Command::renderPause(){
	_window.draw(_continue);
	_window.draw(_pauseQuit);
}

void Missile_Command::renderGameOver(){
	_window.draw(_pauseQuit);
	_window.draw(_gameOverText);
	_window.draw(_scoreText);
}

void Missile_Command::renderWin(){
	_window.draw(_pauseQuit);
	_window.draw(_continue);
	_window.draw(_winText);
	_window.draw(_scoreText);
}

void Missile_Command::renderMenuEffect(){
	if (_menuEffect.particles.size() > 0)
		_window.draw(&_menuEffect.particles[0], _menuEffect.particles.size(), sf::PrimitiveType::Triangles);
}

void Missile_Command::renderBG(){
	_window.draw(_bgSprite1);
	_window.draw(_bgSprite2);
}

void Missile_Command::doHover(ui::Button & button){
	if (button.isHovering(_event)){
		if (_lastHovered.getString() != button.getString()){
			button.setButtonFaceColor(_buttonHoverColor);
			_hoverSounds[rand() % _hoverSounds.size()].play();
			_lastHovered.setString(button.getString());
		}
	}
	else{
		if (_lastHovered.getString() == button.getString())
			_lastHovered.setString(" ");
		button.setButtonFaceColor(_buttonColor);
	}
}

void Missile_Command::setVolume(){
	_shootSound.setVolume(_volume);
	_explodeSound.setVolume(_volume);
	_loseSound.setVolume(_volume);
	_waveWinSound.setVolume(_volume);
	for (int i = 0; i < _hoverSounds.size();i++)
		_hoverSounds[i].setVolume(_volume);
}

void Missile_Command::spawnEnemy(){
	if (rand() % 2 == 0){
		_enemies.push_back(EntityC(5, 5, sf::Vector2f((float)(rand() % 490), -10.0f), sf::Color::Red));
		_enemyVel.push_back(sf::Vector2f(_enemySpeed / (rand() % 5 + 1), _enemySpeed));
	}
	else{
		_enemies.push_back(EntityC(5, 5, sf::Vector2f((float)(rand() % 490 + 500), -10.0f), sf::Color::Red));
		_enemyVel.push_back(sf::Vector2f((_enemySpeed / (rand() % 5 + 1)) * -1, _enemySpeed));
	}
}

void Missile_Command::removeEnemyAt(size_t index){
	_enemies.erase(_enemies.begin() + index);
	_enemyVel.erase(_enemyVel.begin() + index);
}

void Missile_Command::doCollisions(){
	// Missiles -> Enemies
	for (auto missile : _missiles.getExplosions()){
		for (size_t i = 0; i < _enemies.size(); i++){
			if (cd::doesIntersect(missile, _enemies[i])){
				_explodeSound.play();
				_missiles.explodeAtPosition(_enemies[i].getPosition() + sf::Vector2f(_enemies[i].getRadius(), _enemies[i].getRadius()), 1.05f, 0.3f, _enemyExplodeColor, _enemyFadeColor);
				_score += 10;
				removeEnemyAt(i);
				i--;
			}
		}
	}

	// Enemies -> Bases
	for (size_t i = 0; i < _enemies.size(); i++){
		if (_enemies[i].getPosition().y > _window.getSize().y - 200){
			for (size_t j = 0; j < _bases.size(); j++){
				if (_enemies.size() > 0 && cd::doesIntersect(_enemies[i], _bases[j])){
					_explodeSound.play();
					_missiles.explodeAtPosition(_bases[j].getPosition() + sf::Vector2f(_bases[j].getRadius(), _bases[j].getRadius()), 1.5f, 3.0f, _baseExplodeColor, _baseFadeColor);
					removeBaseAt(j);
					removeEnemyAt(i);
					_score += 100 / (_bases.size() + 1);
					std::cout << "sup\n";
					i = i - 1 > 0 ? i - 1 : i;
					break;
				}
			}
		}
	}

	// Enemies -> Floor
	for (size_t i = 0; i < _enemies.size(); i++){
		if (_enemies[i].getPosition().y > _window.getSize().y - 200){
			if (_enemies.size() > 0 && _enemies[i].getPosition().y > _window.getSize().y - 60){
				_explodeSound.play();
				_missiles.explodeAtPosition(_enemies[i].getPosition() + sf::Vector2f(_enemies[i].getRadius(), _enemies[i].getRadius()), 1.05f, 0.3f, _enemyExplodeColor, _enemyFadeColor);
				removeEnemyAt(i);
				_score -= 10;
				i = i - 1 > 0 ? i - 1 : i;
				continue;
			}
			else if (_enemies.size() > 0 && cd::doesIntersect(_enemies[i], _playerFloor)){
				_explodeSound.play();
				_missiles.explodeAtPosition(_enemies[i].getPosition() + sf::Vector2f(_enemies[i].getRadius(), _enemies[i].getRadius()), 1.05f, 0.3f, _enemyExplodeColor, _enemyFadeColor);
				removeEnemyAt(i);
				if (_ammunition >= 5)
					_ammunition -= 5;
				else
					_ammunition = 0;
				i = i - 1 > 0 ? i - 1 : i;
				continue;
			}
		}
	}
}

void Missile_Command::removeBaseAt(size_t index){
	_bases.erase(_bases.begin() + index);
}

void Missile_Command::nextWave(){
	_wave++;
	_enemyCount = (_wave * _wave) + (_wave * _wave) + 10;
	_enemySpeed = _wave / 10.0f + 0.5f;
	_enemiesSpawned = 0;
}

std::vector<std::string> Missile_Command::split(std::string string, char splitParam){
	std::vector<std::string> splitString;
	int strBegin = 0;
	for (size_t i = 0; i < string.size(); i++){
		if (string[i] == splitParam && i != 0){
			splitString.push_back(string.substr(strBegin, i));
			if (string.size() > i + 1)
				strBegin = i + 1;
		}
		if (string.size() <= i + 1){
			splitString.push_back(string.substr(strBegin, i));
		}
	}
	return splitString;
}

void Missile_Command::getHighscores(){
	// read file into program
	_highscores.clear();
	std::string line;
	std::ifstream inputStream("highscores.txt");
	if (inputStream.is_open()){
		while (std::getline(inputStream, line)){
			std::vector<std::string> toIns{ split(line, '|') };
			_highscores.insert(_highscores.end(), toIns.begin(), toIns.end());
		}
		inputStream.close();
	}
	for (size_t i = 0; i < _highscores.size() - 1; i+=2){
		std::cout << _highscores[i] << " " << _highscores[i + 1] << "\n";
	}
}

bool Missile_Command::addHighscore(){
	bool isAdded;
	getHighscores();
	// Add score
	_highscores.push_back(_playerName);
	_highscores.push_back(std::to_string(_score));
	// Sort all scores
	sortHighscores();
	if (atoi(_highscores[_highscores.size() - 1].c_str()) == _score)
		isAdded = false;
	else
		isAdded = true;
	// Remove lowest score
	if (_highscores.size() > 20){
		_highscores.resize(_highscores.size() - 2);
	}
	// Write highscores to file
	writeHighscores();
	return isAdded;
}

// write highscores into file
void Missile_Command::writeHighscores(){
	std::ofstream outputStream("highscores.txt");
	if (outputStream.is_open())
	{
		for (size_t i = 0; i < _highscores.size(); i += 2){
			outputStream << _highscores[i] << '|' << _highscores[i + 1] << '\n';
		}
		outputStream.close();
	}
}

void Missile_Command::sortHighscores(){
	// sort highscores
	int j;
	std::string tmp1;
	std::string tmp2;
	for (size_t i = 3; i < _highscores.size(); i += 2){
		j = i;
		while (j > 1 && std::atoi(_highscores[j - 2].c_str()) < std::atoi(_highscores[j].c_str())) {
			tmp1 = _highscores[j];
			tmp2 = _highscores[j - 1];
			_highscores[j] = _highscores[j - 2];
			_highscores[j - 1] = _highscores[j - 3];
			_highscores[j - 3] = tmp2;
			_highscores[j - 2] = tmp1;
			j -= 2;
		}
	}
}

void Missile_Command::run(){
	while (_window.isOpen()){
		const double elapsedTime = _clock.getElapsedTime().asMicroseconds() / 1000.0;
		_skippedTime += elapsedTime;
		_secondSkipped += elapsedTime;
		_clock.restart();

		while (_secondSkipped >= SECOND){
			std::cout << _updates << " Updates at an average interval of " << _secondSkipped / _updates << "ms.\n";
			_updates = 0;
			_secondSkipped -= SECOND;
		}
		while (_skippedTime >= INTERVAL){
			_updates++;
			_skippedTime -= INTERVAL;

			doEvents();
			update();
		}
		render();
	}
}

Entity.h


#pragma once

template<class T>
class Entity : public T
{
public:
	//Constructors
	Entity(sf::Vector2f size, sf::Vector2f pos, sf::Color color) : T(size){
		setPosition(pos);
		setFillColor(color);
	}
	Entity(float radius, size_t pointCount, sf::Vector2f pos, sf::Color color) : T(radius){
		setPointCount(pointCount);
		setPosition(pos);
		setFillColor(color);
	}
	Entity(float width, float height, float x, float y) : T(sf::Vector2f(width, height)){
		setPosition(x, y);
	}
	Entity() : T(){};

	sf::Vector2i getGridPosition() const;
	void setGridPosition(size_t x, size_t y);
	void setOutline(float thickness, const sf::Color & color);
};

// Definitions ---------------------------------------------------------------------------- Definitions

enum class Side{
	Top,
	Bottom,
	Left,
	Right
};

struct Particles{
	Particles(){}
	std::vector<sf::Vertex> particles;
	std::vector<float> life;
	void append(sf::Vector2f pos, float life, sf::Color color){
		sf::Vertex vertex(pos, color);
		this->life.push_back(life);
		particles.push_back(vertex);
	}
	void append(sf::Vector2f pos){
		sf::Vertex vertex(pos);
		this->life.push_back(100000.01f);
		particles.push_back(vertex);
	}
	void removeAt(size_t index){
		life.erase(life.begin() + index);
		particles.erase(particles.begin() + index);
	}
	void clear(){
		life.clear();
		particles.clear();
	}
};


typedef Entity<sf::RectangleShape> EntityR;
typedef Entity<sf::CircleShape> EntityC;

template<class T>
sf::Vector2i Entity<T>::getGridPosition() const{
	return sf::Vector2i(getPosition() / BLOCKSIZE);
}

template<class T>
void Entity<T>::setGridPosition(size_t x, size_t y){
	setPosition(x * BLOCKSIZE, y * BLOCKSIZE);
}

template<class T>
void Entity<T>::setOutline(float thickness, const sf::Color & color){
	setOutlineThickness(thickness);
	setOutlineColor(color);
}

Entity.cpp


#include "init.h"

Button.h


#pragma once

namespace ui{

	class Button : public sf::Drawable
	{
		EntityR buttonFace;
		sf::Font font;
		sf::Text text;
	public:
		Button(std::string text, size_t textSize, sf::Vector2f pos, sf::Color textColor, sf::Color buttonFaceColor, std::string font);
		Button& operator=(Button other){
			font = other.font;
			text = other.text;
			buttonFace = other.buttonFace;
			text.setFont(font);
			return *this;
		}
		Button(){}

		std::string getString() const;
		EntityR getButtonFaceObj() const;
		sf::Vector2f getPosition() const;
		sf::Vector2f getButtonFaceSize() const;
		size_t getTextSize() const;
		sf::Color getTextColor() const;
		sf::Color getButtonFaceColor() const;

		void setString(std::string text);
		void setPosition(const sf::Vector2f & pos);
		void setTextSize(size_t textSize);
		void setTextColor(const sf::Color & color);
		void setButtonFaceColor(const sf::Color & color);

		bool isPressed(const sf::Event & event);
		bool isHovering(const sf::Event & event);

		virtual void draw(sf::RenderTarget & target, sf::RenderStates states) const;
	};
}
 

Button.cpp (Advice on how I could improve this class would be great)


#include "init.h"


ui::Button::Button(std::string text, size_t textSize, sf::Vector2f pos, sf::Color textColor, sf::Color buttonFaceColor, std::string font){
	this->font.loadFromFile(font);
	this->text.setFont(this->font);
	setTextSize(textSize);
	setString(text);
	setPosition(pos);
	setTextColor(textColor);
	setButtonFaceColor(buttonFaceColor);
}

std::string ui::Button::getString() const{
	return text.getString();
}

EntityR ui::Button::getButtonFaceObj() const{
	return buttonFace;
}

sf::Vector2f ui::Button::getPosition() const{
	return buttonFace.getPosition();
}

sf::Vector2f ui::Button::getButtonFaceSize() const{
	return buttonFace.getSize();
}

size_t ui::Button::getTextSize() const{
	return text.getCharacterSize();
}

sf::Color ui::Button::getTextColor() const{
	return text.getColor();
}

sf::Color ui::Button::getButtonFaceColor() const{
	return buttonFace.getFillColor();
}

void ui::Button::setString(std::string text){
	this->text.setString(text);
	setTextSize(getTextSize());
}

void ui::Button::setPosition(const sf::Vector2f & pos){
	text.setPosition(pos + sf::Vector2f(10, 10));
	float left = text.getGlobalBounds().left;
	float top = text.getGlobalBounds().top;
	buttonFace.setPosition(left - 10, top - 10);
}

void ui::Button::setTextSize(size_t textSize){
	text.setCharacterSize(textSize);
	float left = text.getGlobalBounds().left;
	float top = text.getGlobalBounds().top;
	float width = text.getGlobalBounds().width;
	float height = text.getGlobalBounds().height;
	buttonFace.setSize(sf::Vector2f(width + 20, height + 20));
	buttonFace.setPosition(left - 10, top - 10);
}

void ui::Button::setTextColor(const sf::Color & color){
	text.setColor(color);
}

void ui::Button::setButtonFaceColor(const sf::Color & color){
	buttonFace.setFillColor(color);
}

bool ui::Button::isPressed(const sf::Event & event){
	if (event.type == sf::Event::MouseButtonPressed){
		if (event.mouseButton.button == sf::Mouse::Button::Left){
			sf::Vector2f mousePos((float)event.mouseButton.x, (float)event.mouseButton.y);
			if (cd::doesIntersect(mousePos, buttonFace))
				return true;
		}
	}
	return false;
}

bool ui::Button::isHovering(const sf::Event & event){
	sf::Vector2f mousePos((float)event.mouseMove.x, (float)event.mouseMove.y);
	if (cd::doesIntersect(mousePos, buttonFace))
		return true;
	return false;
}

void ui::Button::draw(sf::RenderTarget & target, sf::RenderStates states) const{
	target.draw(buttonFace);
	target.draw(text);
}

Missiles.h


#pragma once

class Missiles : public sf::Drawable
{
	std::vector<sf::RectangleShape> missiles;
	std::vector<EntityC> explosions;
	std::vector<sf::Vector2f> missileVelocities;
	std::vector<sf::Vector2f> explodePositions;
	std::vector<float> expandVelocities;
	std::vector<float> fadeVelocities;
	std::vector<sf::Color> explodeColors;
	std::vector<sf::Color> fadeColors;
	sf::RectangleShape model;
	sf::Color standardFadeColor;
	float standardFadeVel;
	float standardExpandVel;
public:
	Missiles(const sf::Vector2f & size, const sf::Vector2f & origin, const sf::Color & color, const sf::Color & fadeColor, float expandVelocity, float fadeVelocity);
	Missiles(){};

	std::vector<EntityC> getExplosions() const{ return explosions; }
	void update(SoundEffect & sound);
	void clear();
	void removeMissileAt(size_t index);
	void removeExplosionAt(size_t index);
	void explodeAtPosition(const sf::Vector2f & pos, float fadeVel, float expandVel, sf::Color color, sf::Color fadeColor);
	void fireMissile(const sf::Vector2f & velocity, const sf::Vector2f & explodePos);

	virtual void draw(sf::RenderTarget & target, sf::RenderStates states) const;
};

Missiles.cpp


#include "init.h"

Missiles::Missiles(const sf::Vector2f & size, const sf::Vector2f & origin, const sf::Color & color, const sf::Color & fadeColor, float expandVelocity, float fadeVelocity){
	model.setSize(size);
	model.setPosition(origin);
	model.setFillColor(color);
	standardFadeVel = fadeVelocity;
	standardExpandVel = expandVelocity;
	standardFadeColor = fadeColor;
	clear();
}

void Missiles::update(SoundEffect & sound){
	// Move missiles
	for (size_t i = 0; i < missiles.size(); i++){
		missiles[i].move(missileVelocities[i]);
	}

	// Test for boundary intersections
	for (size_t i = 0; i < missiles.size(); i++){
		if (cd::boundaryIntersect(missiles[i], EntityR((float)SCRW, (float)SCRH, 0, 0), Side::Top) ||
			cd::boundaryIntersect(missiles[i], EntityR((float)SCRW, (float)SCRH, 0, 0), Side::Bottom) ||
			cd::boundaryIntersect(missiles[i], EntityR((float)SCRW, (float)SCRH, 0, 0), Side::Left) ||
			cd::boundaryIntersect(missiles[i], EntityR((float)SCRW, (float)SCRH, 0, 0), Side::Right))
		{
			removeMissileAt(i);
		}
	}

	// Expand explosions and remove explosions that have completed their cycle
	for (size_t i = 0; i < explosions.size(); i++){
		explosions[i].setRadius(explosions[i].getRadius() + expandVelocities[i]);
		explosions[i].setPosition(explosions[i].getPosition() - sf::Vector2f(expandVelocities[i], expandVelocities[i]));

		if (explodeColors[i].a >= fadeVelocities[i]){
			explodeColors[i].a -= fadeVelocities[i];
			if (explodeColors[i].r > fadeColors[i].r)
				explodeColors[i].r--;
			else if (explodeColors[i].r < fadeColors[i].r)
				explodeColors[i].r++;
			if (explodeColors[i].g > fadeColors[i].g)
				explodeColors[i].g--;
			else if (explodeColors[i].g < fadeColors[i].g)
				explodeColors[i].g++;
			if (explodeColors[i].b > fadeColors[i].b)
				explodeColors[i].b--;
			else if (explodeColors[i].b < fadeColors[i].b)
				explodeColors[i].b++;
			explosions[i].setFillColor(explodeColors[i]);
		}
		else{
			removeExplosionAt(i);
			i = 0;
		}
	}

	// Test if a missile should explode
	bool willExplode = false;
	size_t index = 0;

	for (size_t i = 0; i < missiles.size(); i++){
		sf::Vector2f missilePos = missiles[i].getPosition();
		if (missileVelocities[i].x <= 0 && missileVelocities[i].y <= 0 && missilePos.x <= explodePositions[i].x && missilePos.y <= explodePositions[i].y ||
			missileVelocities[i].x <= 0 && missileVelocities[i].y > 0 && missilePos.x <= explodePositions[i].x && missilePos.y > explodePositions[i].y ||
			missileVelocities[i].x > 0 && missileVelocities[i].y <= 0 && missilePos.x > explodePositions[i].x && missilePos.y <= explodePositions[i].y ||
			missileVelocities[i].x > 0 && missileVelocities[i].y > 0 && missilePos.x > explodePositions[i].x && missilePos.y > explodePositions[i].y){
			willExplode = true;
			index = i;
		}
		if (willExplode)
			break;
	}
	if (willExplode){
		sound.play();
		fadeVelocities.push_back(standardFadeVel);
		fadeColors.push_back(standardFadeColor);
		expandVelocities.push_back(standardExpandVel);
		explodeColors.push_back(model.getFillColor());
		explosions.push_back(EntityC(model.getSize().x/2.0f, 30, explodePositions[index], explodeColors[explodeColors.size() - 1]));
		removeMissileAt(index);
	}
}

void Missiles::clear(){
	missiles.clear();
	explosions.clear();
	missileVelocities.clear();
	explodePositions.clear();
	expandVelocities.clear();
	fadeVelocities.clear();
	explodeColors.clear();
	fadeColors.clear();
}

void Missiles::removeMissileAt(size_t index){
	missiles.erase(missiles.begin() + index);
	missileVelocities.erase(missileVelocities.begin() + index);
	explodePositions.erase(explodePositions.begin() + index);
}

void Missiles::removeExplosionAt(size_t index){
	explosions.erase(explosions.begin() + index);
	explodeColors.erase(explodeColors.begin() + index);
	fadeVelocities.erase(fadeVelocities.begin() + index);
	fadeColors.erase(fadeColors.begin() + index);
	expandVelocities.erase(expandVelocities.begin() + index);
}

void Missiles::explodeAtPosition(const sf::Vector2f & pos, float fadeVel, float expandVel, sf::Color color, sf::Color fadeColor){
	fadeVelocities.push_back(fadeVel);
	fadeColors.push_back(fadeColor);
	expandVelocities.push_back(expandVel);
	explodeColors.push_back(color);
	explosions.push_back(EntityC(model.getSize().x / 2.0f, 30, pos, explodeColors[explodeColors.size() - 1]));
}

void Missiles::fireMissile(const sf::Vector2f & velocity, const sf::Vector2f & explodePos){
	explodePositions.push_back(explodePos);
	missileVelocities.push_back(velocity);
	missiles.push_back(model);
}

void Missiles::draw(sf::RenderTarget & target, sf::RenderStates states) const{
	for (auto item : missiles){
		target.draw(item);
	}
	for (auto item : explosions){
		target.draw(item);
	}
}

Collision_Detection.h


#pragma once

enum class Side;

namespace cd{
	// A simple struct to make it easier to do stuff with lines
	struct Line;
	// A simple function to determine whether entity1 is within entity2
	bool doesIntersect(EntityR entity1, EntityR entity2);
	bool doesIntersect(EntityC entity1, EntityR entity2);
	bool doesIntersect(EntityC entity1, EntityC entity2);
	bool doesIntersect(sf::Vector2f point, EntityR entity);
	// Test if an entity is outside the entity bounds
	bool boundaryIntersect(sf::RectangleShape entity, sf::RectangleShape bounds, Side side);
	bool boundaryIntersect(sf::CircleShape entity, sf::RectangleShape bounds, Side side);
	// Test for an intersection between two lines
	bool lineIntersect(Line line, sf::Vector2f pointMoving, float diam);
	// Test if two entities will intersect with the current velocity vel
	bool willIntercept(const EntityR & entityMoving, const EntityR & entityStatic, sf::Vector2f vel, Side side);
}

Collision_Detection.cpp


#include "init.h"

struct cd::Line{
	Line(){}
	Line(sf::Vector2f pointA, sf::Vector2f pointB) : A(pointA), B(pointB){}
	sf::Vector2f A;
	sf::Vector2f B;
};

bool cd::doesIntersect(EntityR entity1, EntityR entity2){
	if (entity1.getPosition().x <= entity2.getPosition().x + entity2.getSize().x &&
		entity1.getPosition().x + entity1.getSize().x >= entity2.getPosition().x &&
		entity1.getPosition().y <= entity2.getPosition().y + entity2.getSize().y &&
		entity1.getPosition().y + entity1.getSize().y >= entity2.getPosition().y)
		return true;
	return false;
}

bool cd::doesIntersect(EntityC entity1, EntityR entity2){
	return false;
}

bool cd::doesIntersect(EntityC entity1, EntityC entity2){
	sf::Vector2f origin1 = entity1.getPosition() + sf::Vector2f(entity1.getRadius(), entity1.getRadius());
	sf::Vector2f origin2 = entity2.getPosition() + sf::Vector2f(entity2.getRadius(), entity2.getRadius());
	float radius1 = entity1.getRadius();
	float radius2 = entity2.getRadius();

	if (sqrt((origin2.x - origin1.x) * (origin2.x - origin1.x) + (origin2.y - origin1.y) * (origin2.y - origin1.y)) < (radius1 + radius2)){
		return true;
	}
	return false;
}

bool cd::doesIntersect(sf::Vector2f point, EntityR entity){
	if (point.x >= entity.getPosition().x &&
		point.x <= entity.getPosition().x + entity.getSize().x &&
		point.y >= entity.getPosition().y &&
		point.y <= entity.getPosition().y + entity.getSize().y)
		return true;
	return false;
}

bool cd::boundaryIntersect(sf::RectangleShape entity, sf::RectangleShape bounds, Side side){
	if (side == Side::Top && entity.getPosition().y < bounds.getPosition().y)
		return true;
	else if (side == Side::Bottom && entity.getPosition().y + entity.getSize().y > bounds.getPosition().y + bounds.getSize().y)
		return true;
	else if (side == Side::Left && entity.getPosition().x < bounds.getPosition().x)
		return true;
	else if (side == Side::Right && entity.getPosition().x + entity.getSize().x > bounds.getPosition().x + bounds.getSize().x)
		return true;
	return false;
}

bool cd::boundaryIntersect(sf::CircleShape entity, sf::RectangleShape bounds, Side side){
	if (side == Side::Top && entity.getPosition().y < bounds.getPosition().y)
		return true;
	else if (side == Side::Bottom && entity.getPosition().y + entity.getRadius() * 2.0f > bounds.getPosition().y + bounds.getSize().y)
		return true;
	else if (side == Side::Left && entity.getPosition().x < bounds.getPosition().x)
		return true;
	else if (side == Side::Right && entity.getPosition().x + entity.getRadius() * 2.0f > bounds.getPosition().x + bounds.getSize().x)
		return true;
	return false;
}

bool cd::lineIntersect(Line line, sf::Vector2f pointMoving, float diam){
	if (line.A.x == line.B.x && pointMoving.x >= line.A.x - diam / 2.0f && pointMoving.x <= line.A.x + diam / 2.0f &&
		pointMoving.y >= line.A.y && pointMoving.y <= line.B.y)
		return true;
	if (line.A.y == line.B.y && pointMoving.y >= line.A.y - diam / 2.0f && pointMoving.y <= line.A.y + diam / 2.0f &&
		pointMoving.x >= line.A.x && pointMoving.x <= line.B.x)
		return true;
	return false;
}

bool cd::willIntercept(const EntityR & entityMoving, const EntityR & entityStatic, sf::Vector2f vel, Side side){
	float m, b;
	sf::Vector2f emCenter(entityMoving.getPosition() + (entityMoving.getSize() / 2.0f));
	m = ((emCenter.y + vel.y) - emCenter.y) /
		((emCenter.x + vel.x) - emCenter.x);
	b = emCenter.y - m * emCenter.x;

	Line line;
	sf::Vector2f start(emCenter);
	sf::Vector2f end;
	if (side <= Side::Bottom){
		line.A = entityStatic.getPosition() + sf::Vector2f(0, entityStatic.getSize().y * (int)side);
		line.B = entityStatic.getPosition() + sf::Vector2f(entityStatic.getSize().x, entityStatic.getSize().y * (int)side);
		end = sf::Vector2f((float)(int)((vel.y + emCenter.y - b) / m), vel.y + emCenter.y);
		if (side == Side::Top){
			for (float y = 0; y <= vel.y; y += 0.1f){
				if (lineIntersect(line, sf::Vector2f((float)(int)((y + emCenter.y - b) / m), y + emCenter.y), entityMoving.getSize().y))
					return true;
			}
		}
		else if (side == Side::Bottom){
			for (float y = 0; y >= vel.y; y -= 0.1f){
				if (lineIntersect(line, sf::Vector2f((float)(int)((y + emCenter.y - b) / m), y + emCenter.y), entityMoving.getSize().y))
					return true;
			}
		}
	}
	else if (side >= Side::Left){
		int tmpSide = (int)side - (int)Side::Left;
		line.A = entityStatic.getPosition() + sf::Vector2f(entityStatic.getSize().x * tmpSide, 0);
		line.B = entityStatic.getPosition() + sf::Vector2f(entityStatic.getSize().x * tmpSide, entityStatic.getSize().y);
		end = sf::Vector2f(vel.x + emCenter.x, (float)(int)(m * (vel.x + emCenter.x) + b));
		if (side == Side::Left){
			for (float x = 0; x <= vel.x; x += 0.1f){
				if (lineIntersect(line, sf::Vector2f(x + emCenter.x, (float)(int)(m * (x + emCenter.x) + b)), entityMoving.getSize().x))
					return true;
			}
		}
		else if (side == Side::Right){
			for (float x = 0; x >= vel.x; x -= 0.1f){
				if (lineIntersect(line, sf::Vector2f(x + emCenter.x, (float)(int)(m * (x + emCenter.x) + b)), entityMoving.getSize().x))
					return true;
			}
		}
	}
	return false;
}

Math.h


#pragma once

namespace mth{
	// Basically pi
	const double PI = 3.14159265358979323846264338327950288419716939937510582097494459;
	// Get the angle between two vectors in degrees
	double getAngleInDegrees(const sf::Vector2f & entityStaticCenter, const sf::Vector2f & entityMovingCenter);
	// Get the angle between two vectors in radians
	double getAngleInRadians(const sf::Vector2f & entityStaticCenter, const sf::Vector2f & entityMovingCenter);
	// Calculate directional veloctiy, given radians and global velocity
	sf::Vector2f getVelocity(double radians, double vel);
}

Math.cpp


#include "init.h"

double mth::getAngleInDegrees(const sf::Vector2f & entityStaticCenter, const sf::Vector2f & entityMovingCenter){
	double winkel = atan2((entityMovingCenter.y - (entityStaticCenter.y)), (entityMovingCenter.x - (entityStaticCenter.x)));
	winkel *= 180 / PI;
	return winkel;
}

double mth::getAngleInRadians(const sf::Vector2f & entityStaticCenter, const sf::Vector2f & entityMovingCenter){
	return atan2f((entityMovingCenter.y - (entityStaticCenter.y)), (entityMovingCenter.x - (entityStaticCenter.x)));
}

sf::Vector2f mth::getVelocity(double radians, double vel){
	return sf::Vector2f((float)cos(radians) * (float)vel, (float)sin(radians) * (float)vel);
}

SoundEffect.h


#pragma once

class SoundEffect
{
	sf::SoundBuffer buffer;
	sf::Sound sound;
public:
	SoundEffect(std::string filePath, float volume);
	SoundEffect(std::string filePath, sf::Sound sound);
	SoundEffect(){ }
	SoundEffect& operator=(SoundEffect other){
		buffer = other.buffer;
		sound = other.sound;
		sound.setBuffer(buffer);
		return *this;
	}

	float getVolume() const;
	void setVolume(float volume);

	void play();
	void pause();
	void stop();
};

SoundEffect.cpp


#include "init.h"

SoundEffect::SoundEffect(std::string filePath, float volume){
	if (!buffer.loadFromFile(filePath))
		std::exit(-3);
	sound.setBuffer(buffer);
	sound.setVolume(volume);
}

SoundEffect::SoundEffect(std::string filePath, sf::Sound sound){
	if (!buffer.loadFromFile(filePath))
		std::exit(-3);
	this->sound = sound;
	this->sound.setBuffer(buffer);
}

float SoundEffect::getVolume() const{
	return sound.getVolume();
}

void SoundEffect::setVolume(float volume){
	sound.setVolume(volume);
}

void SoundEffect::play(){
	sound.play();
}

void SoundEffect::pause(){
	sound.pause();
}

void SoundEffect::stop(){
	sound.stop();
}

Thank you for your time and for reading!

Advertisement

Downloaded and ran just fine. Nice job. Good, simple sound effects and graphics. Good response. I like the mouseover sounds in the menu. Nice touch.

Really don't need the console window. I'm guessing you use that for debugging.

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

1) Don't start identifiers with '_'., In many scopes, with many different rules they are reserved for the implementation of the compiler and standard libraries. You'll run into something weird at some point.

2) When defining a constant, prefer to use the correct constant types for the value so the compiler doesn't accidentally insert runtime casts when they're not needed.


const float GOOD = 1000.0f;
const float NOT_SO_GOOD = 1000;

3) Style nitpick. Prefer to place public class members first, protected second, private last. Anyone browsing your code is going to want to see the public interface to the class, and only dig into the details of how that is implemented after reading the public interface.

4) In Entity.h. Prefer to reserve() your vectors when you know the rough size you expect to avoid odd hickups as you allocate new objects. Vector is amortized O(1) inserts, meaning that some inserts take longer, but on average it comes out to constant time.

5) For your particles class, if you don't actually care about the ordering, you can make your remove function faster by std::swap()ing.


std::swap(vector.begin() + index, vector.begin() + (vector.size() - 1));
vector.pop_back();

this avoids having to copy everything past the deletion point.

6) Button.h. Rule of Three. If you must implement the copy operator, you probably need to implement a copy-constructor and destructor.

7) Missiles.h, you probably want to be returning a reference (or const reference) from getExplosions() to avoid unnecessary copies.

8) Missiles.cpp:70, you should always include { } on your if/else/for/etc. constructs. It helps you avoid errors.

9) CollisionDetection.cpp:29. There's no need for the sqrt. You can compare squared values on both sides (a2 + b2 = c2, so no need to square root if both sides are squared)

Here are my thoughts. Overall, the code is reasonably good and mostly easy to understand and free of obvious bugs.
init.h isn't a particularly good name, as the contents of this header aren't just initialisation related
Headers like "init.h" are not scaleable as your project grows. Better to have smaller, more focused headers
It looks like you're header structure is not standard. Ideally, a header file can be included into any source file in isolation without causing compile errors. It appears your Missile_Command header cannot be included in isolation as it depends on items that are included via init.h. The most common C++ convention is that classname.cpp #includes classname.h as the very first action in the file, which guarantees this property.

const float SECOND = 1000;
const float BLOCKSIZE = 20;
const int GRIDSIZE_X = 50;
const int GRIDSIZE_Y = 30;
const int SCRW = (int)BLOCKSIZE * GRIDSIZE_X;
const int SCRH = (int)BLOCKSIZE * GRIDSIZE_Y;
const double INTERVAL = 16.6; // 1000 / INTERVAL = FPS -> ~60
Some of these constants have poor names. Instead of SECOND, try MILLISECONDS_IN_SECOND. SCRW should probably be more descriptive, I'd guess SCREEN_WIDTH but perhaps this has another purpose. INTERVAL is fairly meaningless, perhaps MILLISECONDS_PER_FRAME or something might be better. Also, consider deriving your constants like so:

const int MILLISECONDS_IN_SECOND = 1000;
const int TARGET_FRAMES_PER_SECOND = 60;
const double MILLISECONDS_PER_FRAME = MILLISECONDS_IN_SECOND / (double)TARGET_FRAMES_PER_SECOND;
Your Missile_Command class is enormous. Consider splitting it into multiple classes, for example, one (or more) for handling the menus, another to handle the actual game, another to handle the high score list, etc.
Consider moving generic helper functions such as std::vector<std::string> split(std::string string, char splitParam); to a separate file as a free function, rather than as a member function of a large stateful class.
It is good defensive coding to obey the rule of three even for classes like Missile_Command that are unlikely to be copied. It is always possible to make a mistake (e.g. intend to pass to a function by reference but forget the reference). However, given that the destructor is empty, consider just omitting the destructor instead.
Exiting the application because you cannot load a file isn't great. At the very least, consider writing a relevant log message to standard error or a dedicated error log file, so that when someone reports the game doesn't start for them, you can quickly rule in or out such issues.
The class names EntityR and EntityC are poor. At this point in the review, I've not encountered their definitions so I cannot even guess what these are supposed to mean. Long names aren't all that difficult to type, in fact they can sometimes be easier than trying to remember how the name was shortened. Most IDEs will offer auto-completion too, so the length generally isn't a problem at all.
Be consistent with your naming. Missile_Command includes an underscore, but EntityR and EntityC do not.

_bases[0] = EntityC(50, 10, sf::Vector2f(100, 520), _baseColor);
_bases[1] = EntityC(50, 10, sf::Vector2f(200, 520), _baseColor);
_bases[2] = EntityC(50, 10, sf::Vector2f(300, 520), _baseColor);
_bases[3] = EntityC(50, 10, sf::Vector2f(600, 520), _baseColor);
_bases[4] = EntityC(50, 10, sf::Vector2f(700, 520), _baseColor);
_bases[5] = EntityC(50, 10, sf::Vector2f(800, 520), _baseColor);
This could be a loop.
You don't seem to be checking for errors loading your font. Even if you don't care that something isn't loaded, at least output a warning to standard error or a dedicated log file.
Consider creating a representation for the HighScore, rather than having a single array where every second element has a different meaning.

if (_playerName.size() <= 0) {
    // ...
}
How would playerName.size() be less than zero? A better condition might be:

if (_playerName.empty()) {
    // ...
}
You have this pattern in many other places too.

case GameState::Paused:
    _bgMoveVel = _bgOriginMoveVel * 2.0f;
    updateBG();
    updateMenuEffect();
    break;
case GameState::GameOver:
    _bgMoveVel = _bgOriginMoveVel * 2.0f;
    updateBG();
    updateMenuEffect();
    break;
case GameState::Win:
    _bgMoveVel = _bgOriginMoveVel * 2.0f;
    updateBG();
    updateMenuEffect();
    break;
You have duplicate code here. Consider moving the common logic to a separate member function, or possibly using the "fall-through" behaviour of a switch statement.

if (rand() % 1000 <= 100) {
    // Do something
}
Minor stylistic note, but I prefer a more explicit version by having a named helper function, something like:

if (random_probability(.1)) {
    // ...
}

for (size_t i = 0; i < _trails.life.size(); i++){
    _trails.life[i]--;
    if (_trails.life[i] <= 0){
        _trails.removeAt(i);
    }
}
This code is slightly incorrect. Imagine there are 3 particles in the list, and the first one is removed. The list now contains particle two at index 0, and particle three at index 1, but "i" will be 1 in the next iteration. Therefore, you've skipped processing particle two this iteration. There are a couple of fixes, including iterating backwards, or conditionally incrementing the loop counter based on whether a particle is removed. Also, consider std::remove_if, which encapsulates this logic.
In Missile_Command::render(), every branch renders the background, so consider doing that outside the switch. Also, consider naming this method renderBackground() rather than renderBG().
Seeing as only the string value of "lastHovered" is used, you can avoid creating an entire Button for it and just have a string. An alternative might be to use a pointer.

void Missile_Command::spawnEnemy(){
    if (rand() % 2 == 0){
        _enemies.push_back(EntityC(5, 5, sf::Vector2f((float)(rand() % 490), -10.0f), sf::Color::Red));
        _enemyVel.push_back(sf::Vector2f(_enemySpeed / (rand() % 5 + 1), _enemySpeed));
    }
    else{
        _enemies.push_back(EntityC(5, 5, sf::Vector2f((float)(rand() % 490 + 500), -10.0f), sf::Color::Red));
        _enemyVel.push_back(sf::Vector2f((_enemySpeed / (rand() % 5 + 1)) * -1, _enemySpeed));
    }
}
These are nearly identical. Consider something like this:

void Missile_Command::spawnEnemy(){
    int offset = 0;
    int direction = 1;
    if (random_probability(0.5)) {
        offset = 500;
        direction = -1;
    }
    _enemies.push_back(EntityC(5, 5, sf::Vector2f((float)(rand() % 490 + offset), -10.0f), sf::Color::Red));
    _enemyVel.push_back(sf::Vector2f((_enemySpeed / (rand() % 5 + 1)) * direction, _enemySpeed));
}

void Missile_Command::removeEnemyAt(size_t index){
    _enemies.erase(_enemies.begin() + index);
    _enemyVel.erase(_enemyVel.begin() + index);
}
When you have code that seems to be maintaining parallel lists, it might mean you'd be better off with a single list of composite objects. That said, there are reasons to separate lists sometimes (e.g. structure of arrays vs array of structure layout).

for (size_t i = 0; i < _enemies.size(); i++){
        if (_enemies[i].getPosition().y > _window.getSize().y - 200){
            for (size_t j = 0; j < _bases.size(); j++){
                if (_enemies.size() > 0 && cd::doesIntersect(_enemies[i], _bases[j])){
                    // ...
                }
            }
        }
    }
}
Deeply nested logic is difficult to understand. Consider the following:

for (size_t i = 0; i < _enemies.size(); i++){
        if (_enemies[i].getPosition().y > _window.getSize().y - 200){
            if (processBaseCollision(_enemies[i])) {
                // Remove enemy
                --i;
            }
        }
    }
}
 
bool processBaseCollision( /* const? */ EnemyType &enemy) {
    for (size_t i = 0; i < _bases.size(); i++){
        if (cd::doesIntersect(enemy, _bases[i])){
            // Remove base
           return true;
        }
    }
    return false;
}
Also, note that the check for _enemies.size() > 0 has been removed. This isn't necessary as the loop body would not run in the case there are no enemies. Likewise, the next loop could similarly be simplified.

(_wave * _wave) + (_wave * _wave)
Trivial, but you could use (2 * _wave * _wave) here.
The high score logic is far more complex as the numeric value is represented as a string. As I mentioned before, introducting a HighScore structure would simplify here too.
I'll stop now, there is more to review, but that is a lot for the time being.
Much of this is stylistic, so don't worry about the volume of comments - people can have different preferences for solving similar problems!
Hope this helps...

Downloaded and ran just fine. Nice job. Good, simple sound effects and graphics. Good response. I like the mouseover sounds in the menu. Nice touch.

Really don't need the console window. I'm guessing you use that for debugging.


Thanks for trying it out!
Yeah, the console window is a little intrusive at times, but is required for score keeping in this case (also debugging). I suppose I just didn't want to write a class utilizing text objects to keep track of user input.


1) Don't start identifiers with '_'., In many scopes, with many different rules they are reserved for the implementation of the compiler and standard libraries. You'll run into something weird at some point.


First of all, thanks for taking the time to read through my code!
As to the underscore naming convention, could you suggest what I should use instead?

2) When defining a constant, prefer to use the correct constant types for the value so the compiler doesn't accidentally insert runtime casts when they're not needed.

const float GOOD = 1000.0f;
const float NOT_SO_GOOD = 1000;


I'll make sure to do that.

3) Style nitpick. Prefer to place public class members first, protected second, private last. Anyone browsing your code is going to want to see the public interface to the class, and only dig into the details of how that is implemented after reading the public interface.


That's interesting, so far I've always written my classes: private > protected > public. I'll keep that in mind for my next project.

4) In Entity.h. Prefer to reserve() your vectors when you know the rough size you expect to avoid odd hickups as you allocate new objects. Vector is amortized O(1) inserts, meaning that some inserts take longer, but on average it comes out to constant time.


I never really got into reserving memory for vectors before... I'll have to look that up, but does it really make in impact on performance to reserve vector space?

5) For your particles class, if you don't actually care about the ordering, you can make your remove function faster by std::swap()ing.

std::swap(vector.begin() + index, vector.begin() + (vector.size() - 1));
vector.pop_back();
this avoids having to copy everything past the deletion point.


Wow, that actually makes way more sense than my code, but wouldn't it be vector.end() and not vector.begin() + (vector.size() - 1)? Or is there some sort of difference?

6) Button.h. Rule of Three. If you must implement the copy operator, you probably need to implement a copy-constructor and destructor.


You're right about the copy constructor, but I believe sfml takes care of deallocating it's own allocated memory, so the destructor wouldn't really be necessary(?) I could implement it anyway, just to stay consistent with the C++ law.

7) Missiles.h, you probably want to be returning a reference (or const reference) from getExplosions() to avoid unnecessary copies.


I thought you only had to do the const reference thing on parameters, but I suppose now that you mentioned it, it kind of does make sense to return a const reference as well.

8) Missiles.cpp:70, you should always include { } on your if/else/for/etc. constructs. It helps you avoid errors.


Yeah, biggrin.png I keep telling myself that, because most of the time I end up expanding my if statements anyway, but I suppose in some cases it's just faster to not include the curly brackets. I think I'll eventually find an equilibrium between curly brackets and no curly brackets.

9) CollisionDetection.cpp:29. There's no need for the sqrt. You can compare squared values on both sides (a2 + b2 = c2, so no need to square root if both sides are squared)


Woops.


Wow, that actually makes way more sense than my code, but wouldn't it be vector.end() and not vector.begin() + (vector.size() - 1)? Or is there some sort of difference?

vector.end() is

1) you could do (vector.end() - 1) as well. The end iterator is one-past the end of the array, and does not point at a valid object.

2) It's all relative to the container. Not all containers provide iterators that support (end() -1). Vector happens to be a random-access iterator, so vector.begin() + x is just as fast as vector.end() - y. So yes, you could base the second point off the end iterator.

You can also use:

std::swap(vector[index], vector.back());
vector.pop_back();

Note that in 2D games, this can cause sprites to "pop" over one another. For small particles in large swarms, this probably isn't going to be noticeable, but for key entities in a game, it might not be worth it.

This is the kind of consequence that caused Kulseran to note:


If you don't actually care about the ordering...

Here are my thoughts. Overall, the code is reasonably good and mostly easy to understand and free of obvious bugs.

init.h isn't a particularly good name, as the contents of this header aren't just initialisation related

Headers like "init.h" are not scaleable as your project grows. Better to have smaller, more focused headers

It looks like you're header structure is not standard. Ideally, a header file can be included into any source file in isolation without causing compile errors. It appears your Missile_Command header cannot be included in isolation as it depends on items that are included via init.h. The most common C++ convention is that classname.cpp #includes classname.h as the very first action in the file, which guarantees this property.


So, I should make multiple headers?
includes.h and constants.h?

But I also don't really see how "init.h" isn't scalable, is it because of the merging of includes and constants in a single file, or is there another reason?

Even with the "classname.cpp #includes classname.h" thing, wouldn't it still be the same? Because the classname.h still might depend on stuff from init.h.
Or am I getting this wrong somehow?


const float SECOND = 1000;
const float BLOCKSIZE = 20;
const int GRIDSIZE_X = 50;
const int GRIDSIZE_Y = 30;
const int SCRW = (int)BLOCKSIZE * GRIDSIZE_X;
const int SCRH = (int)BLOCKSIZE * GRIDSIZE_Y;
const double INTERVAL = 16.6; // 1000 / INTERVAL = FPS -> ~60
Some of these constants have poor names. Instead of SECOND, try MILLISECONDS_IN_SECOND. SCRW should probably be more descriptive, I'd guess SCREEN_WIDTH but perhaps this has another purpose. INTERVAL is fairly meaningless, perhaps MILLISECONDS_PER_FRAME or something might be better. Also, consider deriving your constants like so:


const int MILLISECONDS_IN_SECOND = 1000;
const int TARGET_FRAMES_PER_SECOND = 60;
const double MILLISECONDS_PER_FRAME = MILLISECONDS_IN_SECOND / (double)TARGET_FRAMES_PER_SECOND;


Yeah, I try to spend the least amount of time on naming my variables, if I don't think of a name fast, it might get a bad name tongue.png.

Your Missile_Command class is enormous. Consider splitting it into multiple classes, for example, one (or more) for handling the menus, another to handle the actual game, another to handle the high score list, etc.


I know. After a while it got really confusing to try and find code I was looking for in that file, In my next project I'm going to have my entire GUI in a separate class, that should save me a few lines.

Consider moving generic helper functions such as std::vector<std::string> split(std::string string, char splitParam); to a separate file as a free function, rather than as a member function of a large stateful class.


I was thinking of doing that, but then I didn't... I suppose it was too much work for me tongue.png, especially because it was only used for the high scores.

It is good defensive coding to obey the rule of three even for classes like Missile_Command that are unlikely to be copied. It is always possible to make a mistake (e.g. intend to pass to a function by reference but forget the reference). However, given that the destructor is empty, consider just omitting the destructor instead.


Yeah, KulSeran told me the same thing just about. And my question to him still stands: "[background=#fafbfc]...I believe sfml takes care of deallocating it's own allocated memory, so the destructor wouldn't really be necessary(?)[/background]". Is it always necessary to implement all three? Because in the case of the button class, the destructor would be empty.

Exiting the application because you cannot load a file isn't great. At the very least, consider writing a relevant log message to standard error or a dedicated error log file, so that when someone reports the game doesn't start for them, you can quickly rule in or out such issues.


I remember someone posting on a previous thread of mine that I shouldn't keep the application running after an error has occured. Should I keep it running or not? I'm confused, but I suppose I could write the error to a error log file or something.

The class names EntityR and EntityC are poor. At this point in the review, I've not encountered their definitions so I cannot even guess what these are supposed to mean. Long names aren't all that difficult to type, in fact they can sometimes be easier than trying to remember how the name was shortened. Most IDEs will offer auto-completion too, so the length generally isn't a problem at all.


My attempt at trying to stay consistent with the sfml naming convention (mostly inspired by the sf::Vector<> class) tongue.png, I suppose I failed.

Be consistent with your naming. Missile_Command includes an underscore, but EntityR and EntityC do not.


That's a mistake, I think most of the time I just completely forget that I'm following any kind of naming convention and just name the damn thing whatever seems right at the time tongue.png.


_bases[0] = EntityC(50, 10, sf::Vector2f(100, 520), _baseColor);
_bases[1] = EntityC(50, 10, sf::Vector2f(200, 520), _baseColor);
_bases[2] = EntityC(50, 10, sf::Vector2f(300, 520), _baseColor);
_bases[3] = EntityC(50, 10, sf::Vector2f(600, 520), _baseColor);
_bases[4] = EntityC(50, 10, sf::Vector2f(700, 520), _baseColor);
_bases[5] = EntityC(50, 10, sf::Vector2f(800, 520), _baseColor);
This could be a loop.


Huh, but how would I integrate the jump from 300 to 600? I can't think of a way.

You don't seem to be checking for errors loading your font. Even if you don't care that something isn't loaded, at least output a warning to standard error or a dedicated log file.


Woops, I think the problem here is that I used to have a text class. When I got rid of that I tried to rewrite everything into the button class, I suppose I just missed a few things on the way.


if (_playerName.size() <= 0) {
    // ...
}
How would playerName.size() be less than zero? A better condition might be:


if (_playerName.empty()) {
    // ...
}
You have this pattern in many other places too.


I keep forgetting some of these built in methods to make life easier... I'll implement that.

Seeing as only the string value of "lastHovered" is used, you can avoid creating an entire Button for it and just have a string. An alternative might be to use a pointer.


I used a whole button class, because I thought there was a method to test if one class was equal to another, that failed, so I used the string value in the button, seems stupid now that I think about it.


void Missile_Command::removeEnemyAt(size_t index){
    _enemies.erase(_enemies.begin() + index);
    _enemyVel.erase(_enemyVel.begin() + index);
}
When you have code that seems to be maintaining parallel lists, it might mean you'd be better off with a single list of composite objects. That said, there are reasons to separate lists sometimes (e.g. structure of arrays vs array of structure layout).


That's what I did with the missiles, because I first had like five vectors just for the missiles in my Missile_Command class. But I didn't want to make a whole new class for the enemies, I suppose it slipped my mind that I could have easily used a structure to store both items...

I'll stop now, there is more to review, but that is a lot for the time being.

Much of this is stylistic, so don't worry about the volume of comments - people can have different preferences for solving similar problems!

Hope this helps...


Thanks for reading through my code, I appreciate the time that you took to do so. This review really gave me a further push in the right direction and I'll most likely use this as a reference for future projects.

Wow, that actually makes way more sense than my code, but wouldn't it be vector.end() and not vector.begin() + (vector.size() - 1)? Or is there some sort of difference?

vector.end() is
1) you could do (vector.end() - 1) as well. The end iterator is one-past the end of the array, and does not point at a valid object.
2) It's all relative to the container. Not all containers provide iterators that support (end() -1). Vector happens to be a random-access iterator, so vector.begin() + x is just as fast as vector.end() - y. So yes, you could base the second point off the end iterator.


Oh, I thought vector.end() was just the last item in the array.

You can also use:


std::swap(vector[index], vector.back());
vector.pop_back();
Note that in 2D games, this can cause sprites to "pop" over one another. For small particles in large swarms, this probably isn't going to be noticeable, but for key entities in a game, it might not be worth it.

You're right, in my case it wouldn't matter for the _trails particles, because they all have a one pixel area and are mostly white.


_bases[0] = EntityC(50, 10, sf::Vector2f(100, 520), _baseColor);
_bases[1] = EntityC(50, 10, sf::Vector2f(200, 520), _baseColor);
_bases[2] = EntityC(50, 10, sf::Vector2f(300, 520), _baseColor);
_bases[3] = EntityC(50, 10, sf::Vector2f(600, 520), _baseColor);
_bases[4] = EntityC(50, 10, sf::Vector2f(700, 520), _baseColor);
_bases[5] = EntityC(50, 10, sf::Vector2f(800, 520), _baseColor);
This could be a loop.


Huh, but how would I integrate the jump from 300 to 600? I can't think of a way.

For example, using an array.

for(int n=0; n<6; ++n) {
    static float const data[] = {100, 200, 300, 600, 700, 800};
    _bases[n] = EntityC(50, 10, sf::Vector2f(data[n], 520), _baseColor);
}

Maybe not worth it for such a small piece of repetitive code, but at least it begins to separates the data from the code.


So, I should make multiple headers?

Not particularly. For games, I sometimes have a "constants.h" or similar. However, an alternative is to place the constants in the header or source file they are actually used, rather than moving them into a file full of unrelated values.


But I also don't really see how "init.h" isn't scalable, is it because of the merging of includes and constants in a single file, or is there another reason?

As the number of classes grows, your compile time will get worse and worse. Ideally, when you update a header, only source files that depend (directly or indirectly) on that header should need to be recompiled. When you have a single header that includes all of the classes, any change is going to cause all the files to be rebuilt.


Even with the "classname.cpp #includes classname.h" thing, wouldn't it still be the same? Because the classname.h still might depend on stuff from init.h.
Or am I getting this wrong somehow?

If each header and source file is conservative about what it includes, then it would not be the same - provided your classes don't have unreasonable dependencies on one another.


In my next project I'm going to have my entire GUI in a separate class, that should save me a few lines.

It isn't just about saving lines, it is about manageable complexity. For example, one way to implement your GUI would be to have a class per "screen", so a main menu class, a high score class, etc.


Is it always necessary to implement all three? Because in the case of the button class, the destructor would be empty.

The rule of three states that if a class has a destructor, copy constructor or an assignment operator, it probably should have all three. If a class needs none of these, then that is fine and consistent with the rule.


I remember someone posting on a previous thread of mine that I shouldn't keep the application running after an error has occured. Should I keep it running or not? I'm confused, but I suppose I could write the error to a error log file or something.

It depends: how fun will the game be in the event of that particular error? If a sound fails to load, then you can probably continue. If the player's sprite fails to load, I'm not sure anyone will want to play the game.

Whether you continue or halt, you should log information. This will allow you to diagnose strange behaviour on other people's computers once you release your game. A dedicated log file isn't particularly hard to add, but writing to standard error is too easy to pass up - and you can easily make this output to a log file by running your game from the command line and using stream redirection!


My attempt at trying to stay consistent with the sfml naming convention (mostly inspired by the sf::Vector<> class)

It can make sense to take shortcuts with classes that would be used everywhere, that would otherwise have verbose names and that are unambiguous. For example, "Rect" is a frequent contraction of Rectangle, as there isn't much ambiguity here.


Huh, but how would I integrate the jump from 300 to 600? I can't think of a way.

It is possible, a simple example:


for (int i = 0 ; i < NUMBER_OF_BASES ; ++i) {
     int x = (i + 1) * 100;
     if (i > NUMBER_OF_BASES / 2) {
         x += 300;
     }
    _bases[i] = EntityC(50, 10, sf::Vector2f(x, 520), _baseColor);
}

That is actually less clear than the long way. I didn't actually spot that jump however. Even if you didn't have a jump, the code would not be much clearer with a loop, so don't worry.


But I didn't want to make a whole new class for the enemies...

Creating a class or structure can actually be easier than writing the code to maintain several arrays in parallel.


Thanks for reading through my code, I appreciate the time that you took to do so.

Glad to assist!

This topic is closed to new replies.

Advertisement