Passing data between states in an FSM

Started by
17 comments, last by Khatharr 7 years, 6 months ago

Hey!

Say you have a finite state machine where states are represented by C++ classes, all derived from a common flow state base class. The FSM is used to model the flow of the game and you need to somehow pass data from one state to another. For example, consider the main menu state where the user selects the level to load from a drop-down menu and presses the start button. At this point a transition is fired which means that you exit the main menu state and enter the loading state. Now, the loading state somehow needs to know which level was selected in the previous state.

How do you (or would you) handle this? I can come up with a few ways (all bad):

1: Make the states read from and write to some global data storage. This is can be broken in so many ways.

2: Add a name-value map to each transition to which the "from-state" writes to and the "to-state" reads from. This is quite flexible but leads to problems where you change parameters and on the sending side and forget to change them on the receiving side. In, short it's not type safe and error prone.

3: Have every state that wants to set values on the next state get a pointer to the next state right before executing the transition, and manually call methods on that state. This can be made type safe and efficient but it introduces dependencies between states that I would like to avoid.

Perhaps using templates one could make a version of number 2 that would be type safe, but that sounds awfully complicated for such a simple thing. Also note that transitions can be made pending, so you would need to allocate temp storage for all data until the transition is executed.

Ideas?

Advertisement

I dont think that each level here would need its own state, if you simplify this down abit, you can have an intro state - a menu state - a game state and a pause state.

The game state itself is what will actually handle the game side of these, including which level to load and render etc. I think having all these levels on seperate states like you can see adds a lot of bloat and complication to the design.

It could do something like the following

BaseState {

void Load();

void Update();

void Render();

};

GameState : BaseState::Load()

{

if( currentLevel == 0 )

// Load the levels settings

}

GameState : BaseState::Update()

{

this->currentLevel->Update();

}

GameState : BaseState::Render()

{

this->currentLevel->Render();

}

This is obviously a very basic example, maybe someone has more time to better explain this ( or tell me im completly wrong )

Have a look at this tutorial ( Great reference for a lot of things ) http://gameprogrammingpatterns.com/command.html Altough not about states per say - it will show methods of passing and using paramaters like you have mentioned.

Another example on this site are states http://gameprogrammingpatterns.com/state.html :)
Just read it all, its a very imforative website, and explains alot well and in a fun way

How do you (or would you) handle this?

simple, i don't use states for that.

a program module with init, run, and end methods is called from main():

void main

initprog (or prog.init for you OO types)

prog.run

prog.end

prog.run is the main menu for the game (continue, new, load, help, tools, quit to windows)

then there's a game module with init, run, and end methods.

game.run contains the main game loop (unless its a mission based game).

so newgame, continue, and load, all call game.init, game.run, and game.end, along with doing stuff to load a saved game or start a new game.

if its a mission based game, then there's also a mission module, with init, run, and end methods. game.run is the "between missions menu", and when you select "run next mission", mission.init, mission.run, and mission.end are called. and mission.run contains the main game loop.

an example of use of state machines for flow control in a game, as opposed to AI, would be "FPS mode" and "Action mode" in Caveman. the game is a FPSRPG / person sim hybrid. so it runs in two distinct modes (states): FPS mode (run around like in skyrim), and action mode (like making dinner or any other action in The SIMs, or chopping wood or mining ore in skyrim). so the game tracks which state each player-controlled character is in. each state has its own render, input, and update methods. so when its in FPS mode, its like playing skyrim. when you interact with an object, it switches to being like the sims, and shows an animation of you performing some action.

it seems that when a game can operate in more than one mode at a given point, some sort of "what mode am i in?" variable is called for. this is more or less seems to be the limit of the need for state for flow control in games. states aren't really required for linear control flow, IE initprog, main menu, newgame, init game, run game (main loop), end game, back to main menu. that's linear - one method call followed by another, always in the same order. state variables are needed when control flow is parallel:

in render:

!= player.action DONOTHING render(actionmode_screen) else render(FPSmode_screen)

in input:

!= player.action DONOTHING process_input(actionmode) else process_input(FPSmode)

in update:

!= player.action DONOTHING update(actionmode) else update(FPSmode)

in all three places,the flow of control of the code can go one of two ways, IE there are parallel code paths. a "game state variable" is a good way to chose the desired code path. but this is about the only "proper" use for a "game state variable" that i've ever found.

