Managing scenes: am I doing it wrong?

I'm working in Pygame, which doesn't manage scenes. This is something I've never really got the hang of. I've always gotten away with doing something really ad-hoc. Here's my plan for all the possible scenes in this game I'm working on (I call them contexts):


The way I initially had it, the main game loop would call the current scene, which would then either return self, meaning keep the scene the same, or a new scene object, which then became the current scene. This runs into a circular import problem, for instance, when WorldMap, Action, and GameOver are in different modules. Finally what I settled on is a global stack managed by the Scene base class that I can push onto and pop off of. Instead of the main loop keeping track of the current scene, it checks the stack and uses the topmost scene. GameOver doesn't have to import WorldMap anymore, since it can just pop the stack twice.

I since found out that that's how Cocos does it, so it sounds like a good idea to me. But are there any shortcomings of this approach? It seems like it would still lead to circular import problems in certain situatons, but I think my particular web avoids that problem, but only because of luck.

I'm mostly curious if there's just a better way to do things that I haven't thought of yet.

(log in to comment)

Comments

Keep in mind that not all cutscenes are going to shove you back to your world map (unless they are).  Consider retaining enough GameState info to jump back into the action after a cutscene.
Right, that's why the CutScene box (above Action) has arrows leading back both to Action and to WorldMap. The CutScene scene in this case gets put on top of the global stack above Action. If it wants to return to Action, it pops the stack once when it's done. If it wants to return to WorldMap, it pops twice. Since the Action scene isn't destructed until it's popped, it can resume just fine after a CutScene.

I like that CutScene doesn't have to hang on to a reference to the Action it's above (which I had to do in an earlier version), since it's stored in the global stack.
I do something very similar, but without the explicit scene stack to manage. I just pass the current Scene in the constructor to the new Scene when a scene is stackable. So the game engine itself must always be on the bottom of the stack, because it doesn't take a sub-scene argument; generally the only types that do are menus. A PauseScene will require a sub-scene argument, because it can't exist as the bottom of the stack.

One advantage of this over a central stack that the application manages is that the current scene has complete control of the scene below. There are assurances about the stacks that you will construct so there is less constraint on the Scenes to conform to a common API. PauseScene could tinker with the GameScene to whatever end you like (some visual effect, perhaps?), because it is never going to wrap a MapScene.

Circular dependencies are fairly easy to resolve in Python because you can import inside a function's scope if need be.
That's a really good idea, mauve. I like the sound of it. I may switch to your method, we'll see. Also a good call about resolving the circular dependencies. Thanks! :)
Yeah, cocos' scene management via the global director is really extremely convenient...

game menu -> level = director.push()
end of level = director.pop()
pause = director.push()
end pause = director.pop()
game level -> shop = director.push()
.. and another pop() :-)
game level -> next level = director.replace()

more complex I hear you say?

