Skip to content

[Bug] Nested state machines don't invoke StateChanged event on the root state machine #49

@japsuu

Description

@japsuu

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions