Skip to main content

Detecting Animation State Change in StateMachineBehavior

✍ Last Updated : September 20, 2022

🚪 Prequisite Knowledge (Optional)

Describe Initial Knowledge that reader need to know before reading this article

❓ Key Question / Problem / Issue

Unity doesn’t provide hook to detect when an Animation State is starting to transition

✅ Expected Output/Definition of Done

Find the possibilities to detect when an Animation State is starting to transition

🎁 Resulting Solution

Using OnStateUpdate

The first option is using StateMachineBehavior’s OnStateEnter and OnStateUpdate. This method works by checking any changes in current playing animationClip every state update

		public override void OnStateEnter(Animator animator, AnimatorStateInfo animatorStateInfo, int layerIndex)
    {
        if(animator.GetLayerWeight(layerIndex) == 0)
            return;
        for(int i = 0; i < animator.GetNextAnimatorClipInfo(layerIndex).Length; i++)
        {
            AnimationClip clip = animator.GetNextAnimatorClipInfo(layerIndex)[i].clip;
            currentClip = clip;
        }
    }

		public override void OnStateUpdate(Animator animator, AnimatorStateInfo animatorStateInfo, int layerIndex)
    {
        if(animator.GetLayerWeight(layerIndex) == 0)
            return;
        for(int i = 0; i < animator.GetNextAnimatorClipInfo(layerIndex).Length; i++)
        {
            AnimationClip clip = animator.GetNextAnimatorClipInfo(layerIndex)[i].clip;
						if(currentClip != clip)
            {
                currentClip = clip;
								//Animation Clip is changed
								//OnStateChanged();
            }
        }
    }

The drawback using this method is that it might be resource heavy since it need to check if the clip is changed every frame

Triggering from Other State’s OnEnterState

The second option is to trigger the OnStateChange from other’s state OnEnterState.

In the Animation State that’s going to trigger the StateChange we use OnStateEnter to trigger

public class StateChangeTrigger : StateMachineBehaviour
{
    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.OnStateEnter(animator, stateInfo, layerIndex);
        if (animator.GetLayerWeight(layerIndex) > 0)
        {
            AnimationEventRelay animationEventRelay;
            if(animator.TryGetComponent<AnimationEventRelay>(out animationEventRelay) == false)
            {
                animator.gameObject.AddComponent<AnimationEventRelay>();
                animationEventRelay = animator.GetComponent<AnimationEventRelay>();
            }
            animationEventRelay.OnStateChanged(animator, stateInfo, layerIndex);
        }
    }
}

The AnimationEventRelay class is only to invoke the event received from the StateChangeTrigger

public class AnimationEventRelay : MonoBehaviour
{
    [HideInInspector] public UnityEvent<Animator, AnimatorStateInfo, int> onStateChange;
    Animator animator;
    // Start is called before the first frame update
    void Start()
    {
        animator = GetComponent<Animator>();
    }

    public void OnStateChanged(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        onStateChange?.Invoke(animator, stateInfo, layerIndex);
    }

}

Then on the receiving StateMachineBehavior

public override void OnStateEnter(Animator animator, AnimatorStateInfo animatorStateInfo, int layerIndex)
{
//Add event listener
		AnimationEventRelay animationEventRelay;
		if(animator.TryGetComponent<AnimationEventRelay>(out animationEventRelay) == false)
		{
				animator.gameObject.AddComponent<AnimationEventRelay>();
				animationEventRelay = animator.GetComponent<AnimationEventRelay>();
		}
		animationEventRelay.onStateChange.RemoveAllListeners();
		animationEventRelay.onStateChange.AddListener((animator, stateInfo, layerIndex) => OnStateChange(animator, stateInfo, layerIndex));
}

void OnStateChange(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
...
}

Example :

Untitled

In the example Animator above, to trigger Attack’s OnStateChange, we can trigger it when Hit, Idle or Block is entering their state. So the StateChangeTrigger State Machine Behavior need to be added to the mentioned Animator States.

Pros

  • Lower resource usage

Cons

  • Need to manually attach StateChangeTrigger to Animator States that will trigger the OnStateChange event
  • There’s no possible way to make the OnStateChange to target specific StateMachineBehavior yet. So the OnStateChange will be triggered for all StateMachineBehaviors who listened instead.