Skip to main content

Making Sure Animation Events Get Called When Animation Interrupted

✍ Last Updated : September 15, 2022

🚪 Prequisite Knowledge (Optional)

Using Unity Animator & State Machine Behaviour

❓ Key Question / Problem / Issue

In Unity Animator, when the animator is transitioning from one state to another, any animationEvents whose firing time is after the transition complete time, will not be fired. This is something that not always desirable.

Attack’s animation events in green will be called, but events in red will not

Attack’s animation events in green will be called, but events in red will not

✅ Expected Output/Definition of Done

There should be a way/option to make sure that certain events will always be called when transitioning, even if the firing time is outside the transition time.

🎁 Resulting Solution

To make sure that remaining event get fired when animator is transitioning to another state, one can utilize the StateMachineBehaviour OnStateEnter and OnStateExit.

The script needs to extend from StateMachineBehaviour, not MonoBehaviour

public class AlwaysInvokeEvent : StateMachineBehaviour

First, store all the animation clip events in a List on OnStateEnter hook. At this stage, we can also add a list of string to filter events that are not needed to be fired, here named skippableEvent

		public override void OnStateEnter(Animator animator, AnimatorStateInfo animatorStateInfo, int layerIndex)
    {
        if(animator.GetLayerWeight(layerIndex) == 0)
            return;

        eventsToInvoke.Clear();
        for(int i = 0; i < animator.GetNextAnimatorClipInfo(layerIndex).Length; i++)
        {
            AnimationClip clip = animator.GetNextAnimatorClipInfo(layerIndex)[i].clip;
            currentClip = clip;
            foreach (AnimationEvent animEvent in clip.events)
            {
                if(skippableEvents.IndexOf(animEvent.functionName) < 0)
                    eventsToInvoke.Add(animEvent);
            }
        }
    }

Then on OnStateExit hook, check all the animation events to invoke, comparing the event firing time with the exit time, which is animator normalized time x animator state length. If the event time is bigger than the exit time, then fire the event using SendMessage

		object GetParameter(AnimationEvent animationEvent)
    {
        object obj = null;
        if (animationEvent.floatParameter != 0) obj = animationEvent.floatParameter;
        if (animationEvent.intParameter != 0) obj = animationEvent.intParameter;
        if (string.IsNullOrEmpty(animationEvent.stringParameter) == false) obj = animationEvent.stringParameter;
        return obj;
    }

		public override void OnStateExit(Animator animator, AnimatorStateInfo animatorStateInfo, int layerIndex)
    {
        if(animator.GetLayerWeight(layerIndex) == 0)
            return;
        float exitTime = animatorStateInfo.normalizedTime * animatorStateInfo.length;
        foreach(AnimationEvent animEvent in eventsToInvoke)
        {
            if(animEvent.time > exitTime && animEvent.isFiredByAnimator == false)
                animator.SendMessage(animEvent.functionName, GetParameter(animEvent));
        }
		}

Do not forget to attach the script to the animationState that needed the behavior using Add Behaviour then assign the Skippable Events (if any)

Untitled