Passing information between states?

Started by
7 comments, last by SeanMiddleditch 9 years, 3 months ago

So I've been learning about gamestates lately.

Learning from here: http://gamedevgeek.com/tutorials/managing-game-states-in-c/

And I get the idea of how you can 'push' new states onto the stack and resume for later, but I'm concerned about passing relative information between them.

Say I have a 'menuState' showing available levels and the user clicks 'level1' starting a new state 'gameState'.

How would the gameState I just created know what level to load, and also how would it send data back once I returned to the menu to say "hey, he completed level 1, and can now play level 2"?

Advertisement
This is an area I'm still working through myself, so take this post as an inexperienced opinion.

Signals and slots seem to me the best way to pass information between parent-child hierarchies.
My GameStates can have children, and can rearrange those children. In an active state, only the topmost of its children is active with the parent.

I would have a GameState "PlayingGame" that created and owns both LevelMenu and WithinLevel.

Say I have a 'menuState' showing available levels and the user clicks 'level1' starting a new state 'gameState'.
How would the gameState I just created know what level to load,


Within 'PlayingGame':
LevelID/Handle/Path = this->LevelMenu.getSelectedLevel();
this->WithinLevel->loadLevel(levelID);

this->WithinLevel.RaiseToTop(); //Make 'WithinLevel' the active topmost child of PlayingGame.
(psuedocode)

and also how would it send data back once I returned to the menu to say "hey, he completed level 1, and can now play level 2"?


Within PlayingGame's constructor (right after creating WithinLevel and LevelMenu):
this->WithinLevel.FinishedLevelSignal.connectTo(this->goToNextLevelFunctionCallback);
this->WithinLevel.GameOverSignal.connectTo(this->gameOverFunctionCallback);
(psuedocode)

You can also pass into the constructors of gamestates pointers to the game resources and game data needed, so it can access those details.


I'd be interested to hear how other developers handle it, so I can improve my architecture.

Well, there's nothing that says you can't pass a template'd variable to the ChangeState() function, and that's the data the next state need. Or, you could just have a global object that tracks the important game-related data like Level number, but that's not always the best method.

class GameState
{
...
template<typename StateDateType>
void ChangeState(CGameState* state, StateDataType stateData);
};

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

From my own understanding of gamestates, the state manager doesn't know nor care what state(s) is/are currently active.
The menu itself is a gamestate and a gamestate can know what other gamestate the manager has to make active next.
So when the user selects Level 1 the menu gamestate tells the statemanager to swap in Level 1, when Level 1 completes it swaps Menu gamestate back in if you lose for example and Level 2 if you passed.
Swap in/out is meant very generally here as you could have a stack of states or just 1 active at a time. Think pop up menus or crossfading between states.

You use the CGameEngine to hold all data (like which level is next).

I thought this was pretty obvious from the article:


  virtual void HandleEvents(CGameEngine* game) = 0;
  virtual void Update(CGameEngine* game) = 0;
  virtual void Draw(CGameEngine* game) = 0;

These functions from CGameState specifically take a pointer to CGameEngine, so you can modify the current game data, and since there is going to be only one CGameEngine instance, it gets "shared" by all CGameState's, along with the data it holds - this is your global data for the game states. So in your example, you could simply add getter/setter functions and/or just a (public) member variable m_Level which holds the current level number to the CGameEngine class. Then, in the "menu" and "game" gamestate classes, you simply read&write to this variable. If you use getter/setter, you can also implement the setter so that it saves the last successfully played level to disk etc.

More likely, you will be changing the level (the CGameEngine::m_Level member variable) inside the CGameState::HandleEvents method, based on input from the user. HandleEvents is probably also where you call CGameEngine::ChangeState to set the next game state after the user selected a level from the menu, or after a game level was completed sucessfully (or not). You could also make all of the CGameState instances singletons, because in your example, you won't need more than one of each, and you probably won't have to re-create them every time the current state changes.

