Game State Management Suggestions/Review

Started by
5 comments, last by Kylotan 15 years, 9 months ago
Hi [smile] I've been having various doubts as to how to implement game states. Any and every article I read out there usually just abuses singletons (what if I have an in-game computer? [wink]). Currently, I have written a basic version that seems to be working better than my previous attempt. Yet, I'm still not sure if this is the way to go about it. I guess my root question here is: "how does this look to you, and how would you have done things differently?" states.hpp

#ifndef STATE_HPP_INCLUDED
#define STATE_HPP_INCLUDED

#include <map>
#include <deque>

#include <boost/shared_ptr.hpp>

#include "types.hpp"

namespace game
{

    class stateStack;
    class state
    {
        private:
            engine::string _name;

        public:

            const engine::string &name() const
            {
                return _name;
            }

            state(const engine::string &name):
                _name(name)
            {
            }

            virtual ~state()
            {
            }

            virtual void tick(engine::real dt, stateStack &stack) = 0;
            virtual void render() = 0;

            virtual void suspend() = 0; // still on stack, but not top (stop)
            virtual void resume() = 0; // on stack, now top (keep going)
            virtual void pushed() = 0; // pushed onto stack (load)
            virtual void popped() = 0; // popped off stack (unload)


    };

    typedef boost::shared_ptr<state> statePtr;

//    template<typename A, typename B>
//    boost::shared_ptr<A> stateCast(const boost::shared_ptr<B> &ptr)
//    {
//        return boost::static_pointer_cast<A>(ptr);
//    }

    typedef std::map<engine::string, statePtr> stateMap;
    typedef std::deque<statePtr> stateDeque;

    class stateStack
    {
        private:

            stateMap _states;
            stateDeque _stack;

        public:

            // register state
            void operator+=(const statePtr &ptr);
            // remove state
            statePtr operator-=(const engine::string &name);
//            // remove state
//            void operator-=(statePtr s);
//            // retreive state
//            statePtr operator[](const engine::string &name) const;

            // push a state onto the stack
            stateStack &operator<<(const engine::string &name);
            //stateStack operator<<(const statePtr &s);
            // pop a state off the stack
            stateStack &pop();

            bool tick(engine::real dt);
            void render();
    };

    typedef boost::shared_ptr<stateStack> stateStackPtr;

}



states.cpp


#include "log.hpp"

#include "state.hpp"

namespace game
{



    void stateStack::operator+=(const statePtr &ptr)
    {
        if (!ptr)
        {
            log("tried to register null state in stack");
            return;
        }

        stateMap::const_iterator i = _states.find(ptr->name());
        if (i != _states.end())
        {
            log("state \"" + ptr->name() + "\" already registered in stack");
            return;
        }

        _states[ptr->name()] = ptr;
    }

    statePtr stateStack::operator-=(const engine::string &name)
    {
        stateMap::const_iterator i = _states.find(name);
        if (i == _states.end())
        {
            log("could not unregister state \"" + name + "\", not found in stack");
            return statePtr();
        }
        return i->second;
    }

    stateStack &stateStack::operator<<(const engine::string &name)
    {
        stateMap::const_iterator i = _states.find(name);
        if (i == _states.end())
        {
            log("could not push state \"" + name + "\", not found in stack");
            return *this;
        }

        _stack.push_back(i->second);
        i->second->pushed(); // tell the state it got pushed

        return *this;
    }

    stateStack &stateStack::pop()
    {
        if (_stack.empty())
        {
            log("nothing to pop off stack");
            return *this;
        }
        statePtr ptr = _stack.back();
        _stack.pop_back();
        ptr->popped(); // tell the state it got popped
        return *this;
    }

    void stateStack::render()
    {
        if (_stack.empty())
        {
            log("no state on stack to render");
            return;
        }

        _stack.back()->render();
    }

    bool stateStack::tick(engine::real dt)
    {
        if (_stack.empty())
        {
            log("no state on stack to tick");
            return false;
        }

        _stack.back()->tick(dt, *this);

        return !_stack.empty();
    }


}



Then, I use it as so:

game::stateStack states;
// register logoState
states += statePtr(new logoState(_gui, _renderer->viewport()));
// push it onto the stack
states << "logoState";

game loop
{
    if (!states.tick(deltaTime))
        break;
    states.render();
}
I'm mainly looking for logic-related suggestions here, but code-related suggestions are appreciated, as well. (do you think I abuse operator overloading a bit?) My main idea was to be able to manipulate the state stack without knowing the implementation details of other states. For example, you can push "playState" without knowing about the actual instance of playState. Is this a good approach?
Advertisement
Looks ok. Separating out the ticking and rendering is good. Dodgy operator overloading is less good, but that's your problem. Use meaningful function names instead, so other people can see instantly what it means. You have probably typed more code out to define that stack push operator than you will save by using it.