game menu (or level) -> cutscene -> level = director.replace(level), director.push(cutscene) .. play cutscene .. director.pop()
Also cocos has a built-in pause feature - just hit <accel>-P (ie. ⌘P on OS X, ^P on Windows / Linux)
Stacks rock.
What you are calling scenes* (or contexts) I usually call game states.  What I usually do is have a game state manager that is used for switching states and calling the current state's update and draw methods.  States are derived from a GameState base class and have a gain_focus and lose_focus method that the state can use for whatever reason it needs (for example, loading resources when gain_focus is called instead of at the object's creation.)  gain_focus and lose_focus can have the name of the previous/next state passed to them to facilitate interacting with other states (for example, for having the pause state call the previous state's draw method then drawing pause screen graphics on top of it.)

It's easy for a game state to switch to any other state without any complex tangled web of inter-dependencies.  In a game state's update method simply call:

    self.state_mgr.switch_state("new state")

Alternatively it can be designed to use a state stack:

    self.state_mgr.push_state("new state")   # Use new game state
    self.state_mgr.pop_state()  # Return to whatever state was previously used

This will have the state manager call lose_focus on the old state, switch the current state to the state found at the dictionary entry matching the given state name, then call gain_focus on that.  Now when the game loop calls the game state manager's update and draw methods the game state manager will be calling the methods of this new current state.


* To me a scene might have collections of entities, background layers, etc. and would be owned by a game state (most likely the play game state, for obvious reasons.)
That seems, for the most part, like what I'm doing, and what cocos does, although you've gotten around having global state by passing around a few more references.

But I have a question. Do you literally call switch_state with a string, or do you call it with a reference to the state that you're switching to? If it's the string, how does that work? If it's the state, then I think you run into the same interdependency issue that I mentioned. You'll wind up with circular imports at some point, right? (Although I like mauve's solution of putting the imports inside a method, so it's not really a problem, I just don't see how your framework is any different in this respect.)
I use a string.  The game state manager would have a dictionary containing the state objects.

class GameStateManager:
    def __init__(self):
        self.game_states = {}
        self.game_states["intro"] = GameStateIntro(self)
        self.game_states["play"] = GameStatePlay(self)
        self.game_states["pause"] = GameStatePause(self)

        self.current_state = self.game_states["intro"]

Alternatively you could use the objects directly instead of a dictionary (which might not be a bad idea):

class GameStateManager:
    def __init__(self):
        self.play_state = GameStatePlay(self)
        self.pause_state = GameStatePause(self)
        # etc.

Then the game states could switch states with:

    self.state_mgr.switch_state(self.state_mgr.pause_state)

Which seems a little more verbose.
As a side note about "circular imports."  Python, being a dynamic language, does not need modules explicitly imported if a reference has been passed to another module.  C++ would require the header files for classes to be included, but in Python the module would only need to be imported if a new object is being created or a function in the module is being used.

What I mean to say is, if you used:

    self.state_mgr.switch_state(self.state_mgr.pause_state)

Python would know what state_mgr is and that it has a pause_state even without importing the game state manager module.  In this example, state_mgr is a reference to the game state manager the game state belongs to.

The game state manager module would import the modules for the pause state, play state, etc. (to create these objects), but these game states would not need to import the game state manager's module.
Okay, so if you either initialize everything with a string, or pre-initialize it, how do you pass parameters that need to go to the state's initializer? For instance, the GameStateMap needs to somehow tell GameStatePlay that the player selected Level 6. The way I would think to do this is:

    self.state_mgr.switch_state(GameStatePlay(self.state_mgr, level=6))

But that doesn't work if you can only pass the string "play".
You can have *args and **kwargs as extra parameters, like
    self.state_mgr.switch_state("play_state", level=6)

with definition os switch_state like
def switch_state(self, state, *args, **kwargs):
    self.current_state = self.game_states[state]
    self.current_state.set_options(*args, **kwargs)

or if the state-dictionary just contain the classes like
     self.game_states["intro"] = IntroState
then do something like this
    self.current_state =  self.game_states[state](*args, **kwargs)
I wrote a lengthy description of why stacks are a huge oversimplification of game state, based on my experience, on Game Development Stack Exchange: http://gamedev.stackexchange.com/questions/1783/1785#1785

That being said, for a pyweek game, they are probably fine.
Thanks for the link, very interesting! Can you explain a little more how a set of states rather than a stack would work in practice? I'm not sure I understand. Do you put one item of each state into the set at startup? For instance, do you preload Ttile, WorldMap, Action, and Pause states, or do you only add them to the set when they're needed? And how do determine which state becomes the new active state when one state finishes? For instance, if the Pause state finishes, how do you know to go back to Action or WorldMap or whatever if they're not sequenced?
States are registered as a set of callback functions, e.g. on_enter, on_update, on_exit. Each frame the state manager runs the on_update callback for all states that are active. (You can track active states via a mapping of names/integers to callback functions); on_enter and on_exit are run when a state enters or leaves the active state list. There is no "loading" a state; there's just on_enter, which may request some resources, but the state manager is totally oblivious to it.

There's no "going back to" Action or WorldMap when Pause exits; they were on the entire time. Pause's on_enter e.g. sets a time scale to 0, and its on_exit sets it back to 1.
Er, whoops, I lost a sentence in there. It should be "You can track active states via a set or bitfield (and you can track all registered states via a mapping of names/integers to callback functions); ...".
Thanks, this is definitely very different from what I'm used to. You have states set global variables (like the time scale) that are flags to other states to act as though they're inactive, even though they're technically active. Seems like a roundabout way to do it, but I guess it would work.

So if you have more than one active state at a time, how do you choose which one draws to the screen and accepts input? Is this also done with global flags?
States receive arguments, they only rarely poke around global variables. For example, a pause state would set the global time scale to 0, but the other states don't need to know that - they just know their update functions are now getting dts of 0 (or if you're using a fixed timestep, they just don't get called).

They all draw to the screen; they all accept input. Those obviously need to be a stack, but that can be handled by doing whatever input / render pushes are necessary in on_enter. Likewise you just draw back-to-front, and a state that doesn't want something else showing can paint over it. The renderer and input system have stacks (even without a state system, these will have stacks out of some necessity); the state system does not.