IMHO, the CGameState classes described in the article are more like helper classes. They aren't meant to hold actual game data, but to simplify management of different game states based on the game's actual current state, which you can store anywhere/anyway you want. This was a bit confusing to me at first when I started reading the article, because I always thought that the actual "game state" is the game data - i.e., the variables where you keep track of things, not the classes which handle that data. But that's OOP for you - always abstracting things that really matter. smile.png

I haven't read the whole article, but you could also implement CGameEngine to keep a list of multiple active states at the same time (even of the same type), but with only one of them drawing to the screen and accepting input (this is the "foreground" state). The ones not using the screen/input could simply be updated in the background. In this case, you could also keep the m_Level variable as a public member inside one of those background states, like the "menu" CGameState, which becomes a background state once the "playing" CGameState state is the foreground state. Then your "menu" CGameState is kept updated with the current level number while it is in the background, and when you bring it back to the foreground, it's already updated, and all you need to do is start calling it's Update() and Draw() methods. This is harder to implement though, and it shouldn't be needed in your simple menu example, but it also allows you to do cool stuff like having a PIP-view of one game state, while another one is in focus, or Fredericvo's example of crossfading between two game states. For this, the foreground CGameState's Update() and Draw() methods need to be able to call one of the background CGameState's Update() and Draw() methods, which means you either hold a pointer to that background CGameState inside the foreground CGameState, or you can use dynamic_cast to find the specific implementation of the background CGameState among the list of active background CGameState's from CGameEngine. Just be careful not to have too many game states active at the same time, which might slow down your game, or not to cause infinite recursion with the Update() and Draw() functions...

And I'm sure that there are many other ways to expand upon the example from the article...

It really depends on how complex your game is. Don't over-engineer.


Say I have a 'menuState' showing available levels and the user clicks 'level1' starting a new state 'gameState'.
How would the gameState I just created know what level to load...

Sounds like the simplest way is to create a PlayLevelState("level1").

... also how would it send data back once I returned to the menu to say "hey, he completed level 1, and can now play level 2"?

I have a "game context" object, which holds high level game data like this, perhaps in a "game levels" sub-object. When the player completes the level successfully, the state might run gameContext.gameLevels.unlockNext("level1"). Then, it will create a new LevelSelectState(), which will read the current levels from the game context.

The game context might be created / loaded at startup and passed to each state at construction, or alternatively passed to the Update() / HandleInput() routines.

Here's my quick answer: What I would do is overload the different GameState constructors with a custom set of parameters. That way every GameState has a specific set of data that it needs to work with. That is the simplest way I can think of.

Edit: rip-off has this suggestion as well.

New game in progress: Project SeedWorld

My development blog: Electronic Meteor

I tend to have a "game" object with accessors for getting information, e.g. GetLevel() or Volume(). I then make a pointer to this available in all states as i construct them. It contains basically global game data and some helper functions as well as some other bits and bobs.
I've seen this handled in three broadly different ways:

(1) "Global" data.

A bunch of state is held outside of the actual states, like the currently selected level. This leaks implementation details and adds coupling and dependencies in places it need not be.

(2) State change messages w/ data.

In this approach, you either can only change states via messages or you can pass an additional message payload along with the state change. The new state can then read this message in a fashion similar to (if not identical to) your existing message handling infrastructure. The message's data can include things like the level to load.

(3) States are objects that live independently of the state manager.

In this approach, you still have some means of accessing the state objects. You can access them even when not active. You could write code then that roughly says, "get a reference to the playing state, set its level to 'foo', wait until it says the level is loaded, and then tell the manager to transition to the playing state."


You can, of course, use a mixture of all three. Engineers who work on adding a single "pure" solution for a multitude of problems generally just cause more problems. Evaluate each individual state and each individual piece of data to determine what makes the most sense for that circumstance. For instance, if multiple states need to know the current level, you really need the first option. If you tend to give a different command to a state when you transition to it, the second option makes the most sense. The third option makes a lot of sense when you have infrequent needs to change state data, like setting an option for a state only when leaving the main menus.

Sean Middleditch – Game Systems Engineer – Join my team!

This topic is closed to new replies.

Advertisement