[SOLVED] Unable to figure out what is going wrong with 2D sprite animation clip access in code

Hello and thanks to all in advance.

I am currently trying to have a 2D sprite of a spaceship play an animation when the player dies and then disable the object. I am trying to accomplish this within a coroutine. To do this, I need the length of the animation but the animation has the samples set to 15 frames a second in order to slow the playback. Therefore, I need to access the clip via the runtimeAnimatorController instead of just calling GetCurrentAnimatorClipInfo(0). I accomplish this with the following code:

private IEnumerator ExplodeRoutine()
{
    m_animator.Play("PlayerExplodingAnim");
    AnimationClip clip = null;
    AnimationClip[] clips = m_animator.runtimeAnimatorController.animationClips;
    AnimatorClipInfo[] clipInfo = m_animator.GetCurrentAnimatorClipInfo(0);
    for (int i = 0; i < clips.Length; i++)
    {
        if (clipInfo[0].clip.name == clips[i].name)
        {
            clip = clips[i];
            break;
        }
    }
    Debug.Assert(clip != null);
    if (clip != null)
    {
        yield return new WaitForSeconds(clip.length);
        gameObject.SetActive(false);
    }
}

Here is the state machine for the animator:

What’s going wrong is the value of clip is “PlaceholderIdleAnimation”. From my understanding, when Play is called from an animator without the layer it will attempt to go to a state of the same name. It actually works this way in other places in my project.

When debugging, the animation “PlayerExplodingAnim” does indeed play and shows in the state machine as being played, but the state remains on the idle animation.

The most obvious thing is am I setting it to the idle animation outside the coroutine? No, unfortunately. I’m not able to figure out what’s going wrong here.

Hi,

It seems that the problem is in internal animator state not being updated after m_animator.Play("PlayerExplodingAnim"); that’s why value of clip is “PlaceholderIdleAnimation” - clip being played at the momnet ExplodeRoutine is called.

I would propose getting clip directly by name instead:

private IEnumerator ExplodeRoutine()

{
    m_animator.Play("PlayerExplodingAnim");
    // Assuming that clip name is the same as animator state name and layer index is zero
    var clip = m_animator.GetAnimationClip(0, "PlayerExplodingAnim");
    Debug.Assert(clip != null);
    if (clip != null)
    {
        yield return new WaitForSeconds(clip.length);
        gameObject.SetActive(false);
    }
}

private static AnimationClip GetAnimationClip(this Animator animator, int layerIndex, string clipName)
{
    var clipsInfo = animator.GetCurrentAnimatorClipInfo(layerIndex);
    var index = Array.FindIndex(clipsInfo, x => x.clip.name == clipName);
    if (index == -1)
    {
        Debug.LogWarningFormat("Clip with name {0} not found in layer with index {1}", clipName, layerIndex);
        return null;
    }
    var clipInfo = clipsInfo[index];
    return clipInfo.clip;
}

Hope this helps.

Another approach on the problem you are solving here with coroutine would be using animation event at the end of “PlayerExplodingAnim” clip, see https://docs.unity3d.com/Manual/animeditor-AnimationEvents.html for more info.

Actually that does help. Although your code is something I’ve tried already, what you said about it not yet being set before the rest of the coroutine is most likely exactly what’s happening. It put me on to the following solution:

private IEnumerator ExplodeRoutine()
{
   m_animator.Play("PlayerExplodingAnim");

   AnimationClip clip = GetControllerClipFromName(m_animator, "PlayerExplodingAnim");
   WaitForSeconds defaultWait = new WaitForSeconds(clip.length);
   while (true)
   {
       AnimatorStateInfo info = m_animator.GetCurrentAnimatorStateInfo(0);
       if (info.IsName("PlayerExplodingAnim"))
       {
           if (info.length <= info.normalizedTime)
               break;
           else
               yield return new WaitForSeconds(info.length - info.normalizedTime);
       }
       else
           yield return defaultWait;
   }

   gameObject.SetActive(false);
}

public static AnimationClip GetControllerClipFromName(Animator anim, string name)
{
   AnimationClip[] clips = anim.runtimeAnimatorController.animationClips;
   for (int i = 0; i < clips.Length; i++)
   {
       if (clips[i].name.Equals(name))
           return clips[i];
   }
   return null;
}