Suggestion: Have your state rendering function return a bool and if it's true, render the next state in the stack as well. This lets you render menus on top of the game, etc. You don't always want to suspend a state just because it's not on top of the stack, although things might well be easier that way.

I also don't see exactly how your interface for adding and removing states will work - does each state know about the stack and about all other states it might wish to replace it?
Quote:Original post by Kylotan
Suggestion: Have your state rendering function return a bool and if it's true, render the next state in the stack as well. This lets you render menus on top of the game, etc. You don't always want to suspend a state just because it's not on top of the stack, although things might well be easier that way.

Hm, excellent idea about the rendering. Should any kind of updating be done as well? I'm not sure how I'd tell which states would need updating if it's not only the one on the top of the stack.
Quote:
I also don't see exactly how your interface for adding and removing states will work - does each state know about the stack and about all other states it might wish to replace it?

Well, this was the main part I wasn't entirely sure how to go about. What I ended up doing at first is to register the states with the stack (operator+=), and then actually push them onto the stack using their names (operator<<). So, basically:
stateStack states;states += statePtr(new playState(...));states += statePtr(new editorState(...));states += statePtr(new creditsState(...));states += statePtr(new mainMenuState(...));states << "mainMenu"; // use main menu firstvoid mainMenuState::tick(engine::real dt, stateStack &states){    switch (selection)    {        case MM_PLAY:            states << "play";        break;        case MM_EDITOR:            states << "editor";        break;        case MM_CREDITS:            states << "credits";        break;        default:            /* no state selected */        break;    }}

This way, the states that a run don't have to know about the actual instances of the other states, they only need to know the common name of what they want next. If such a state is no registered (for example, the credits state), then the stateStack prints something like this: state "credits" not registered with stack.

What do you think about this approach? Is there some other, better approach? Am I pretty much just abusing strings as a kind of "informal" singleton? But then again, each stateStack can have its own registered states, so it's not much of a singleton.

Thanks!
Could I suggest you something?

http://gamedevgeek.com/tutorials/managing-game-states-in-c/

This is a good article in how to handle different states in a game if you don't want to use the Quake-like style (a switch on an int that defines in which state you are).

I'm using it on my game-engine and it works very well, even if I recommend to make the Render() method a callback.
Using the method in this article ensure polymorphism and let you don't bother from outside on which state is currently running!

Hope it helps!
---------------------------------------http://badfoolprototype.blogspot.com/
Quote:Original post by agi_shi
Hm, excellent idea about the rendering. Should any kind of updating be done as well? I'm not sure how I'd tell which states would need updating if it's not only the one on the top of the stack.


I'd say yes. If a state is paused, it can set a pause flag before the next state is pushed on top, and choose not to do anything during its update. Normally you might want updates to continue as usual. eg. in a multiplayer FPS, if you bring up a menu, you might still want to see enemies running past in the background. But really it depends on your game - it might be easier just to only update the top state.

Quote:Well, this was the main part I wasn't entirely sure how to go about. What I ended up doing at first is to register the states with the stack (operator+=), and then actually push them onto the stack using their names


What part of the system does the look up between a name and the state object?

I think it would be better if you just ditched that, and created the state objects locally, pushing them directly on. This lets you parameterise it more easily, eg:
if (keypress==KEY_MAIN_MENU)    states.push(new MenuState("main_menu.xml"));if (keypress==KEY_OPTIONS_MENU)    states.push(new MenuState("options_menu.xml"));
Quote:This way, the states that a run don't have to know about the actual instances of the other states, they only need to know the common name of what they want next.


Given how few states you're likely to have, I don't think that's a benefit worth working for.
Quote:Original post by Kylotan
Quote:Original post by agi_shi
Hm, excellent idea about the rendering. Should any kind of updating be done as well? I'm not sure how I'd tell which states would need updating if it's not only the one on the top of the stack.


I'd say yes. If a state is paused, it can set a pause flag before the next state is pushed on top, and choose not to do anything during its update. Normally you might want updates to continue as usual. eg. in a multiplayer FPS, if you bring up a menu, you might still want to see enemies running past in the background. But really it depends on your game - it might be easier just to only update the top state.

That's another good point. So I can basically iterate through the stack, and any state that doesn't want to update/render if it's not on the top, can figure that out on its own. Besides, the rendering will be best done bottom to top.
Quote:
Quote:Well, this was the main part I wasn't entirely sure how to go about. What I ended up doing at first is to register the states with the stack (operator+=), and then actually push them onto the stack using their names


What part of the system does the look up between a name and the state object?

