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 :
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.
No Comments