0
\$\begingroup\$

Lately I've been using game state stack implementation from a book SFML Game Development and I've stumbled upon a problem with passing data between states. In this implementation State constructors are predefined and I can't really pass values between them. What's the better way to implement game states so I can for example pass player's score from gameplay state to gameover state?

Here is the game state header from the book:

#ifndef BOOK_STATESTACK_HPP
#define BOOK_STATESTACK_HPP

#include <Book/State.hpp>
#include <Book/StateIdentifiers.hpp>
#include <Book/ResourceIdentifiers.hpp>

#include <SFML/System/NonCopyable.hpp>
#include <SFML/System/Time.hpp>

#include <vector>
#include <utility>
#include <functional>
#include <map>


namespace sf
{
    class Event;
    class RenderWindow;
}

class StateStack : private sf::NonCopyable
{
    public:
        enum Action
        {
            Push,
            Pop,
            Clear,
        };


    public:     
        explicit            StateStack(State::Context context);

        template <typename T>
        void                registerState(States::ID stateID);
        template <typename T, typename Param1>
        void                registerState(States::ID stateID, Param1 arg1);

        void                update(sf::Time dt);
        void                draw();
        void                handleEvent(const sf::Event& event);

        void                pushState(States::ID stateID);
        void                popState();
        void                clearStates();
        

        bool                isEmpty() const;


    private:
        State::Ptr          createState(States::ID stateID);
        void                applyPendingChanges();


    private:
        struct PendingChange
        {
            explicit            PendingChange(Action action, States::ID stateID = States::None);

            Action              action;
            States::ID          stateID;
        };


    private:
        std::vector<State::Ptr>                             mStack;
        std::vector<PendingChange>                          mPendingList;

        State::Context                                      mContext;
        std::map<States::ID, std::function<State::Ptr()>>   mFactories;
};


template <typename T>
void StateStack::registerState(States::ID stateID)
{
    mFactories[stateID] = [this] ()
    {
        return State::Ptr(new T(*this, mContext));
    };
}

template <typename T, typename Param1>
void StateStack::registerState(States::ID stateID, Param1 arg1)
{
    mFactories[stateID] = [this, arg1] ()
    {
        return State::Ptr(new T(*this, mContext, arg1));
    };
}

#endif // BOOK_STATESTACK_HPP

And implementation:

#include <Book/StateStack.hpp>
#include <Book/Foreach.hpp>

#include <cassert>


StateStack::StateStack(State::Context context)
: mStack()
, mPendingList()
, mContext(context)
, mFactories()
{
}

void StateStack::update(sf::Time dt)
{
    // Iterate from top to bottom, stop as soon as update() returns false
    for (auto itr = mStack.rbegin(); itr != mStack.rend(); ++itr)
    {
        if (!(*itr)->update(dt))
            break;
    }

    applyPendingChanges();
}

void StateStack::draw()
{
    // Draw all active states from bottom to top
    FOREACH(State::Ptr& state, mStack)
        state->draw();
}

void StateStack::handleEvent(const sf::Event& event)
{
    // Iterate from top to bottom, stop as soon as handleEvent() returns false
    for (auto itr = mStack.rbegin(); itr != mStack.rend(); ++itr)
    {
        if (!(*itr)->handleEvent(event))
            break;
    }

    applyPendingChanges();
}

void StateStack::pushState(States::ID stateID)
{
    mPendingList.push_back(PendingChange(Push, stateID));
}

void StateStack::popState()
{
    mPendingList.push_back(PendingChange(Pop));
}

void StateStack::clearStates()
{
    mPendingList.push_back(PendingChange(Clear));
}

bool StateStack::isEmpty() const
{
    return mStack.empty();
}

State::Ptr StateStack::createState(States::ID stateID)
{
    auto found = mFactories.find(stateID);
    assert(found != mFactories.end());

    return found->second();
}

void StateStack::applyPendingChanges()
{
    FOREACH(PendingChange change, mPendingList)
    {
        switch (change.action)
        {
            case Push:
                mStack.push_back(createState(change.stateID));
                break;

            case Pop:
                mStack.back()->onDestroy();
                mStack.pop_back();

                if (!mStack.empty())
                    mStack.back()->onActivate();
                break;

            case Clear:
                FOREACH(State::Ptr& state, mStack)
                    state->onDestroy();

                mStack.clear();
                break;
        }
    }

    mPendingList.clear();
}

StateStack::PendingChange::PendingChange(Action action, States::ID stateID)
: action(action)
, stateID(stateID)
{
}
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

StateStack can route 1 parameter to the state's constructor. Notice how states are registered in Application::registerStates.

You could design a new GameState subclass that accepts a pointer to the shared data.

// Design a data container.
struct GameData { int HighScore; }

// Add a new member variable to the Application class.
GameData mGameData;

// Add a new member to the relevant state classes.
GameData *mGameData;

// Update state constructors.
GameState::GameState(StateStack& stack, Context context, GameData *gameData) {...}
GameOverState::GameOverState(StateStack& stack, Context context, GameData *gameData) {...}

// Update state registrations.
mStateStack.registerState<GameState>(States::Game, &mGameData);
mStateStack.registerState<GameOverState>(States::GameOver, &mGameData);
```
\$\endgroup\$
2
  • \$\begingroup\$ At this point, wouldn't it be more convenient to just make HighScore a global variable? \$\endgroup\$
    – wixy0
    Commented Apr 23, 2021 at 20:14
  • 1
    \$\begingroup\$ @wixy0 global mutable state is never convenient in the long term, the best practice is to avoid it (unless you're making a game for a toaster-tier hardware). Read more here: softwareengineering.stackexchange.com/q/148108/44030 \$\endgroup\$ Commented Apr 23, 2021 at 21:12

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .