Game State Control

Started by
32 comments, last by Bob Janova 17 years, 7 months ago
I've never liked how I managed my game state. It worked, but I always felt constrained by the structure of it, and I've found things that were really difficult to do with it, that should be easy [like interacting with states that weren't the top state]. I'm sick of this system that I'd made, and am now looking for a new method of doing this, and am wondering how you've all gone about organizing game states.
Advertisement
It depends on what you mean by 'state'.

Do you mean states like menu levels, etc? In this case I just use the computer's stack by calling subroutines.

If you mean states like game modes, a good way would probably be to tape out a base GameRules class with methods like OnInit, OnPlayerDie, etc, and then inherit from it for each of your modes, then toss it to a RunGame method that will poke the right parts of the mode in the right sequence.

Whatever you do, always make sure your reset method is up to date -- I've had many, many state bugs that were due to re-entering the game after exiting to the main menu. [rolleyes]
the later is what I'm refering to, actual game states like the main menu, the splash screen, the game itself, ingame option menus ect.

The way I used to do it is with a stack, but the way it was set up made it very difficult to communicate between the states. I'm changing the way I'll deal with this issue in the future, though my current project is too far along to really change something this low-level. Trying to lay the brain-work down now so when it comes time to actually do this, I'll have it all figured out.

The way I am doing it now is just plain bad, and I've used this method on a few little stupid projects now [like a tetris clone, and a pong game, a small 2d scroller] and have never been very happy with it, and the horribleness of it is only really showing through now that I'm attempting to make a rather large project. This was a solution that I sorta patch-worked together to begin with, and never really got around to changing it because, at least at the time, it was 'good enough'.

So wondering how the big-brains of the game-forum did theirs, since obviously this is a more important piece of the puzzle then I originally gave it credit for being.
I have a stack-based system, and if I need to communicate among different states, I just have the state being pushed store a reference to the state on the top of the stack before it gets pushed.
I like to solve problems harder than the one in front of me. Paradoxically, it is often easier.

Over the course of a game, states spawn states. This too me looks like a tree. So instead of having a game state, you can have a game tree. Make certain that the tree is fully visible to all game states, and that the special positions of "yourself" "the root" and "the current state" are accessable to each game state node.

This places the logic and flow of your structure into data and not in code. Your code becomes a simple event loop which tosses messages at the game state tree. But it also means your game state can introspect and know it's own structure.

The disadvantage of placing logic and flow into data is that corrupted data behaves like corrupted program logic. :) Debugging can be a bitch. On the plus side, turning your code into an "interpreter" and "executer" of data is a huge force multiplier on what your code can do.
NotAYakk: are there any situations where the tree is not just a stack (i.e. can a game state ever have two children at once)? I can't think of one offhand. However your thinking is good :) ... event messages should go in at the top of the stack and percolate down until someone wants them, imho, thus you don't have to worry about 'finding the right state', the state will find the message.

