Preventing AnimationEvent using Animator IsInTransition flag
The theory in the alternative approach would be using AnimationEventHandler class which will handle all the events and then check if current clip is in transition or not. If it’s not in transition, then trigger corresponding event.
First on OnStateEnter, add all clips in the next animatorClipInfo into a list inside AnimationEventhandler
public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
base.OnStateEnter(animator, stateInfo, layerIndex);
if (animator.GetLayerWeight(layerIndex) == 0)
return;
if (animationEventHandler == null)
animationEventHandler = animator.GetComponent<AnimationEventHandler>();
for (int i = 0; i < animator.GetNextAnimatorClipInfo(layerIndex).Length; i++)
{
AnimationClip clip = animator.GetNextAnimatorClipInfo(layerIndex)[i].clip;
clips.Add(clip);
animationEventHandler.RegisterAsActiveClip(clip);
}
}
Then in the AnimationEventHandler class, instead of the class that is going to use the animation event, listening directly to Animator’s animationEvent, it needs to listen to this AnimationEventHandler’s events.
public class AnimationEventHandler : MonoBehaviour
{
[HideInInspector] public UnityEvent<object> onAttack;
[HideInInspector] public UnityEvent<object> onAttackCheck;
[HideInInspector] public UnityEvent<object> onAttackEnd;
Animator animator;
readonly List<AnimationClip> animationInTransitions = new List<AnimationClip>();
// Start is called before the first frame update
void Start()
{
animator = GetComponent<Animator>();
}
public void RegisterAsActiveClip(AnimationClip clip)
{
if(!activeClips.Exists(o => o == clip))
activeClips.Add(clip);
}
When receiving animationEvent, check if the current animation is in transition before firing/triggering the corresponding Events.
void Attack(object param)
{
if (isInTransition == false)
onAttack?.Invoke(param);
}
void Hit(object param)
{
onHit?.Invoke(param);
}
void AttackCheck(string param)
{
if (isInTransition == false)
onAttackCheck?.Invoke(param);
}
void AttackEnd(string param)
{
Debug.Log("Attack End");
if (isInTransition == false)
onAttackEnd?.Invoke(param);
}
If the animator is in transition and currentClip is inside the activeClips, then it’s in transition.
bool isInTransition
{
get
{
AnimationClip currentClip = null;
for (int i = 1; i < animator.layerCount; i++)
if (animator.GetLayerWeight(i) == 1)
{
if (animator.IsInTransition(i) == false)
return false;
currentClip = animator.GetCurrentAnimatorClipInfo(i)[0].clip;
}
if (activeClips.Exists(o => o == currentClip))
{
Debug.Log($"{currentClip.name} is IN TRANSITION");
return true;
}
return false;
}
}
Then on OnStateExit in the StateMachineBehaviour script, remove the clips from the activeClips list.
public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
base.OnStateExit(animator, stateInfo, layerIndex);
foreach(AnimationClip clip in clips)
animationEventHandler?.UnregisterFromActiveClip(clip);
clips.Clear();
}
public void UnregisterFromActiveClip(AnimationClip clip)
{
animationInTransitions.Remove(clip);
Debug.Log($"{clip.name} unregistered. Remaining clips in transition {activeClips.Count}");
}
Add AnimationEventHandler.cs class into the gameObject with Animator component, and assign the ActiveClipRegistration.cs (StateMachineBehavior) script into the AnimatorState that need to be checked.
Note
The Events corresponding to the animationEvents on the clip where the StateMachineBehaviour (ActiveClipRegistration.cs) is added, will not be called if the animator is in transition. In this example, AttackEnd event at the end of clip, will never be triggered when transitioning from Attack State to Idle State
To work around this, ensure that the State’s transition time is bigger than the animationEvent time, in the Attack → Idle transition inspector.
Full Scripts
Pros :
- Lower memory footprint
Cons :
- Need to add event handlers for each event that need to be checked if in transition or not.
No Comments