gamestates : enums vs lists vs stacks

Started by
8 comments, last by mightypigeon 16 years, 5 months ago
Im designing a simple way to track game states. For example, the gamestate can only be one of the following values : mainmenu play pause gameover This screams 'use an enum' to me, so my solution would be to define an enum and declare a enum variable. enum possiblestates{mainmenu,play, pause, gameover} possiblestates currentstate = mainmenu; When something happens that changes the state, simply do : currentstate = play;//or whatever state When handling input / drawing etc..., have a switch statement select what code to run. (lists and stacks would work this way too, unless theres a better way, in which case Id love to hear about it) However, I read about state stacks, and being able to push and pop a state on or off a stack being a handy thing to do. I dont understand how that could always work. Going from pause to main menu for example. What if theres more than one instance of a particular state in the stack? And then theres lists. You dont have to access states in the order they were put on a stack. But isnt this overkill (and a performance hit) when enums do the same job just as well?
Advertisement
Keep it simple. Unless you need fancy (read as: crazy [smile]) state transitions, an enum will probably be quicker and easier than anything else.
Using an enum is going to cause problems when you want to add a new state: all those switch statements will need updating. Make states objects:
class IState{public:  virtual ~IState () {}  virtual IState *Update () = 0;  virtual void Render () = 0;};class MainMenuState : public IState{  // implementation  virtual IState *Update ()  {    IState      *next_state = this;    // do something    if (some_condition)    {      next_state = new PlayState ();    }    else    {      if (quitting)      {        next_state = 0;      }    }    return next_state;  }};class PlayState : public IState{  // implementation};

And then you can push them and pop them with a stack and so on. The main loop could be:
void MainLoop (){  IState    *current_state = new MainMenuState ();  while (current_state)  {    current_state->Render ();    current_state = current_state->Update ();  }}

The above is just a rough outline, you'd need a way to tidy up the states - at the moment the states will leak memory as they're not deleted.

Skizz
Rip-off

Yeah, I think for my purposes an enum will work, I can even make cool transition effects fairly easy. However Im still learning and trying to push my limits. I know Im there when I start getting pissed off at the compiler lol.

Skizz

Being able to call an states Render() and Update() automatically is extremely appealing. Hadnt thought of that. Must try this.
A stack would be useful if you have nested menus/states, and you want to be able to backtrack.

For example, let's say there was a Playing state, and a Pause state, and within the pause menu there was Set Options, and then a Change Volume menu.

When the user goes from Playing to Pause, we put the 'Playing' state on the stack so that we can get back to it later. Then if they go all the way down to Change Volume, the stack looks like:

[Set Options] <-- top
[Pause]
[Playing]

So when the user finishes with the Change Volume menu, we pop the stack and use the popped state as the new state. So now they're in Set Options. If they exit again, pop, and they're in Pause. Etc.

Stacks only make sense for states that are nested. You shouldn't ever get in a situation where the same state is on the stack twice. You could instead just have a list of "previous states" if you want to be able to backtrack at any time (like a browser's back button).

No, lists wouldn't be a performance hit. How many times per second is the user going to change states? One maybe? Don't worry about performance unless a piece of code is going to be executed at least 10,000 times a second.
I use a stack of polymorphic 'game state' objects to facilitate nested states such as menus, as described above. This also allows states that aren't at the top of the stack to continue running, to support e.g. the game pausing/dimming when the in-game menu is activated. (There was a recent thread on this topic in which it was pointed out that 'processes' may be a better term to use here, since strictly speaking only one 'state' should be active at once.)

I would think that enums would become unwieldy pretty quickly for a project of any complexity. There's also 'run and return successor', but for my own project I wasn't quite able to figure out how to make that work with the concurrent processes and whatnot. (However, in other ways I think RARS would have been a better solution, since some states - such as a 'high score' screen - don't fit as naturally into the stack scheme as others.)

[Edit: I seem to remember some of the more design-savvy members of this forum advising against the use of game state stacks, but I can't remember what the reasoning was...]
With polymorphic state objects, maintaining an explicit stack seems rather cumbersome. Instead, you could simply pass previous states to the new state constructors. For example with nested menu states, you pass the previous menu state to the child, which the child can transition to after its demise. Similarly, whether states need to be updated when a new state takes over is usually a decision that is determined by the new state, not the old state, so passing that decision off to the new state rather than the code that calls the state members is more natural.
What about creating a state machine without using a stack?

You create each state on initialization and set each transition according to different events. Then, after setting an initial state, you run the main loop. Each state will maintain all of its properties until the machine sets it to be running again, which works just like the stack in the case of after popping one of the states and getting back to how everything was before you had pushed that one. To make the state recreate itself when it is set to be running is easy and you can also pass stuff on events, by using a polymorphic event entity.

struct GameEvent{    virtual ~GameEvent() { }};struct GameState{    virtual ~GameState() { }    virtual void run() = 0;    virtual GameEvent* event() = 0;};class GameStateMachine{  public:    // some methods for inserting events, transitions and setting initial    void run()    {        while(true) {            m_current->run();            GameState *next = m_current->event();            // check if that transition is registred... else, next = 0            if(!next) {                break;            }        }    }      private:    GameState *m_current;};


Or something like that... Is it a valid idea?
I pretty much favor that GameStateMachine idea, but I'm having some doubts now, as other people have said that adding/removing states on a stack would create too much allocation/deallocation of memory unless all the info is created from the start.

Also, if you had MainMenu->Options->Game, to go from game back to main menu, you have to pop the stack twice from the Game state to get back. Now if you add an extra state between MainMenu to Game, you now need to remember to change the code in Game state to pop the stack three times. Someone said that this can be a potential maintenance nightmare when adding a lot of states in the mix.

Yet others suggest a "sub-state" system where one state can have other states running concurrently. I don't think the answers will ever end.
Electronic Meteor - My experiences with XNA and game development
An old game of mine was based around a client/server networking model. So to load a map you had to start a server. So if you weren't connected, then I would show the main menu. If you were, then the client and server loops would take care of themselves. It was simple, and it worked well.

I guess it all depends on the complexity of your game. I have implemented a stack-based state manager before. I didn't really use the push/pop functionality much.

I wouldn't use the game state manager for a menu. Your UI stuff should look after itself.
[size="1"]

This topic is closed to new replies.

Advertisement