inspiaaa / unityhfsm Goto Github PK
View Code? Open in Web Editor NEWA simple yet powerful class-based hierarchical finite state machine for Unity
License: MIT License
A simple yet powerful class-based hierarchical finite state machine for Unity
License: MIT License
Default name is Tests, can conflict with project, if you've another Test asmdef defined for your game before
I think it's must be, UnityHFSM.Tests
I have a TriggerTransitionFromAny and I've noticed that if I'm already in the target state, it's not working. I have code in the target state's OnEnter that I have to run each time the Transition fires.
Is it possible to have a trigger transition from the same state to itself?
I implement a simple chain of states like:
A => submachine state B => submachine state C => D
using UnityEngine;
using FSM;
public class Test : MonoBehaviour
{
StateMachine stateMachine = new StateMachine();
private class SimpleState : StateBase
{
public SimpleState(bool needsExitTime) : base(needsExitTime) { }
public override void OnEnter() { Debug.Log("Enter: " + name); }
public override void OnLogic()
{
if (Input.GetMouseButtonUp(0))
{
Debug.Log("Can exit: " + name);
fsm.StateCanExit();
}
}
}
void Start()
{
var submachine = new StateMachine(needsExitTime: true);
submachine.AddState("Submachine state B", new SimpleState(true));
submachine.AddState("Submachine state C", new SimpleState(true));
submachine.AddTransition("Submachine state B", "Submachine state C");
submachine.SetStartState("Submachine state B");
stateMachine.AddState("A", new SimpleState(true));
stateMachine.AddState("D", new SimpleState(true));
stateMachine.AddState("Submachine", submachine);
stateMachine.AddTransition("A", "Submachine");
stateMachine.AddTransition("Submachine", "D");
stateMachine.SetStartState("A");
stateMachine.Init();
}
void Update()
{
stateMachine.OnLogic();
}
}
What I expected: Changing state one by one every mouse click.
What I get:
Enter: A
Mouse click.
Can exit: A
Enter: Submachine state B
Mouse click.
Can exit: Submachine state B
Enter: Submachine state C
The problem is this: the state of the root state machine instantly changes to "D". This happens because StateCanExit always propagates StateCanExit to the root state machine without any mechanism defining this behavior.
Enter: D
Since this is something that should be done early to avoid having to rewrite a lot of code, I think it would be good to at least put a little hint higher up in the README that you're allowed to do this. I just saw this feature while looking for something more complex after already creating a bunch of states using strings. It's not a big deal to refactor, but I think it might save people time in the future if this was changed.
Haven't found a way to listen to any transition happening.
Currently I have found I would like something like that for debugging, as I'm working with hierarchical machines.
But I also feel I could make use for something like that to build UI around State. As to keep the game logic in states and respective transitions, but UI being reactive on top of it, no matter what and how deep is going on.
Is there a reason for not implementing StateBase as an interface so states can be used as protected types such as MonoBehaviour and ScriptableObject? It seems like the only concrete part of the class is the exitTime constructor, but that seems like something that doesn't need to be. Implementing classes can simply choose to provide constructors that override that internal value or not and the interface can have a property for it.
Hi~ Can we have more samples and explaination on Class-based architectures? Like:
Using Class-based architecture to rebuild the GuardAI Example.
Derived from StateBase/ActionState/State and their difference
How te create Custom Actions in such class instead of ".AddAction("OnFixedUpdate", () => { })"
Thank you!
Use an example from the wik.
enum PlayerStates {
MOVE, JUMP
}
enum MoveStates {
WALK, DASH
}
enum JumpStates {
JUMP,FALL
}
enum Events {
ON_DAMAGE, ON_WIN
}
var fsm = new StateMachine<PlayerStates, Events>();
var moveFsm = new StateMachine<PlayerStates, MoveStates, Events>();
var jumpFsm = new StateMachine<PlayerStates, JumpStates, Events>();
fsm.AddState(PlayerStates.MOVE, moveFsm);
fsm.AddState(PlayerStates.JUMP, jumpFsm);
moveFsm.AddState(MoveStates.WALK);
moveFsm.AddState(MoveStates.DASH);
moveFsm.AddTransition(new Transition<MoveStates>(MoveStates.WALK, MoveStates.DASH));
jumpFsm.AddState(JumpStates.JUMP);
jumpFsm.AddState(JumpStates.FALL);
jumpFsm.AddTransition(new Transition<JumpStates>(JumpStates.JUMP, JumpStates.FALL));
//It will get an error.
moveFsm.AddTransition(MoveStates.DASH,JumpStates.FALL);
I'm new to coding,I only know a compromise that fsm.AddTransition(PlayerStates.MOVE,PlayerStates.JUMP)
and then jumpFsm.SetStartState(JumpStates.FALL)
.
I have a simple scenario like:
var FSM = new StateMachine(true);
FSM.AddState("Idle", new State(onEnter: Idle, onLogic: Idle));
var movementFSM = new StateMachine(true);
FSM.AddState("Movement", movementFSM);
movementFSM.AddState("Walk", new State(onLogic: Walk));
movementFSM.AddState("Dodge", new State(onLogic: Dodging, canExit: _=> dodgeAnim.Animation.IsFinished));
FSM.AddTransition(new Transition("Idle", "Movement", _ => GetMovementInput.magnitude != 0, true));
FSM.AddTransition(new Transition("Movement", "Idle", _ => GetMovementInput.magnitude == 0 , false));
movementFSM.AddTransition(new Transition("Dodge", "Walk", _ => dashAnim.Animation.IsFinished, forceInstantly: false));
movementFSM.AddTransition(new Transition("Walk", "Dodge", _ => Globals.Input.Player.Dodge.WasPerformedThisFrame(), true));
Even though the "force instantly" is false for the Movement>Idle
transition, as well as the Dodge>Walk
, and there is a canExit
condition on my Dodge
state that isn't true until the animation is actually finished, it will still instantly leave my dodge animation if the Movement>Idle
transition condition is true.
This isn't making any sense to me and seems to defeat the whole purpose of a hierarchical state machine. What am I missing here or is this a bug?
Starting from Unity 2023.1, there is now an Awaitable class that makes it possible to use async/await functionality. It may be beneficial to add a sort of TaskState as an alternative to the CoState, as TaskStates could get around some of the limitations such as not being able to use lambdas,
Just reading through the 2.0 changelog! I don't grasp exactly what the HybridStateMachine is. It seems simply like an improved version of the normal StateMachine class? It would be greatly appreciated to see some documentation on the HybridStateMachine, explaining what's different about it and what its functionality is.
Given the following statemachine configuration:
private StateMachine _stateMachine;
void Start()
{
StateMachine a = new();
a.AddState("A");
a.AddState("B");
a.AddTriggerTransition("T", "A", "B");
a.AddTriggerTransition("T", "B", "A");
StateMachine b = new();
b.AddState("C");
b.AddState("D");
b.AddTriggerTransition("T", "C", "D");
b.AddTriggerTransition("T", "D", "C");
_stateMachine = new StateMachine();
_stateMachine.AddState("root", new ParallelStates(a, b));
_stateMachine.Init();
}
Using _statemachine.Trigger("T")
will not cause a and b to perform their transitions. This unfortunately makes ParallelStates useless for statemachines that rely heavily on trigger transitions.
This can be fixed by having ParallelStates implement ITriggereable. There is however a potential issue that should be addressed. Say that statemachine "a" has some nested statemachines and statemachine "b" has a transition whose guard checks ActiveStateName of one of those nested machines. An Exception will be thrown if the nested machine is not the active state of its parent machine.
I was hoping for a method that would output a string representing the nested state such as Patrol.Move.SearchForPOI
?
An ArgumentNullException
is thrown when using the AddTransitionFromAny(to, condition, forceInstantly)
shortcut extension method for FSMs where TStateId
is a string
type.
This occurs because the extension method passes default
through to CreateOptimizedTransition()
, where the default value for a string is null. I'm not sure there's a 'clean' way to fix this, other than adding string-specific extension method variants.
ArgumentNullException: Value cannot be null.
Parameter name: key
System.Collections.Generic.Dictionary`2[TKey,TValue].FindEntry (TKey key) (at <cdc4992cc04a4e77a24a09cd121af77b>:0)
System.Collections.Generic.Dictionary`2[TKey,TValue].TryGetValue (TKey key, TValue& value) (at <cdc4992cc04a4e77a24a09cd121af77b>:0)
FSM.StateMachine`3[TOwnId,TStateId,TEvent].GetOrCreateStateBundle (TStateId name) (at Assets/_Game/Scripts/Salvage.Core/Runtime/HFSM/StateMachine.cs:322)
FSM.StateMachine`3[TOwnId,TStateId,TEvent].AddTransition (FSM.TransitionBase`1[TStateId] transition) (at Assets/_Game/Scripts/Salvage.Core/Runtime/HFSM/StateMachine.cs:368)
FSM.StateMachineShortcuts.AddTransitionFromAny[TOwnId,TStateId,TEvent] (FSM.StateMachine`3[TOwnId,TStateId,TEvent] fsm, TStateId to, System.Func`2[T,TResult] condition, System.Boolean forceInstantly) (at Assets/_Game/Scripts/Salvage.Core/Runtime/HFSM/StateMachineShortcuts.cs:84)
Hello, I'm guessing that you don't have the time to work on this right now, but I'm wondering how difficult you think it would be to add.
I love creating state machines in code with HFSM, but it's difficult for other members of the team to jump in and make changes to long blocks of code. It would be really cool if HFSM could generate a document or Editor window (GraphView?) which documents the transitions between the various states.
Add OnTransition event to the Transition class, so that when you add a transition, and that transition happens, a specific function can be called.
Otherwise you have to do things like setting the function in OnEnter/OnExit and then check for the state or store separately which transition is being used. Not very easy to use.
Hi!
I've been using UnityHFSM for a bit and I really like it.
Now, I've run into a scenario where I'm not sure how to model it correctly using UnityHFSM. Let me set up the scenario for it a bit:
I develop a little bullethell game, like Vampire Survivor etc.,. I have a player who can move around using his own state machine.
I have a Weapon that works independently of the player, so the player does not know about his weapon and the weapon does not know about the player (only his transform is relevant).
The weapon, when idling will follow and circle around the player. The weapon has a firerate of 1 shoot per second (I'll call it cooldown). When it shoots the circling should stop and it should attack the enemy.
Now, I have some issues how to model the "idle when circling and cooling down". I have some possibilities to model this scenario:
Maybe I'm not seeing something that I could leverage in order to achieve this scenario.
Any idea will be appreciated. :-)
Is there any way to run code when a particular state transition occurs? I can't see any way to add a callback to the transition or to access the previous state in an onEnter
.
I see that the state machine is generic, but I would like to have my states identified by enums instead of strings. However, this does not work in a hierarchical setup if the child state machines have a different enum type (which is highly desirable).
Hey!
This repo is awesome. Helped massively for complex state machines in my UX flows. However, I've found myself repeatedly wishing that non-root state machines could trigger onEnter and onExit before they transition to the startState. Maybe they could even run their own onLogic.
Thoughts on this? I might volunteer to implement this if it sounds useful. :)
Are there any hard dependencies on Unity?
private StateMachine fsm;
Awake()
{
// Fails, because this is StateMachine<T> and StateMachine<T> is not derived from StateMachine
fsm = new HybridStateMachine();
}
or
private StateMachine fsm;
Awake()
{
var nestedFSM = new HybridStateMachine();
nestedFSM.AddState("INITIAL");
fsm = new StateMachine();
fsm.AddState("NESTED", nestedFSM);
}
Update()
{
switch (fsm.ActiveState) {
case StateMachine { name: "NESTED" } nestedFSM:
// do something...
// fails again, because HybridStateMachine isn't derived from StateMachine
break;
}
}
When a state calls fsm.StateCanExit
while in its OnEnter
method, the call seems to be ignored. The state will never exit. However, if the call is moved inside the OnLogic
method, everything works as expected.
Not quite sure if this is intended behavior or not, thus labelling this as an issue for now.
If this indeed is intended behavior, I'd recommend adding a warning to the README and to fsm.StateCanExit
XML docs about this.
Example script that showcases the behavior:
using UnityEngine;
using UnityHFSM;
namespace UnityHFSMTest
{
public class TestState : StateBase
{
public TestState() : base(needsExitTime:true) { }
public override void OnEnter()
{
Debug.Log("TestState.OnEnter");
// Calling "fsm.StateCanExit" in OnEnter does not allow the state to exit.
fsm.StateCanExit();
Debug.Log("TestState.OnEnter: StateCanExit called");
}
public override void OnLogic()
{
Debug.Log("TestState.OnLogic");
}
}
/// <summary>
/// This example demonstrates how the calling "StateCanExit" in OnEnter does not allow the state to exit.
/// </summary>
public class UnityHFSMStateCanExitTest : MonoBehaviour
{
private StateMachine _rootFsm;
private void Awake()
{
_rootFsm = new StateMachine();
// ----- States -----
_rootFsm.AddState("StateA",
onEnter: _ =>
{
print("StateA.OnEnter");
},
onLogic: state =>
{
if (state.timer.Elapsed > 1)
state.fsm.StateCanExit();
},
needsExitTime: true
);
_rootFsm.AddState("TestState", new TestState());
// ----- Transitions -----
// Alternate between "state A" and "TestState".
_rootFsm.AddTransition(new Transition("StateA", "TestState"));
_rootFsm.AddTransition(new Transition("TestState", "StateA"));
}
private void Start() => _rootFsm.Init();
private void Update() => _rootFsm.OnLogic();
}
}
The "TestState.OnLogic" debug line would NOT get printed.
If we move the fsm.StateCanExit()
call to the OnLogic
method, the state is exited as expected:
I found a lot of state machine-related projects, and found that this library is still the best, but there are no Issues, and there are too few stars. I should write more examples.
UnityHFSM Version: 2.1.0
When a state machine is nested inside another state machine, the StateMachine.StateChanged
event of the parent (root) machine will not be called for any state changes inside the nested machine.
Not quite sure if this is intended behavior or not, thus labelling this as an issue for now.
If this indeed is intended behavior, I'd recommend adding a note to the StateChanged event XML comment. Furthermore, a HierarchyChanged
event could be implemented to allow users to get feedback on state changes without polling.
My reasoning for this is that I don't want to keep polling the StateMachine.GetActiveHierarchyPath()
method each frame, since it generates garbage with the string concatenations.
Example script that showcases the behavior:
using UnityEngine;
using UnityHFSM;
namespace UnityHFSMTest
{
/// <summary>
/// This example demonstrates how the <see cref="StateMachine.StateChanged"/> event does NOT get triggered when a nested state machine changes its state.
/// </summary>
public class UnityHFSMStateChangedTest : MonoBehaviour
{
private StateMachine _rootFsm;
private void Awake()
{
_rootFsm = new StateMachine();
// ----- Root States -----
// State A: Normal state that waits for one second before transitioning to the next state.
_rootFsm.AddState("State A",
onEnter: _ =>
{
print("Enter state A");
},
onLogic: state =>
{
if (state.timer.Elapsed > 1)
state.fsm.StateCanExit();
},
needsExitTime: true
);
// State B: A state machine, that contains two states (X and Y).
StateMachine stateBFsm = new(needsExitTime: true);
stateBFsm.AddState("Nested X",
onEnter: _ =>
{
print("Enter state B-X");
},
onLogic: state =>
{
if (state.timer.Elapsed > 1)
state.fsm.StateCanExit();
},
needsExitTime: true
);
stateBFsm.AddState("Nested Y",
onEnter: _ =>
{
print("Enter state B-Y");
},
onLogic: state =>
{
if (state.timer.Elapsed > 1)
state.fsm.StateCanExit();
},
needsExitTime: true
);
// Add state B transitions. Nested Y is an exit transition.
stateBFsm.AddTransition(new Transition("Nested X", "Nested Y"));
stateBFsm.AddExitTransition(new Transition("Nested Y", ""));
// Add the state machine to the root FSM.
_rootFsm.AddState("State B", stateBFsm);
// ----- Root Transitions -----
// Alternate between "state A" and "state B".
_rootFsm.AddTransition(new Transition("State A", "State B"));
_rootFsm.AddTransition(new Transition("State B", "State A"));
}
private void OnEnable() => _rootFsm.StateChanged += OnStateChanged;
private void OnDisable() => _rootFsm.StateChanged -= OnStateChanged;
private void Start() => _rootFsm.Init();
private void Update() => _rootFsm.OnLogic();
private void OnStateChanged(StateBase<string> newState)
{
print($"StateChanged @: {newState.name}");
}
}
}
Expected output:
"StateChanged @ stateName" Debug.Log call for each state change.
Actual output:
Debug.Log call for only "State A" and "State B" (the highest-up states in the hierarchy):
I think it's more intuitional to use tool to create a fsm than to use code.
So is there any editor tool for UnityHFSM?
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:
menuFSM
was before it exited: LEVEL_SELECTION.Any way to do it with existing functionality?
I suggest to add an Active StateChanged event to StateMachine class to track the state.
I took a quick look at the code, and I think it would be possible by invoking the event at StateMachine's ChangeState method. if this function is ok, I am willing to do pull request.
simple example
fsm.OnActiveStateChanged += (activeState)=> Debug.Log(activeState);
I'm creating a state machine for an enemy which uses physics to move. I'm calling Rigidbody.AddForce() in FixedUpdate to move the enemy. How can I do this in UnityHFSM? I can only use OnLogic. I could put it in the FixedUpdate() method but that would run everything there, even those commands that I only need is regular Update().
Maybe I'm just missing something obvious here. If not, is it possible to have both an OnLogic and an OnFixedLogic (or something like that) so I can have separate Update and FixedUpdate code for states?
Dear maintainers,
while working through this repo, I came across the following questions / remarks:
Why do we have both fsm and parentFSM and then we set parentFSM to be fsm? I do not see the added value of parentFsm in IStateMachine
Say I have a class MyState
which has a property Priority
of type int
, and I want to only transition between two states (esepcially in TriggerTransitionFromAny
, TransitionFromAny
, etc, when fromState.priority <= toState.Priority
.
The way I am currently implementing this is in a derived MyTransition
class and a ShouldTransition
override.
Here we see a limitation of the expandability of the current base classes. IStateMachine
has no GetState
and no ActiveState
. This forces me to pass in the StateMachine
into the constructor of the transition.
Are there any recommended ideas to implement this?
This is essentially a special case when the transition needs to know data from the states to decide whether it should allow a state change.
A similar "limitation" is that the StateBase.CanExit()
does not know what state it was asked to exit to.
Hi~ Can we have more samples and explaination on Class-based architectures? Like:
Using Class-based architecture to rebuild the GuardAI Example.
Derived from StateBase/ActionState/State and their difference
How te create Custom Actions in such class instead of ".AddAction("OnFixedUpdate", () => { })"
Thank you!
One of the core features of a statechart as described here https://statecharts.dev/what-is-a-statechart.html, is the ability for a state to have two or more state machines running in parallel. Does the package support this? I couldn't quite see anything like that in the documentation.
How to visualize multi-layer state machines, such as Behavior Designer - 1.7.4
Bing translation
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.