Game state management (C++)

Started by
3 comments, last by Norman Barrows 10 years, 8 months ago

Hi guys,

My gamestate management uses classes (gamestates) with a init(), run() and close() function,

When Changestate(state) gets run, it closes the old state, inits the new one and runs it.

Here is my problem, whenever I use Changestate() in my current state, e.g. someone presses the "Game Menu" button

so Changestate(gamemenu) is called, it still has to finish the old state. For instance take this example:

//some gamestate
remove_old_gamestate()
 
while(running)
{
update();
draw();
}
 
update()
{
if(button.pressed())
{
ChangeState(menu);
}
}

The Changestate() makes a copy of the old gamestate (which will be removed using remove_old_gamestate() later, and inits the new one.

This is not very efficient as in this period the game needs about double the memory (old one is not closed down yet, new one is already initialized). However it is nessacary, because using "currentstate->close(); delete currentstate; currentstate = newstate" does not work, as soon as the ChangeState() function is over, the call stack goes back to where it was called: the old gamestate, and the draw function still has to be called, in a state already closed/deleted. I dont understand how I can overcome this problem without using gotos (which I prefer not to use).

Anyone has an idea?

Advertisement

Hard to tell because you provided only partial pseudo-code for your solution. The usual way (I think) it is done is - you don't instantly change the state (especially not as a responsibility of state class itself), but you ask to change the state on next update. Then finite state machine gets updated and it processes to next state, cleaning up old one. It does not require holding any copy or anything like that. Its just: finish old state, prepare new state, switch current state to new state.


Where are we and when are we and who are we?
How many people in how many places at how many times?

Yeah you are right, guess I'll have to make a couple of small changes to my code.

I want it to work as easy as possible, implementing a new state at this moment only requires me to have new code files for the state, but guess ill just need to implement

some kind of enumeration somewhere that has all possible states.

One option is to change the way you change states. Rather than change the state inside the state code signal the calling code that you want the state to change. Once method is to use the run and return successor pattern: state functions return a (smart) pointer to the next state and the calling function uses that pointer to set the state. Some sample code of how you might use that is in this older thread.

a game state management system can sometimes take modules that are at different levels and place them at the same level, which can lead to complications.

init_program, run_program, and shutdown_program are all top level modules.

game startup menus are a module called by run_program. they in turn call run_game. so the startup menus or "shell" is the second level of modules.

init_game, run_game , end_game is the next level of modules.

the in_game menu is a module called by run_game. its at the next level down.

by making the in_game menu or game startup menu just another state along with init_game, run_game, end_game, you "force" them to be at the same level, which may require extra communication which is normally handled implicitly by the call hierarchy.

note that its very common to have a game loop that looks like this:

while (! gameover)

process_input

if (gameover) return

update_all

if (gameover) return

render all

the sad fact is that there are a number of places you must break out of a game loop for a number of reasons. such as the player pressing the hotkey for the ingame menu, or the game ending due to any of a variety of reasons (mission goals achieved, mission goals failed, all bad guys dead, player dead, etc).

so there will be a number of places where something will occur and you'll want to break out of the game loop and call a sub-module such as an ingame menu, or return control to the calling module (the shell / wrapper / game startup menus).

about all you can do is set a flag when such things occur, and either finish the current module's processing (such as updating all) or return immediately. then after each call to a module where the flag can be set, you check to see if you should break out of the game loop.

one thing i note in this particular case is that your loop is of the form:

while ! quitgame

update

render

and you get input inside update...

update_each:

if (is_player) get_human_input else get_AI_input

or some such thing.

with a loop of the form:

while (! quitgame)

render_alll

process_input

update_all

you only need to check for gameover after getinput:

while (! quitgame)

renderalll

process_input

if (! gameover) update_all

you can also change the order to eliminate the check completely:

while (! quitgame)

update_all

render_all

process_input

but you would still need a check for gameover due to updates (such as player getting killed):

while (! quitgame)

{

update_all

if (! gameover)

{

render_all

process_input

}

}

i usually go with:

while (! gameover)

{

render_all

process_input

if (! gameover) update_all

}

note that by having an explicit process_input module in your loop, the in-game menu simply becomes a sub module called by process_input, instead of a state to be dealt with.

now, here's an example of when its a good idea to use "game states":

the game has 4 different types of environments / types of locations / points of view to draw: outside, in_cave, in_cavern, and up_a_tree.

each uses its own unique method of rendering the required scene, essentially 4 separate types of graphics engines.

as the player moves about the world, their "location" can change from outside to in_cave, etc. location is the "state" variable, with the 4 pre-defined state values of: outside, incave, incavern, and upatree.

when render_all is called, it checks the current state (the location) and calls the appropriate rendering engine.

each of the 4 rendering engines is a module at the same level, just below the render_all module. the render_all module NEEDS to be able to operate in one of 4 states. so its a good candidate for a "game state" approach. the key is that all the states are at the same level, so they are truly ONE thing (the renderer) operating in different states, as opposed to different things at different levels (the game loop and wrapper or in-game menus) being force to operate at the same level via state transitions.

what you're doing is replacing the flow control implicit in call hierarchy with explicit state transition flow control. but your problem is that in a game, you're not really dealing with a single state machine with multiple states like init, shutdown, startup_menu, ingame_menu, and rungame.

you're dealing with a hierarchy of state machines.

your highest level state machine has the states init_program, run_program, end program.

your next highest level state machine is run_program, with the states init_game, run_game and end_game.

your next highest level state machine is run_game with states render_all, process_input, and update_all.

your next highest level state machine is the one controlling the in-game menu, which is called from the process_input state as needed. think of the entire in-game menu as a "sub-state" of process_input that the game switches to as needed (IE when the player pulls up the in-game menus).

but as you can see, just from the previous sentence, trying to think of call hierarchies in terms of states at the same level can be confusing. that's because call hierarchies are tree type structures of modules, and state machines are more like peer to peer networks of modules.

peer to peer networks don't seem to me to lends themselves especially well to representing tree structures.

note that in a peerless network all the nodes (modules) are at the same "level". no client server hierarchy.

i think this may be a good test for when to use and not to use game states. are the modules that execute the states all inherently at the same level? if so, they may be a good candidate, if not, they may not be such a good idea.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

This topic is closed to new replies.

Advertisement