Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to return to the previous sub-state of a Hierarchical FSM? (2.0) #28

Closed
tomsseisums opened this issue Aug 31, 2023 · 2 comments
Closed

Comments

@tomsseisums
Copy link

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:

  1. "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 menuFSM was before it exited: LEVEL_SELECTION.
  2. "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?

@Inspiaaa
Copy link
Owner

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):

  1. 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)
    );
  2. By using the HybridStateMachine class to set the start state in its beforeOnExit call. 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.

Inspiaaa added a commit that referenced this issue Nov 14, 2023
This implements the new feature requested in #28.
@Inspiaaa
Copy link
Owner

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants