How to return to the previous sub-state of a Hierarchical FSM? (2.0)
I'm already using/evaluating 2.0 WIP version, hence you can see ExitTriggerTransitions
I have a fairly simple game, which I can easily model as a multi-level Hierarchical FSM.
var menuFSM = new StateMachine(needsExitTime: true);
menuFSM.AddState("HOME_SCREEN");
menuFSM.AddState("SETTINGS");
menuFSM.AddState("LEVEL_SELECTION", levelSelectionState);
menuFSM.AddTriggerTransition("CHANGE_SETTINGS", "HOME_SCREEN", "SETTINGS");
menuFSM.AddTriggerTransition("SELECT_LEVEL", "HOME_SCREEN", "LEVEL_SELECTION");
menuFSM.AddTriggerTransition("BACK_TO_HOME", "LEVEL_SELECTION", "HOME_SCREEN");
menuFSM.AddTriggerTransition("BACK_TO_HOME", "SETTINGS", "HOME_SCREEN");
menuFSM.AddExitTriggerTransition("PLAY_LEVEL", "LEVEL_SELECTION");
var rollingFSM = new StateMachine(needsExitTime: true);
rollingFSM.AddState("RUNUP");
rollingFSM.AddState("RACING");
rollingFSM.AddTriggerTransition("STOPWATCH", "RUNUP", "RACING");
rollingFSM.AddExitTriggerTransitionFromAny("FALL", forceInstantly: true);
rollingFSM.AddExitTriggerTransition("FINISH", "RACING", forceInstantly: true);
var gameFSM = new StateMachine(needsExitTime: true);
gameFSM.AddState("INITIALIZATION", isGhostState: true);
gameFSM.AddState("SETUP", isGhostState: true);
gameFSM.AddState("PAUSED");
gameFSM.AddState("ROLLING", rollingFSM);
gameFSM.AddState("ENDED");
gameFSM.AddTransition("INITIALIZATION", "SETUP");
gameFSM.AddTransition("SETUP", "ROLLING");
gameFSM.AddTransition("ROLLING", "ENDED");
gameFSM.AddTriggerTransitionFromAny("RESTART", "SETUP", forceInstantly: true);
gameFSM.AddTriggerTransition("PAUSE", "ROLLING", "PAUSED", forceInstantly: true);
gameFSM.AddTriggerTransition("RESUME", "PAUSED", "ROLLING");
gameFSM.AddExitTriggerTransition("BACK_TO_MENU", "PAUSED", forceInstantly: true);
gameFSM.AddExitTriggerTransition("BACK_TO_MENU", "ENDED", forceInstantly: true);
var globalFSM = new StateMachine();
globalFSM.AddState("MENU", menuFSM);
globalFSM.AddTransition("MENU", "GAME");
globalFSM.AddState("GAME", gameFSM);
globalFSM.AddTransition("GAME", "MENU");
globalFSM.Init();
But there is one caveat, exiting sub-state completely resets it, and there is no way to enter back to a state it was previously in:
- "MENU" to "GAME": As can be seen, MENU can only be exited via PLAY_LEVEL trigger that can only be called from LEVEL_SELECTION.
- Going from "GAME" to "MENU", will always start on HOME_SCREEN, but I want to return back to state where
menuFSMwas before it exited: LEVEL_SELECTION.
- Going from "GAME" to "MENU", will always start on HOME_SCREEN, but I want to return back to state where
- "ROLLING" sub-machine: exiting ROLLING via PAUSE trigger and entering back with RESUME trigger, will always start at initial state RUNUP, even though I may have paused while RACING.
Any way to do it with existing functionality?
Hi @tomsseisums,
Thanks a lot for providing a real-world example with your issue. This really helps me understand the problem and also the way that UnityHFSM is being used.
This is a use-case I had not considered before, so there is no time-saving way to achieve this automatically. But it should be possible with the existing functionality.
What you want to do is to start in the last state that the state machine was in. An idea that comes to my mind is to simply set the start state when the state machine exits. There are two ways to achieve this (please keep in mind, that I have not tested the example code yet):
-
By adding a transition callback to the exit transition that then sets the start state. E.g.
menuFSM.AddExitTriggerTransition( "PLAY_LEVEL", from: "LEVEL_SELECTION", onTransition: t => menuFSM.SetStartState(menuFSM.ActiveStateName) ); -
By using the
HybridStateMachineclass to set the start state in itsbeforeOnExitcall. This is probably the most reusable solution. E.g.var menuFSM = new HybridStateMachine( beforeOnExit: fsm => fsm.SetStartState(fsm.ActiveStateName), needsExitTime: true );
In any case, I think that this is a useful feature that I want to add to UnityHFSM. As I'm quite close to releasing 2.0, I'm going to add it to the next release (2.1), probably implementing it directly in the StateMachine class with a parameter like rememberLastState that can be set in the constructor.
Let me know if this works.
Hi @tomsseismus,
I have now implemented the new "remember last state" feature in the StateMachine class. It is available in the most recent release (2.1).