My usual recommendation on threads like this is for two state stacks: one for the game itself (current level->cinematic, doesn't often go deeper than that) and one for menus (main menu->options menu->graphics options dialogue->advanced shader options dialogue). Thus interacting with the 'current game state' from a menu is really easy; this is what you need to do almost all the time when you think you need to go digging in a normal game state stack. For the unusual cases where you need to talk to states other than (a) the top game state; (b) the state you're in; (c) your parent or (d) your child, each state should be named and there should be a GameEngine.FindState(stateName) method.
Ya know, for the longest time, I was so gung ho about clever game state management. Stacks... carefully considered parameterization... multi-level pops... signals... I did it all.

Now? Now I use a handful of booleans and some if statements. I have a function to enter a state, and where necessary, a function to leave it. My rendering and update code has if..else statements and switch statements to handle what bushy inheritance hierarchies did before. When I notice code duplication, I factor it out into a CloseAllMenus or something, and move on.

See, I've come to the conclusion that good OO style in this area is extremely counterproductive. By spending time worrying about an elegant parameterization of how game state works, you end up pushing yourself conceptually away from what is really a very simple problem. Every minute you've spent worrying about whether a GameState's activate() should be re-called when a state above it is popped off could have been ten seconds of coding it how you want it in that particular situation, then fifty seconds of doing other useful stuff.
Quote:Original post by Bob Janova
NotAYakk: are there any situations where the tree is not just a stack (i.e. can a game state ever have two children at once)? I can't think of one offhand. However your thinking is good :) ... event messages should go in at the top of the stack and percolate down until someone wants them, imho, thus you don't have to worry about 'finding the right state', the state will find the message.


As an example of a set of game states that don't form a stack, one game I played recently (Space Rangers 2) had space and planet interfaces/states, but you could access the ship interface/state from either. Interestingly, you could advance time in both the space and planet interfaces, but not in the ship interface.

There's usually very little that can be said about states except that they can take and yield control. So really, you need to encapsulate the data of each state and provide a process for yielding control while retaining the data of the yielding state.

A very simple way of doing this is by giving each state its own class, which inherits from something with a method like "runme(State parent)". This way, you can yield to arbitrary states (even ones you haven't designed yet) by calling "otherstate.runme(this)", and although the stack is not determined wholly by the code (rather, it's determined by the user's actions), the stack will easily keep track of which state called which. If you need access to the parent's state (for example, the amount of money a PC has will need to be accessible from most states), you can add methods to the State class to do that, or you can add a single method to propagate messages up the state stack, with messages caught like exceptions.

If you don't add any methods to the State class except runme, you could accurately call your state class a continuation, and your state maintenance would then be in "continuation passing style" (from the "otherstate.runme(this)"). The only difference from a language-level continuation would be that maintaining the state would have to be done manually; you couldn't just use callcc to return to the current location of the code.
---New infokeeps brain running;must gas up!
I've recently created a state-machine for emulating a radar system. The state machine contains about 100 state and variable objects so far with more to come.

There are two types of state object, CStateAtom, CVariableAtom. Each state change is propagated throughout the whole state machine. The state object decides if it needs to react to the state that it has received.

CVariableAtom's can receive state messages; these change its minimum, default and maximum value. The CVariableAtom can also receive direct value changes - providing the value is within its current limits. The CVariableAtom cannot affect any other state objects. I use this to store variables that change as a consequence of the state machine.

CStateAtom stores a list of states it can change to. If the received state is on the list, the atom will revert to that state. If it changes state, there is an external effector list. This list holds 'cause' versus 'effect' states, or "If I change to X state, I shall transmit Y state BACK to the state machine". This way a CStateAtom can affect other states. The state object with the most external effector states is my STARTUP_DEFAULTS atom. You post a single startup message to the state machine, and the external effector list of this atom posts all of the startup states for all of the atoms.

CStateAtom also holds gui effects, toggle behavior enable and disable states.

Transmission of state to all objects is inefficient, I should probably arrange the state-machine in some sort of tree structure, something I may implement some day. Thus far however I haven’t experienced performance problems.

All of the behavior is defined and tested within an editor. The target software loads the XML file and manages the state from there.

When errors or omissions are found in the behavior, the XML state machine definition is edited and the behavior is fixed - all without a line of code being touched. This will be useful when the customer accepts delivery. This is already useful during development because sionce the state machine is set-up, additions are clicks within the editor rather than new lines of code.

The rest of the program reads the state machine by accessing a façade (an array), rather than accessing the objects directly. When the state object changes state/value, it amends the array element associated with it.

Thus far it’s working well and found to be quite flexible.

One thing I may add is CDecisionAtom. This will say "If I receive X state, and Y state is equal to Z, then transmit state Q to the rest of the state machine". This differs from the other atoms because it checks other states rather than just managing itself.

The other advantage to this, is that once you’ve set-up your framework, its flexible to be reused in all of your programs requiring a state-machine, just change the names of the atoms and possible states and create your state machine in the editor.

From my patchy description, would others be interested in trying this out when its ready for public consumption?
Quote:Original post by Sneftel
Ya know, for the longest time, I was so gung ho about clever game state management. Stacks... carefully considered parameterization... multi-level pops... signals... I did it all.

Now? Now I use a handful of booleans and some if statements. I have a function to enter a state, and where necessary, a function to leave it. My rendering and update code has if..else statements and switch statements to handle what bushy inheritance hierarchies did before. When I notice code duplication, I factor it out into a CloseAllMenus or something, and move on.

Ditto.
enum GAME_STATE {   GAME_RUNNING = 1,   GAME_PAUSED = 2,   GAME_OVER = 3,   GAME_MENU = 4,   /// etc};GAME_STATE g_state;

Gets the job done fine without the need to overengineer. Which leaves me free to overengineer in other areas. *shrug*

This topic is closed to new replies.

Advertisement