7
\$\begingroup\$

I need a way to detect if animation's clip finished playing and then execute a certain code. Here's some script, that I wrote (called in Update):

    if (Input.GetButtonDown ("Fire1") && pc_atttacking == false && PlayerStats.staminaCurrent != 0) {
        pc_atttacking = true;
        pc_anim.SetBool("attack", true);
    }

    if (pc_anim.GetCurrentAnimatorStateInfo (0).IsName ("attack")) {
        pc_atttacking = false;
        pc_anim.SetBool("attack", false);
    }

But it doesn't work as I want; it executes second code too early - when "attack" animation is at slightly more than half of playing position. I want to execute it when "attack" animation is at last frame.

\$\endgroup\$
10
\$\begingroup\$

I usually prefer to use Animation Events for this, so that there's not a check hitting every Update when it's rarely actually needed. You could even make a generic alert system and pass in arguments to handle different animations ending or other events.

Example of Unity's Animation Events used to detect an Animation finishing

In this example, AlertObservers is in a component on the animated object.

    public void AlertObservers(string message)
    {
        if (message.Equals("AttackAnimationEnded"))
        {
            pc_attacking = false;
            pc_anim.SetBool("attack", false);
            // Do other things based on an attack ending.
        }
    }
| improve this answer | |
\$\endgroup\$
  • 2
    \$\begingroup\$ I like this approach, although I might add that using Enums is a nice choice as it avoids string comparison and also allows the use of a drop-down menu in the editor \$\endgroup\$ – PixelCake Games May 21 '18 at 23:57
  • \$\begingroup\$ An update, after using this approach for a while: Animation Events can still trigger during animation transitions (i.e. when cross-fading between animations), which can cause unwanted behavior if you aren't careful with the timing. \$\endgroup\$ – Nate Buck Jan 31 at 19:59
7
\$\begingroup\$

You can check if the animation is complete by looking at the normalizedTime property of the Animator's AnimatorStateInfo:

if(pc_anim.GetCurrentAnimatorStateInfo(0).IsName("attack") && 
   pc_anim.GetCurrentAnimatorStateInfo(0).normalizedTime >= 1.0f)
{
    pc_atttacking = false;
    pc_anim.SetBool("attack", false);
}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ You do not need the check against the normalizedTime from what I can tell. That is just putting a 1 second delay on whatever logic you want to run after the Animator is in that state. \$\endgroup\$ – Shadoninja Apr 23 '18 at 17:29
3
\$\begingroup\$

Well, on the other hand sometimes Coroutines would be just like a gift of God for you.

You can use it in more good way.

void Update()
{
    if (Input.GetButtonDown ("Fire1") && pc_atttacking == false && PlayerStats.staminaCurrent != 0) {
        pc_atttacking = true;
        pc_anim.SetBool("attack", true);
        StartCoroutine("OnCompleteAttackAnimation");
    }
}

IEnumerator OnCompleteAttackAnimation()
{
    while(pc_anim.GetCurrentAnimatorStateInfo(0).normalizedTime < 1.0f)
        yield return null;

    // TODO: Do something when animation did complete
}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Should work, but this is a bit smelly in my opinion. \$\endgroup\$ – Evorlor Feb 29 '16 at 13:58
  • 1
    \$\begingroup\$ @Evorlor I think it is better than adding more lines in Update \$\endgroup\$ – Hamza Hasan Feb 29 '16 at 13:59
  • \$\begingroup\$ If you are speaking from a performance point of view, I think it's the same. But not sure...now I'm curious. I do agree though that sticking it in the update method isn't nice either. I guess the smelliness is on Unity's end, unless someone comes up with a clean way to do this. \$\endgroup\$ – Evorlor Feb 29 '16 at 14:01
  • \$\begingroup\$ +1 for an alternative solution, but I hope others still can think of more. This is something that I have always struggled with in Unity \$\endgroup\$ – Evorlor Feb 29 '16 at 14:02
  • 1
    \$\begingroup\$ I suggest replacing your while loop with: yield return new WaitUntil(() => pc_anim.GetCurrentAnimatorStateInfo(0).normalizedTime < 1.0f); \$\endgroup\$ – Evorlor Feb 29 '16 at 14:12
2
\$\begingroup\$

Use StateMachineBehavior, there's OnStateExit

override public void OnStateExit (Animator animator, 
                                  AnimatorStateInfo stateInfo, 
                                  int layerIndex)
{
    if(stateInfo.IsName("attack")) return;
    pc_atttacking = false;
    pc_anim.SetBool("attack", false);
}

Create class (a new component) that inherit StateMachineBehaviour with above code, add the component to the gameobject that has the animation.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I could never really get this working on my loopless animation. Start would fire, but Exit would not. \$\endgroup\$ – Norbert Feb 5 at 8:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.