"stacked" game states?

Oct 28, 2010 at 6:56 PM
Edited Oct 28, 2010 at 6:58 PM

Is there a supported way to "stack" game states? For instance, I would like to push the gameplay state onto the stack, and then push the menu state onto the stack, and have the gameplay state continue updating and rendering in the background while the menu renders (and is active) in the foreground.

(See any Valve game for an example of what I'm talking about. In Team Fortress 2, for example, if you bring up the menu during gameplay, the game (obviously) doesn't pause for the rest of the server.)

Oct 30, 2010 at 10:33 AM

Game states can be stacked, but only the topmost state will get the Update() and Draw() calls.

In the design I envision, the menu is opened by the active state (because it is part of the state - for example, the 'save game' button has its callback in the running GameplayState). I'm generally not using the game state system for popups and such, but maybe there are valid cases where this makes sense?

- One problem when adding this feature would be with distributing input to the different states. The states stacked below the current would have to be notified that they're now in the background (scenario: game uses event-based input, thus has to disconnect the events or ignore them while the player is in the popup menu)

- Developers expecting the current behavior might just stack several game states (eg. my current game stacks MenuState -> GameplayState -> ConstructBuildingsState) and be unaware that the states below are still wasting time rendering themselves - or an unlucky mouse click might have the MenuState quit the game :)

How do you think this should be implemented?

- I could add an optional parameter to the Push() method (bool isPopup or something) that tells the game state manager that the state below should continue to be updated and drawn. Should the game state being put in the background still receive the Pause() and Resume() methods in this case?

- Another option would be to have pop-up game states implement a special interface or inherit from a special PopupGameState class. This would introduce more complexity to the system, however, and disallow using one game state both as a popup and standalone (but is there even a use case for that?)

Oct 30, 2010 at 2:08 PM
Edited Oct 30, 2010 at 2:09 PM

I think I understand your design a little better now. Instead of having a "MenuState" that I show at start up or show when the user hits the menu button during gameplay, I should instead implement some sort of MenuProvider that renders transparently on top of (and steals user input from) whatever Nuclex.Game.States.GameState is currently running.

You're right, having concurrently running GameStates would muddy the waters a little bit. Since the current design is very clean, I wouldn't want to implement a parameter (bool isPopup). I think instead I will explore adding another base class that derives from GameState, and allows multiple "GameSubStates" (or whatever) to be running.

Nov 6, 2010 at 7:48 PM
Edited Nov 6, 2010 at 7:52 PM

Well, clean is relative :)

When I wrote this 3 years ago (yikes, it's really been 3 years already!) I thought the tight coupling between GameStateManager and GameState was a good idea (it allowed me to use internal methods, thus only giving users access to the methods they were supposed to use), but with my current emphasis on unit testing and dependency injection, in hindsight, it seems to be a rather poor design decision. I've been waiting for an excuse to tweak this code a bit :)

The current SVN trunk contains some tweaks in the GameStateManager:

  • the GameState class no longer requires a reference back to the GameStateManager - if the state wants to switch to a different state, it can store the IGameStateService reference itself
  • stacked "pop-up" game states are for now possible. The default behavior still is to not update the obscured states, but the Push() and Switch() methods have an optional argument now that keeps the obscured state(s) below it receiving Update() and Draw() calls
  • split GameState in GameState and DrawableGameState, so GameStates can be used without the IGraphicsDeviceService now. I had some ideas how this could benefit my current game's MVC-like structure that I want to try out with this ;-)

I'll play around with the tweaks for a bit to see how useful they are. Pop-up game states do make sense in my mind if one doesn't want to use a full-blown GUI system or maybe "block" the game until the user has performed an action (like reinserting the memory card, reconnecting the controller or so).

Hope you don't mind the little compatibility break (old GameState's equivalent is DrawableGameState). If you have the time, I'd be happy about some feedback :)