The states manager, stateStack. Take a look at my example above:
stateStack states;states += statePtr(new playState(...));states += statePtr(new editorState(...));states += statePtr(new creditsState(...));states += statePtr(new mainMenuState(...));

The stateStack is used to register various available states. They are stored in an std::map, where the key is their name (which is where the look-up comes from). Their name is chosen by themselves - meaning, the base state class needs to be provided a name, and the deriving states are the one to provide it:
mainMenuState::mainMenuState(...):    state("mainMenu"), // give constructor of state a name    ...{ ... }


So now all you need to do is provide a name:
states << "mainMenu";

Quote:
I think it would be better if you just ditched that, and created the state objects locally, pushing them directly on. This lets you parameterise it more easily, eg:
if (keypress==KEY_MAIN_MENU)    states.push(new MenuState("main_menu.xml"));if (keypress==KEY_OPTIONS_MENU)    states.push(new MenuState("options_menu.xml"));
Quote:This way, the states that a run don't have to know about the actual instances of the other states, they only need to know the common name of what they want next.


Given how few states you're likely to have, I don't think that's a benefit worth working for.

Hm, do you really think so? After failing to design a proper engine the first time, this time I'm really questioning things and thinking them over [smile]: should any given state know about the implementation details of any other states it wishes to push onto the stack? Or should the higher-level ower of the stateStack know about the state implementation details, only (such that it tells the stateStack what is available and what is not)? (this is the way I'm doing it right now)

With your particular example, the owner of the stateStack would push multiple menuStates, and give them different names. Which, then brings up another point: since my states name themselves, this would not be possible. Thus, I'd need to separate the naming from the actual state types (there wouldn't be a 1:1 correspondence). Which also means that the states shouldn't have any names to begin with, only the stateStack should contain names.

Edit:
So this is what I came up with (just as an example):
game::stateStack states;states += namedState("keyMenu", statePtr(new menuState("key_menu.xml")));states += namedState("graphicsMenu", statePtr(new menuState("gfx_menu.xml")));states += namedState("physicsMenu", statePtr(new menuState("phys_menu.xml")));states += namedState("mainMenu", statePtr(new menuState("root_menu.xml")));states << "mainMenu";// button callback, buttons are found in the XML configuration file// only buttons that should change the state are assigned this callbackvoid menuState::stateButtonClickCallback(gui::button *b){    stateChange = b->caption();}void menuState::tick(engine::real dt, stateStack &states){    if (!stateChange.empty())        states << stateChange;}


Thanks!

[Edited by - agi_shi on July 22, 2008 9:17:58 AM]
Quote:Original post by agi_shi
stateStack states;states += statePtr(new playState(...));states += statePtr(new editorState(...));states += statePtr(new creditsState(...));states += statePtr(new mainMenuState(...));

The stateStack is used to register various available states. They are stored in an std::map, where the key is their name (which is where the look-up comes from). Their name is chosen by themselves - meaning, the base state class needs to be provided a name, and the deriving states are the one to provide it:
mainMenuState::mainMenuState(...):    state("mainMenu"), // give constructor of state a name    ...{ ... }


Don't you think this is all just a load of extra code to achieve virtually nothing? Ok, so you no longer need to #include a file to add a state. Ok, a small benefit. But now you have this state registration step, you have to make each state store a name, etc. You also have to validate those names when you push a state, but what good is that? You can't legitimately recover at runtime from the absence of such a state anyway.

Quote:Hm, do you really think so? After failing to design a proper engine the first time, this time I'm really questioning things and thinking them over [smile]: should any given state know about the implementation details of any other states it wishes to push onto the stack? Or should the higher-level ower of the stateStack know about the state implementation details, only (such that it tells the stateStack what is available and what is not)? (this is the way I'm doing it right now)


Adding extra boilerplate code just to make your system fail at run-time rather than compile-time is not, in my opinion, making it 'proper'. I believe there's no problem with states knowing about each other, as long as that interface is kept as narrow as possible - ie. you can create one and push it on the stack, that's all. That won't be difficult to maintain.

Quote:With your particular example, the owner of the stateStack would push multiple menuStates, and give them different names.


No - firstly, it only pushes one state, depending on what it's transitioning to. Secondly, those are not names, they are data. A simple system has no need for names.

As for the rest of your code, I've already said why I don't like that system so I don't have much else to add. But I would certainly not put state names into GUI button captions. What if you want to change the text on the menu? You gonna rename the state too, in every place it's pushed onto the stack? Put an intermediate layer in there so that the GUI notifies the app of an intention and the actual implementation of that intention - ie. changing state - is held entirely separately from the GUI.

This topic is closed to new replies.

Advertisement