After reading the SFML Game Development book, I decided to put what was learned to the test by creating a simple Breakout clone. I have posted the source code to GitHub: https://github.com/andrewd440/breakoutsfmlI wanted to see if I could get any sort of feedback on the general code structure that I used or any programming techniques that I could improve on before I proceed to my next project.Application.h/// <summary>/// Initializing class for the game app/// </summary>class Application : sf::NonCopyable{public:                                Application();    void                        run();private:    void                        update(sf::Time dt);    void                        processEvents();    void                        render();    void                        registerStates();    void                        loadResources();private:    FontHolder                  mFonts;    TextureHolder               mTextures;    sf::RenderWindow            mWindow;    StateStack                  mStateStack;    static const sf::Time       mTimePerFrame;    SoundPlayer                 mSoundPlayer;};  Application.cpp    const sf::Time Application::mTimePerFrame = sf::seconds(1.f / 60.f);/// <summary>/// Initializes a new instance of the <see cref=Application/> class./// </summary>Application::Application(): mWindow(sf::VideoMode(865, 956), Drewski's Block Breaker, sf::Style::Close), mStateStack(mWindow), mFonts(), mTextures(), mSoundPlayer(){    registerStates();    loadResources();    // Start statestack at title screen    mStateStack.pushState(States::Title);}/// <summary>/// Starts the main game loop./// </summary>void Application::run(){    sf::Clock clock;    sf::Time elapsedTime = sf::Time::Zero;    while (mWindow.isOpen())    {        elapsedTime += clock.restart();        while (elapsedTime >= mTimePerFrame)        {            update(mTimePerFrame);            processEvents();            elapsedTime -= mTimePerFrame;        }        render();    }}/// <summary>/// Updates all game objects./// </summary>/// <param name=dt>The delta time.</param>void Application::update(sf::Time dt){    mStateStack.update(dt);    mSoundPlayer.removeInactiveSounds();}/// <summary>/// Processes the events./// </summary>void Application::processEvents(){    sf::Event event;    while (mWindow.pollEvent(event))    {        mStateStack.handleEvent(event);        switch (event.type)        {            case sf::Event::Closed:                mWindow.close();        }    }    if (mStateStack.isEmpty())        mWindow.close();}/// <summary>/// Renders this all game objects./// </summary>void Application::render(){    mWindow.clear();    mStateStack.draw();    mWindow.display();}/// <summary>/// Registers the game states./// </summary>void Application::registerStates(){    mStateStack.registerState<GameState>(States::Game);    mStateStack.registerState<TitleState>(States::Title);    mStateStack.registerState<PauseState>(States::Pause);}/// <summary>/// Loads fonts and textures used in the game./// </summary>void Application::loadResources(){    // Set ServiceLocator window    ServiceLocator::setWindow(mWindow);    // Load all game fonts and set ServiceLocator font holder    mFonts.load(Fonts::Main, Media/Fonts/Sansation.ttf);    ServiceLocator::setFontHolder(mFonts);    // Load all game textures and set ServiceLocator texture holder    mTextures.load(Textures::Title, Media/Textures/TitleScreen.png);    mTextures.load(Textures::Paddle, Media/Textures/Paddles.png);    mTextures.load(Textures::Ball, Media/Textures/Ball.png);    mTextures.load(Textures::Bricks, Media/Textures/BrickTextures.png);    mTextures.load(Textures::Particle, Media/Textures/Particle.png);    ServiceLocator::setTextureHolder(mTextures);    // Set ServiceLocator SoundPlayer    ServiceLocator::setSoundPlayer(mSoundPlayer);}StateStack.h/// <summary>/// Used to manage various states in the game/// </summary>class StateStack : public sf::NonCopyable{public:    // Actions for the statestack    enum Action    {        Pop,        Push,        Clear,    };    explicit                    StateStack(sf::RenderWindow& window);    void                        update(sf::Time dt);    void                        handleEvent(const sf::Event& event);    void                        draw();    template <typename T>    void registerState(States::ID id);    // Called by outside code for changing the current statestack    void                        popState();    void                        pushState(States::ID id);    void                        clearStates();    bool                        isEmpty() const;private:    void                        applyPendingChanges();    // Called from applyPendingChanges when a state is pushed    State::Ptr                  createState(States::ID);private:    // Structure for applying actions to the statestack at the correct time during game update    struct PendingAction    {                                            PendingAction(Action a, States::ID i);        Action                              action;        States::ID                          id;    };private:    sf::RenderWindow&                       mWindow;    std::vector<State::Ptr>                 mStack;    std::vector<PendingAction>              mPendingList;    std::map<States::ID,        std::function<State::Ptr()>>        mFactories;};/// <summary>/// Registers the game state./// </summary>/// <param name=id>The state identifier.</param>template <typename T>void StateStack::registerState(States::ID id){    // Map functors for states that are registered    mFactories[id] = [this]()    {        return State::Ptr(new T(*this, mWindow));    };}StateStack.cppStateStack::PendingAction::PendingAction(Action a, States::ID i = States::None): action(a), id(i){}StateStack::StateStack(sf::RenderWindow& window): mWindow(window), mPendingList(), mStack(), mFactories(){}void StateStack::popState(){    mPendingList.push_back(PendingAction(Pop));}void StateStack::pushState(States::ID id){    mPendingList.push_back(PendingAction(Push, id));}void StateStack::clearStates(){    mPendingList.push_back(PendingAction(Clear));}/// <summary>/// Applies the pending changes./// </summary>void StateStack::applyPendingChanges(){    // We only update the statestack at optimal times,     // so all requests go through a pending list and are then executed    for (auto change : mPendingList)    {        switch (change.action)        {            case Push:                mStack.push_back(createState(change.id));                break;            case Pop:                mStack.pop_back();                break;            case Clear:                mStack.clear();                break;        }    }    mPendingList.clear();}/// <summary>/// Find the functor for the state and return the instantiated state/// </summary>/// <param name=id>The state identifier.</param>/// <returns>Pointer to the new state</returns>State::Ptr StateStack::createState(States::ID id){    auto found = mFactories.find(id);    assert(found != mFactories.end());    return found->second();}void StateStack::update(sf::Time dt){    // Update each state until one returns false in it's update function    for (auto state = mStack.rbegin(); state != mStack.rend(); state++)    {        if (!(*state)->update(dt))            break;    }    applyPendingChanges();}void StateStack::handleEvent(const sf::Event& event){    for (auto state = mStack.rbegin(); state != mStack.rend(); state++)    {        if (!(*state)->processEvent(event))            break;    }    applyPendingChanges();}void StateStack::draw(){    for (auto state = mStack.begin(); state != mStack.end(); state++)        (*state)->draw();}bool StateStack::isEmpty() const{    return mStack.size() <= 0;}GameState.h/// <summary>/// Represents main gameplay state/// </summary>class GameState :    public State{public:    GameState(StateStack& stack, sf::RenderWindow& window);    virtual bool                update(sf::Time dt);    virtual bool                processEvent(const sf::Event& event);    virtual void                draw();private:    void                        displayCompletionStatus() const;private:    World                       mWorld;    sf::Text                    mCompletionStatusText;    sf::Time                    mCompletionDisplayTimer;};GameState.cpp// Used for displaying success/failure text at end of gameconst sf::Time CompletionDisplayInterval = sf::seconds(3.f);GameState::GameState(StateStack& stack, sf::RenderWindow& window): State(stack, window), mWorld(window), mCompletionStatusText(), mCompletionDisplayTimer(sf::Time::Zero){    mCompletionStatusText.setFont(ServiceLocator::getFontHolder().get(Fonts::Main));    mCompletionStatusText.setPosition(window.getSize().x / 2.f, window.getSize().y / 2.f);    mCompletionStatusText.setCharacterSize(30);}bool GameState::update(sf::Time dt){    if (!mWorld.isWorldComplete())        mWorld.update(dt);    else    {        // If World is complete, show result text        mCompletionDisplayTimer += dt;        if (mWorld.getMissionStatus() == World::Success)            mCompletionStatusText.setString(You won!!!);        else            mCompletionStatusText.setString(Sorry, you failed.);    }    if (mCompletionDisplayTimer > CompletionDisplayInterval)    {        requestStackClear();        requestStackPush(States::Title);    }    return true;}bool GameState::processEvent(const sf::Event& event){    mWorld.handleInput(event);    if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape)        requestStackPush(States::Pause);    return true;}void GameState::draw(){    mWorld.draw();    if (mWorld.isWorldComplete())    {        sf::RenderWindow& window = getWindow();        centerOrigin(mCompletionStatusText);        window.draw(mCompletionStatusText);    }}World.h/// <summary>/// Represents the game world/// </summary>class World{public:    enum MissionStatus    {        Running,        Success,        Failed    };                                                                World(sf::RenderWindow& window);    void                                                        handleInput(const sf::Event& event);    void                                                        update(sf::Time dt);    void                                                        draw();    bool                                                        isWorldComplete() const;    World::MissionStatus                                        getMissionStatus() const;private:    void                                                        handleCollisions();    void                                                        loadNextLevel();    void                                                        loadBalls(Levels::ID level);    void                                                        removeDestroyedBalls();    void                                                        updateRemainingBalls();private:    sf::RenderWindow&                                           mWindow;    Player                                                      mPlayer;    std::vector<std::unique_ptr<Ball>>                          mBalls;    std::vector<std::unique_ptr<Brick>>                         mBricks;    unsigned int                                                mCurrentLevel;    bool                                                        mIsChangingLevel;    sf::Time                                                    mLevelTransitionTimer;    bool                                                        mSpawnedNewBricks;    sf::Text                                                    mLevelChangeText;    int                                                         mRemainingBalls;    sf::Text                                                    mBallDisplayText;    bool                                                        mIsWorldComplete;    World::MissionStatus                                        mMissionStatus;    ParticleSystem                                              mBallTrails;    ParticleSystem                                              mBackgroundBounceParticles;    ParticleEmitter                                             mBounceParticleEmitter;};World.cppconst sf::Time LevelTransitionInterval = sf::seconds(4.f);namespace{    // Sets up table indexed by Level::ID, provides: Max Balls and Lives Per Ball    const std::vector<LevelInfo> LevelTable = initializeLevelTable();}World::World(sf::RenderWindow& window): mWindow(window), mPlayer(), mBalls(), mBricks(), mCurrentLevel(0), mIsChangingLevel(false), mLevelTransitionTimer(sf::Time::Zero), mSpawnedNewBricks(false), mLevelChangeText(), mBallDisplayText(), mRemainingBalls(0), mIsWorldComplete(false), mMissionStatus(World::Running), mBallTrails(Particle::Trail), mBackgroundBounceParticles(Particle::BackgroudBouncers), mBounceParticleEmitter(mBackgroundBounceParticles){    auto windowBounds = mWindow.getSize();    mPlayer.setPosition(windowBounds.x / 2.f, windowBounds.y - 50.f);    // Set position for background particles    mBounceParticleEmitter.setPosition(windowBounds.x / 2.f, windowBounds.y / 2.f);    // Text to display on level changes    mLevelChangeText.setFont(ServiceLocator::getFontHolder().get(Fonts::Main));    mLevelChangeText.setPosition(window.getSize().x  / 2.f, window.getSize().y / 2.f);    mLevelChangeText.setCharacterSize(30);    // Balls Remaining display    mBallDisplayText.setFont(ServiceLocator::getFontHolder().get(Fonts::Main));    mBallDisplayText.setPosition(window.getSize().x - 200, window.getSize().y - 30);    mBallDisplayText.setCharacterSize(20);    loadNextLevel();}/// <summary>/// Handles events and input for the game world./// </summary>/// <param name=event>Game event.</param>void World::handleInput(const sf::Event& event){    mPlayer.handleInput();}/// <summary>/// Updates the game world./// </summary>/// <param name=dt>The delta time.</param>void World::update(sf::Time dt){    updateRemainingBalls();    handleCollisions();    removeDestroyedBalls();    // If there are no more ball, but still bricks left, you lose    if (mRemainingBalls <= 0 && !mBricks.empty())    {        mMissionStatus = World::Failed;        mIsWorldComplete = true;    }    // If bricks are cleared and world is not complete, load next level    if ((mBricks.empty() || mIsChangingLevel) && !mIsWorldComplete)    {        mIsChangingLevel = true;        mLevelTransitionTimer += dt;        loadNextLevel();    }    mPlayer.update(dt);    for (auto& ball : mBalls)    {        ball->update(dt);    }    mBallTrails.update(dt);    mBounceParticleEmitter.update(dt);    mBackgroundBounceParticles.update(dt);    for (auto& brick : mBricks)        brick->update(dt);}/// <summary>/// Draws the game world./// </summary>void World::draw(){    mWindow.draw(mBackgroundBounceParticles);    for (auto& ball : mBalls)    {        mWindow.draw(*ball);    }    mWindow.draw(mPlayer);    mWindow.draw(mBallDisplayText);    mWindow.draw(mBallTrails);    for (auto& brick : mBricks)        mWindow.draw(*brick);    if (mIsChangingLevel)    {        centerOrigin(mLevelChangeText);        mWindow.draw(mLevelChangeText);    }}/// <summary>/// Determines whether the World is complete./// </summary>/// <returns></returns>bool World::isWorldComplete() const{    return mIsWorldComplete;}World::MissionStatus World::getMissionStatus() const{    return mMissionStatus;}/// <summary>/// Handles collisions with all game objects./// </summary>void World::handleCollisions(){    for (auto& ball : mBalls)    {        auto ballRect = ball->getBoundingRect();        float ballWidth = ballRect.width;        float ballHeight = ballRect.height;        float ballLeft = ballRect.left;        float ballTop = ballRect.top;        // Create four contact points for the ball        sf::Vector2f ballLeftPoint(ballLeft, ballTop + (ballHeight / 2.f));                 // Middle of left side        sf::Vector2f ballRightPoint(ballLeft + ballWidth, ballTop + (ballHeight / 2.f));    // Middle of right side        sf::Vector2f ballTopPoint(ballLeft + (ballWidth / 2.f), ballTop);                   // Middle of top        sf::Vector2f ballBottomPoint(ballLeft + (ballWidth / 2.f), ballTop + ballHeight);   // Middle of bottom        // Ball/Paddle collisions        auto playerRect = mPlayer.getBoundingRect();        if (ballRect.intersects(playerRect))        {            // How the paddle velocity will affect the ball bounce            float angleChangeFactor = 0;            if (mPlayer.getVelocity().x > 0)                angleChangeFactor = -10.f;            else if (mPlayer.getVelocity().x < 0)                angleChangeFactor = 10.f;            // Adjust Ball angle for bounce            if (playerRect.contains(ballBottomPoint))            {                ball->setAngle(360.f - ball->getAngle() + angleChangeFactor);                ball->setPosition(ball->getPosition().x, mPlayer.getPosition().y - (mPlayer.getBoundingRect().height / 2.f) - (ballHeight / 2.f));            }            else if (playerRect.contains(ballLeftPoint) || playerRect.contains(ballRightPoint))                ball->setAngle(180.f - ball->getAngle() + angleChangeFactor);        }        // Ball/Brick collisions        for (auto itr = mBricks.begin(); itr != mBricks.end();)        {            auto brickRect = (*itr)->getBoundingRect();            float ballAngle(ball->getAngle());            if (ballRect.intersects(brickRect))            {                // If contact is on the left or right of the ball, adjust angle properly                if (brickRect.contains(ballLeftPoint) || brickRect.contains(ballRightPoint))                    ball->setAngle(180.f - ball->getAngle());                // If contact is on the top or top of the ball, adjust angle properly                else if (brickRect.contains(ballTopPoint) || brickRect.contains(ballBottomPoint))                    ball->setAngle(360.f - ball->getAngle());            }            // If the angle of the ball changed, then we know a brick was hit. We damage that brick and if it is destroyed,            // if so, we remove the brick from our brick container.            if (ballAngle != ball->getAngle())            {                // Play a random bounce sound                ServiceLocator::getSoundPlayer().play(static_cast<Sounds::ID>(randomInt(Sounds::BallCount)));                (*itr)->damage(ball->getPower());                if ((*itr)->isDestroyed())                    mBricks.erase(itr);                break;            }            else                itr++;        }    }}/// <summary>/// Loads the next level./// </summary>void World::loadNextLevel(){    // Show level completed text at beginning of level transition    if (mCurrentLevel > 0 && mLevelTransitionTimer < LevelTransitionInterval / 2.f )         mLevelChangeText.setString(Level complete!);    // Spawn the bricks in at half the level transition interval    if (mLevelTransitionTimer >= LevelTransitionInterval / 2.f && !mSpawnedNewBricks)    {        mSpawnedNewBricks = true;        mBalls.clear();        mCurrentLevel++;        // Set loading level text        mLevelChangeText.setString(Loading level  + toString(mCurrentLevel));        // Load new bricks and spawn balls        switch (mCurrentLevel)        {        case 1:            mBricks = spawnLevelOne();            loadBalls(Levels::One);            break;        case 2:            mBricks = spawnLevelTwo();            loadBalls(Levels::Two);            break;        case 3:            mBricks = spawnLevelThree();            loadBalls(Levels::Three);            break;        default:            mLevelChangeText.setString();        }    }    // Close out level transition when the interval is closed    if (mLevelTransitionTimer >= LevelTransitionInterval)    {        mIsChangingLevel = false;        mSpawnedNewBricks = false;        mLevelTransitionTimer = sf::Time::Zero;        // If we passed to last level, we win        if (mCurrentLevel > Levels::Count)        {            mIsWorldComplete = true;            mMissionStatus = World::Success;        }    }}/// <summary>/// Load Balls./// </summary>/// <param name=number>The number of balls to load.</param>void World::loadBalls(Levels::ID level){    for (int i = 0; i < LevelTable[level].maxBalls; i++)    {        std::unique_ptr<Ball> ball(new Ball(LevelTable[level].livesPerBall, mBallTrails));        mBalls.push_back(std::move(ball));    }}/// <summary>/// Removes the destroyed balls./// </summary>void World::removeDestroyedBalls(){    for (auto itr = mBalls.begin(); itr != mBalls.end();)    {        if ((*itr)->isDestroyed())            itr = mBalls.erase(itr);        else            itr++;    }}/// <summary>/// Updates the remaining balls and updates ball display./// </summary>void World::updateRemainingBalls(){    int sum = 0;    for (auto& ball : mBalls)    {        sum += ball->getLives();    }    mRemainingBalls = sum;    mBallDisplayText.setString(Balls Remaining:  + toString(mRemainingBalls));}Ball.h/// <summary>/// Represents the Ball object/// </summary>class Ball :    public Entity{public:                            Ball(int lives, ParticleSystem& particlSystem);    float                   getAngle() const;    void                    setAngle(float angle);    void                    update(sf::Time dt);    void                    resetPosition();    sf::FloatRect           getBoundingRect() const;    unsigned int            getPower() const;    unsigned int            getLives() const;    bool                    isDestroyed() const;    void                    markForDestroyed();    void                    reactToBounce();private:    void                    handleWallCollision();    void                    checkIfBallIsLive();    void                    draw(sf::RenderTarget& target, sf::RenderStates states) const;    float                   linearVelocityX() const;    float                   linearVelocityY() const;private:    float                   mAngle;    sf::Vector2f            mSpawnPoint;    sf::Sprite              mSprite;    unsigned int            mPower;    float                   mDefaultVelocity;    sf::Time                mMovementTimer;    int                     mLives;    bool                    mIsDestroyed;    ParticleEmitter         mBallTrailEmitter;};Ball.cppconst sf::Time MovementDelayInterval = sf::seconds(.1f);Ball::Ball(int lives, ParticleSystem& particlSystem): Entity(), mSprite(ServiceLocator::getTextureHolder().get(Textures::Ball)), mSpawnPoint(ServiceLocator::getWindow().getSize().x / 2.f, ServiceLocator::getWindow().getSize().y / 2.f), mDefaultVelocity(500.f), mPower(1), mLives(lives), mMovementTimer(sf::Time::Zero), mIsDestroyed(false), mAngle(), mBallTrailEmitter(particlSystem){    centerOrigin(mSprite);      resetPosition();}void Ball::draw(sf::RenderTarget& target, sf::RenderStates states) const{    states.transform *= getTransform();    target.draw(mSprite, states);}void Ball::resetPosition(){    // Reset the movement timer for a delay after the reset    mMovementTimer = sf::Time::Zero;    setPosition(mSpawnPoint);    mAngle = randomInt(361);}unsigned int Ball::getLives() const{    return mLives;}void Ball::update(sf::Time dt){    mMovementTimer += dt;    // Provide updates to ball trail particles    mBallTrailEmitter.setPosition(getPosition());    mBallTrailEmitter.update(dt);    if (mMovementTimer >= MovementDelayInterval)    {        checkIfBallIsLive();        handleWallCollision();        sf::Vector2f velocity;        velocity.x = linearVelocityX() * mDefaultVelocity;        velocity.y = linearVelocityY() * mDefaultVelocity;        setVelocity(velocity);        Entity::update(dt);    }}/// <summary>/// Handles the ball collisions with the wall./// </summary>void Ball::handleWallCollision(){    sf::RenderWindow& window = ServiceLocator::getWindow();    // If the ball hits the sides of the window, rebound its angle by 180    if (getPosition().x + (mSprite.getLocalBounds().width / 2.f) > window.getSize().x        || getPosition().x - (mSprite.getLocalBounds().width / 2.f) < 0.f)    {        setAngle(180.f - getAngle());        // Play collision sound        ServiceLocator::getSoundPlayer().play(Sounds::Wall1);        // Make sure ball is inside the screen        if (getPosition().x + (mSprite.getLocalBounds().width / 2.f) > window.getSize().x)            setPosition(window.getSize().x - mSprite.getLocalBounds().width / 2.f, getPosition().y);        else            setPosition(mSprite.getLocalBounds().width / 2.f, getPosition().y);        // If angle is too flat, make it wider        if ((getAngle() < 190.f && getAngle() > 170.f))            setAngle(getAngle() - 20.f);        else if (getAngle() < 10.f || getAngle() > 350.f)            setAngle(getAngle() + 20.f);    }    // If the ball hits the top of the window, rebound angle by 360    else if (getPosition().y - (mSprite.getLocalBounds().height / 2.f) < 0.f)    {        setAngle(360.f - getAngle());        ServiceLocator::getSoundPlayer().play(Sounds::Wall2);        if (getPosition().y + (mSprite.getLocalBounds().height / 2.f) < 0)            setPosition(getPosition().x, 0);    }    // Keep angle from 0-360    if (getAngle() > 360)        setAngle( getAngle() - 360);    else if (getAngle() < 0)        setAngle(getAngle() + 360);}/// <summary>/// Checks if ball is inbounds./// </summary>void Ball::checkIfBallIsLive(){    sf::RenderWindow& window = ServiceLocator::getWindow();    if (getPosition().y - (mSprite.getLocalBounds().height / 2.f) > window.getSize().y)    {        mLives--;        if (mLives > 0)            resetPosition();        else            markForDestroyed();    }}sf::FloatRect Ball::getBoundingRect() const{    return getTransform().transformRect(mSprite.getGlobalBounds());}Brick.h/// <summary>/// Represents a Brick object./// </summary>class Brick :    public Entity{public:    enum Type    {        None,        Green,        Blue,        Purple,        Red,        Count    };                            Brick(Brick::Type type, float x, float y);    unsigned int            getType() const;    sf::FloatRect           getBoundingRect() const;    unsigned int            getHitpoints() const;    void                    setHitpoints(unsigned int hitpoints);    void                    damage(unsigned int damage);    bool                    isDestroyed() const;    void                    update(sf::Time dt);private:    void                    draw(sf::RenderTarget& target, sf::RenderStates states) const;private:    Brick::Type             mType;    sf::Sprite              mSprite;    unsigned int            mHitpoints;};Brick.cppnamespace{    // Setup brick table indexed by Brick::Type that provides: hitpoints and texture rect.    const std::vector<BrickInfo> Table = initializeBrickTable();}Brick::Brick(Brick::Type type, float positionX, float positionY): Entity(), mSprite(ServiceLocator::getTextureHolder().get(Textures::Bricks), Table[type].textureRect), mType(type), mHitpoints(Table[type].hitpoints){    setPosition(positionX, positionY);    centerOrigin(mSprite);    //updateTexture();}bool Brick::isDestroyed() const{    return mHitpoints <= 0;}void Brick::draw(sf::RenderTarget& target, sf::RenderStates states) const{    states.transform *= getTransform();    target.draw(mSprite, states);}void Brick::update(sf::Time dt){    Entity::update(dt);}sf::FloatRect Brick::getBoundingRect() const{    return getTransform().transformRect(mSprite.getGlobalBounds());}
Breakout-Clone built in SFML
c++;game;sfml