UnityHFSM Documentation

repository·master·Indexed 23 days ago

https://github.com/inspiaaa/unityhfsm

A lightweight, efficient, and scalable hierarchical finite state machine (HFSM) library for Unity (version 2.3.0). It supports class-based architectures and lambda-based shortcuts, focusing on minimized GC allocations and temporal state transitions. Key features include nested state machines, CoState for Unity Coroutines, ghost states for multiple transitions per update, and flexible transition types including polling, event-based triggers, and global transitions.

Tokens
4.4K
Snippets
7
Records
19
Agent score
32%

What's inside UnityHFSM

  1. Use generics for state identifiers and events

    master

    UnityHFSM supports generics to allow using types other than string for state identifiers (TStateId) and events (TEvent). Using enum or int instead of string can improve type safety (preventing typos) and improve performance by up to 50% in internal mechanics.

    When building a hierarchy, every nested state machine can use its own TStateId type. However, all state machines in a given hierarchy must share the same TEvent type so that triggers can be passed down the hierarchy.

    enum PlayerStates {
        IDLE, MOVE, JUMP
    }
    
    enum MoveStates {
        WALK, DASH
    }
    
    enum Events {
        ON_DAMAGE, ON_WIN
    }
    
    // Root FSM: Uses PlayerStates for its own ID, Events for triggers
    var fsm = new StateMachine<PlayerStates, Events>();
    
    // Nested FSM: Uses PlayerStates as its parent ID, MoveStates for its own ID, and Events for triggers
    var moveFsm = new StateMachine<PlayerStates, MoveStates, Events>();
    
    // Adding states and transitions
    fsm.AddState(PlayerStates.IDLE, new State<PlayerStates, Events>());
    fsm.AddState(PlayerStates.MOVE, moveFsm);
    
    moveFsm.AddState(MoveStates.WALK);
    moveFsm.AddState(MoveStates.DASH);
    moveFsm.AddTransition(MoveStates.WALK, MoveStates.DASH);
  2. Use Two-Way Transitions for simplified logic

    master

    When you have a pattern where a transition occurs in one direction when a condition is true and in the opposite direction when it is false, use AddTwoWayTransition. This significantly reduces boilerplate compared to defining two separate Transition objects.

    Example:

    // Instead of two separate transitions:
    // fsm.AddTransition("A", "B", cond => true);
    // fsm.AddTransition("B", "A", cond => !true);
    
    // Use one two-way transition:
    fsm.AddTwoWayTransition("A", "B", condition => condition);
  3. Implement Hierarchical State Machines

    master

    UnityHFSM supports nesting state machines within other states because the StateMachine class inherits from StateBase. This allows you to break complex behaviors into sub-problems by creating a hierarchy of states.

    To implement a hierarchical state machine:

    1. Create a new StateMachine instance for the nested logic.
    2. Add the nested StateMachine to the parent StateMachine using AddState.
    3. Define states and transitions within the nested machine as usual.
    // This is the main state machine.
    fsm = new StateMachine();
    
    // This is the nested state machine.
    var extractIntel = new StateMachine();
    fsm.AddState("ExtractIntel", extractIntel);
  4. State Change Patterns in UnityHFSM

    master

    UnityHFSM provides three primary ways to manage state transitions, allowing you to mix polling-based, event-based, and direct logic approaches:

    1. Transition Objects (Polling): Define Transition objects that are checked automatically during every OnLogic call. This is useful for continuous condition checking.
    2. Direct State Change Requests: States can manually trigger a transition by calling RequestStateChange. This bypasses the need for a formal Transition object.
    3. Trigger Transitions (Event-based): These are transitions that only evaluate when a specific trigger name is activated via fsm.Trigger(triggerName). This is more efficient than polling for discrete events.

    Additionally, you can define Global Transitions (using AddTransitionFromAny or AddTriggerTransitionFromAny) which have the highest priority and can be triggered from any state in the machine.

  5. Control state exit timing with needsExitTime

    master

    The needsExitTime property determines how strictly a state must finish its logic before a transition can occur.

    • needsExitTime = false (Default): The state can exit immediately when a transition condition is met, regardless of its internal progress.
    • needsExitTime = true: The state machine will not exit the state immediately. Instead, the transition becomes "pending." The state machine waits until the active state explicitly signals it is ready to exit.

    To signal that a state with needsExitTime = true is ready to exit, call fsm.StateCanExit() within the state's logic or use the declarative canExit property.

    // Option 1: Manual exit signaling in onLogic
    extractIntel.AddState("SendData",
        onLogic: state => {
            if (state.timer.Elapsed > 5)
                state.fsm.StateCanExit();
        },
        needsExitTime: true
    );
    
    // Option 2: Declarative canExit property
    extractIntel.AddState("SendData",
        onLogic: state => RotateAtSpeed(100f),
        canExit: state => state.timer.Elapsed > 5,
        needsExitTime: true
    );
  6. Understand the OnLogic Control Flow and Priority

    master

    The OnLogic method of a StateMachine follows a strict execution order to ensure predictable behavior and prevent infinite loops. Only one transition can occur per OnLogic call (unless using Ghost States).

    Execution Order:

    1. Global Transitions: The FSM checks all transitions defined with AddTransitionFromAny. If one triggers, the FSM moves to the new state and immediately calls that new state's OnLogic.
    2. Direct Transitions: If no global transition triggers, the FSM checks transitions specific to the current active state. If one triggers, it moves to the new state and calls its OnLogic.
    3. Active State Logic: If no transitions trigger, the FSM executes the OnLogic function of the currently active state.

    Priority Rule: Global transitions always have the highest priority. This is ideal for critical states like Dead that should be reachable regardless of the current state.

    Ghost States: To perform multiple transitions in a single OnLogic call, mark a state as a "ghost state" using isGhostState: true. When the FSM enters a ghost state, it immediately evaluates its outbound transitions. If one succeeds, it transitions again instantly, effectively

  7. Install UnityHFSM via Git URL

    master

    You can add UnityHFSM directly from GitHub in Unity 2019.4+. Note that updates must be performed manually when using this method.

    1. Open the Package Manager (Window > Package Manager).
    2. Click the + button and select Add from Git URL.
    3. Paste one of the following URLs:
      • https://github.com/Inspiaaa/UnityHFSM.git#upm (Latest stable release - recommended)
      • https://github.com/Inspiaaa/UnityHFSM.git#release (Development version)
      • https://github.com/Inspiaaa/UnityHFSM.git#v1.8.0 (A specific version, e.g., v1.8.0)
    4. Click Add.

    Tip: If IntelliSense is not working in VSCode, regenerate project files via Edit > Preferences > External Tools > Regenerate project files.

  8. Debug state machine paths and hierarchy

    master

    When a hierarchical state machine is not behaving as expected, you can inspect the current active state path using these methods:

    1. From the root state machine: Use GetActiveHierarchyPath() to get a string representing the current path (e.g., /ExtractIntel/CollectData).
    2. From within a state: If you only have access to the local state machine, use StateMachineWalker.GetStringPathOfState(fsm) from the UnityHFSM.Inspection namespace to get the path (e.g., Root/Fight/Hit).
    3. Visual Debugging: Use the Animator Graph feature to create a Unity AnimatorController that visually represents the hierarchy and shows the active state in real-time.
  9. Create a Simple State Machine

    master

    A basic state machine in UnityHFSM involves creating a StateMachine instance, adding states (using either the State class or shortcut methods), adding transitions, initializing, and then calling OnLogic() in your update loop.

    Core Workflow

    1. Instantiate: fsm = new StateMachine();
    2. Add States: Use fsm.AddState(id, state) or shortcuts like fsm.AddState(id, onLogic: ...).
    3. Add Transitions: Use fsm.AddTransition(from, to, condition) or fsm.AddTwoWayTransition(id1, id2, condition).
    4. Initialize: fsm.SetStartState(id); followed by fsm.Init();.
    5. Update: Call fsm.OnLogic(); inside a Unity Update() method.
    using UnityEngine;
    using UnityHFSM;
    
    public class EnemyController : MonoBehaviour
    {
        private StateMachine fsm;
    
        void Start()
        {
            fsm = new StateMachine();
    
            // Adding states using shortcuts
            fsm.AddState("ExtractIntel");
            fsm.AddState("FollowPlayer", onLogic: state => MoveTowardsPlayer(1));
            fsm.AddState("FleeFromPlayer", onLogic: state => MoveTowardsPlayer(-1));
    
            fsm.SetStartState("FollowPlayer");
    
            // Adding two-way transitions using shortcuts
            fsm.AddTwoWayTransition("ExtractIntel", "FollowPlayer",
                transition => DistanceToPlayer > ownScanningRange);
            fsm.AddTwoWayTransition("ExtractIntel", "FleeFromPlayer",
                transition => DistanceToPlayer < playerScanningRange);
    
            fsm.Init();
        }
    
        void Update()
        {
            fsm.OnLogic();
        }
    }
  10. Use exit transitions for hierarchical state machines

    master

    When a nested state machine has needsExitTime = true, it will not exit immediately even if the parent state machine requests a transition. To allow the parent to force the nested machine to exit, you must use exit transitions.

    Exit transitions are special transitions that are only evaluated when the parent state machine has a pending transition. They allow the nested machine to transition to a state that allows it to exit cleanly.

    Key Rules:

    • Use AddExitTransition(stateName) to create a shortcut for an exit transition.
    • Precedence matters: Add exit transitions before normal transitions to ensure they are checked first. If an exit transition is checked first, it can prioritize exiting the hierarchy over moving to a different state within the same hierarchy.
  11. Install UnityHFSM via OpenUPM

    master

    To add UnityHFSM using the OpenUPM scoped registry, follow these steps:

    1. Open Edit/Project Settings/Package Manager.
    2. Add a new Scoped Registry with the following details:
      • Name: OpenUPM
      • URL: https://package.openupm.com/
      • Scope(s): com.inspiaaa.unityhfsm
    3. Click Save.
    4. Open the Package Manager window.
    5. Change the dropdown in the top left to My Registries.
    6. Select UnityHFSM and click Install.