2
\$\begingroup\$

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/breakoutsfml

I 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.cpp

StateStack::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 game
const 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.cpp

const 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.cpp

const 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.cpp

namespace
{
    // 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());
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.