apparently at some point somebody someplace wrote a book on simple game development and used states to control init vs main menu vs rungame main loop, and so on. while it worked for the trivially simple games in question, it adds unnecessary complexity when the code is not parallel. IE when you simply transition from one state to the next, you don't need states, just a series of method calls. and when you transition to some state and then always back to the previous state, that's just a subroutine call. its only when you can transition from one state to two or more possible next states that a branch on a state variable is required. or when the software can run in more than one mode (state) at one or more specific places in the code.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

Say you have a finite state machine where states are represented by C++ classes

I would fix this problem rendering the other mute.

- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara

This isn't an entirely uncommon problem.

A fairly simple solution is to use messages to control state transitions. To enter the MainMenuState, you must send an EnterMainMenuMessage to the state controller. This message can contain whichever data the state might need. It can take a little work to make this type-safe but it's easy work if you're well-versed in C++.

Sean Middleditch – Game Systems Engineer – Join my team!

Say you have a finite state machine where states are represented by C++ classes

I would fix this problem rendering the other mute.

From a lack of experience, I also use states derived from an abstract class; how would you suggest to improving that?

1: Make the states read from and write to some global data storage. This is can be broken in so many ways.

2: Add a name-value map to each transition to which the "from-state" writes to and the "to-state" reads from. This is quite flexible but leads to problems where you change parameters and on the sending side and forget to change them on the receiving side. In, short it's not type safe and error prone.

3: Have every state that wants to set values on the next state get a pointer to the next state right before executing the transition, and manually call methods on that state. This can be made type safe and efficient but it introduces dependencies between states that I would like to avoid.

1) It doesn't need to be global, and you can have more than one. (More on this in a moment.)

2) FSM often have meaningful state transitions (moving from A to B triggers a specific set of actions), but for switching between game states this is not necessary.

3) You like-a spaghetti, eh?

If you have a Game object that encapsulates your top-level game loop then you can make one member of that class a SharedStore object, which holds persistent long-term data. If you explicitly divide that data into two groups (things that get saved and things that don't) then saving and loading the game becomes much easier. Additionally, depending on the type of game you may want to have one or more additional persistent objects hanging out, such as a MapData class that holds the data for the loaded map in a game where you can leave the map scene to access a menu or something. (This would be in a game like Final Fantasy where you may transition to the menu state or the battle state but you want to retain some of the information about where you are in the world.)

You can place the MapData object into the SharedStore and then just pass a SharedStore pointer into every scene as you create it.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

I asked a similar questions here

http://www.gamedev.net/topic/681957-transitioning-game-states-when-using-a-stack-of-states

and Kylotans excellent answer has been working well. I guess it is similar to your option 3.

maybe something like this . I wouldn't use something like state machines for level loading though. Atleast it doesn't make much sense to me, I usually use it for movement logic

or ai logic. Using a state machine adds spaghetti code, and when you later add features such as async level loading, this entire thing breaks.


struct SStateData
{
   enum Type
   {
     TYPE_INT,
     TYPE_FLOAT,
    ....
   };
   
  explicit SStateData(int Value):
   iVal(Value),
   m_Type(TYPE_INT)
{

}
  int AsInt()
  {
    assert(m_Type==TYPE_INT)
    return iVal;
  }

 ....
  

private:

  union 
  {
    int iVal;
    float fVal;
    void* ptrVal;
    char strValue[256];
  };

  Type m_Type;

};

class IState
{
  enum EStateID
  {
     STATE_MENU,
     STATE_GAME
  };

  //called when entering this state
  virtual void OnEnterState(const IState* PrevState, const SStateData& Data) = 0;
  //called when existing this state, fills pertinent data in "SStateData"
  virtual void OnExitState(const IState* NextState, SStateData& Data) = 0;
  virtual EStateID GetID() const = 0;


};

class CMainMenuState : public IState {

  virtual void OnExitState(const IState* NextState, SStateData& Data)
  {
   const char* LevelName = "David";
    //if the next state is level load, then set the level name 
    if(NextState->GetID() == STATE_LEVELLOADING)
    {
      Data.SetString(LevelName);
   }
  }

 }
Using a state machine adds spaghetti code

FSM generally reduces coupling? (At least compared to trying to cram all possibilities into one place.)

It's true that you don't really want a unique state for each level though. Levels should be data-driven and loaded by a single scene per level type (most games only have one level type).

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

This topic is closed to new replies.

Advertisement