When normalizedTime actually gets 0 when you triggered Animation with SetTrigger, in coroutines?

This is simple question. Have you ever seen AnimatorStateInfo’s normalizedTime actually got 0 (or very small value close to zero)? Today I set up some state machine and transitions and wrote following code to make sure the range of normalizeTime.

   public IEnumerator DoAnimate()
   {
        animator.SetTrigger(nameOfAnimation);

        isAnimating = true;

       // wait for transition from current state to triggered state
        while(animator.IsInTransition(0))
        {
            var stateInfo = animator.GetNextAnimatorStateInfo(0);
            Debug.Log("DoAnimate: normalized time (in transition) = " + stateInfo.normalizedTime);
            yield return null;
        }

        yield return null;

        // triggered state is running
        while(!animator.IsInTransition(0))
        { 
            var stateInfo = animator.GetCurrentAnimatorStateInfo(0);
            Debug.Log("DoAnimate: normalized time = " + stateInfo.normalizedTime);
            yield return null;
        }

       // from triggered state to next state
        while(animator.IsInTransition(0))
        {
            var stateInfo = animator.GetCurrentAnimatorStateInfo(0);
            Debug.Log("DoAnimate: normalized time (in transition) = " + stateInfo.normalizedTime);
            yield return null;
        }
        
        isAnimating = false;
    }

    [ContextMenu("Test")]
    public void TestAnimation()
    {
        StartCoroutine(DoAnimate());
    }

As you see, I used SetTrigger to transit the states. It works fine and the transition occurred. I experimented with various transition duration and Has Exit Time on/off (but transition offset = 0 whole the time and triggered state itself has its Exit Time). But for some reason first block checking the transition never runs (the while loop waiting the transition from previous state to triggered state).

I’ve never realized this whole the time. So, I decided to ask how it supposed to work. According to my understanding, the code should have worked in this way;

  1. After the trigger is fired, the state machine start to change its state from current state to triggered state.
  2. So, animator.IsInTransition(0) should be true, if the transition duration (set on inspector) is more than 0. In this case, it was. So I expected normalizeTime to be zero or at least very small value right after the transition (but it never happened).
  3. Then, after the transition complete, animator.IsInTransition(0) will return false (it did). At this point, the value of normalizedTime was already somewhere around 0.15 to 0.3 or so.
  4. The next transition occurs when the Animation clip (played in triggered state) reaches its Exit Time (it was somewhere around 0.8 or so in my case). animator.IsInTransition(0) returns true, third while loop prints normalizedTime until it gets 0.99 or so.

I also inserted some codes to wait one or several frames after SetTrigger. But it doesn’t do anything either. Maybe it’s caused by my settings or bugs I put in my code…but my animations seem to be played from the beginning!

The question is;

  1. Am I doing something wrong? Or is this how state machine supposed to work?
  2. If so, Are the any documents about some details like this?
  3. Have you every seen normalizedTime == 0 in your project?

The point is, my code is working, somehow. But I really couldn’t get myself comfortable without knowing such a basics…I thought I kinda understood how state machine works but it seems like there’s more for me to understand.

By the way, I know maybe I should use StateBehavior scripts to track the state but I still want to know how it works under the food.

Thank you for reading this long post.

I did experimented a bit more and found StateMachineBehavior, OnStateEnter, OnStateUpdate returns normalizedTime from 0. It starts with (exact) 0 and gradually increased. So normalizedTime gets 0 if you attached StateMachineBehavior script.

But I finally couldn’t get normalizedTime = 0 outside of the state machine behavior. What is the common approach to track the state transition outside of the state machine?

And what is the formal behavior of animator.IsInTransition(0) right after the trigger?

Additional detail

I still think it may be the bug of my code but I simplified everything and tested again.

public IEnumerator DoAnimate()
    {
        animator.SetTrigger(nameOfAnimation);

        isAnimating = true;

        var timer = 5.0f;

        while(timer > 0)
        {
            var nextStateInfo = animator.GetNextAnimatorStateInfo(0);
            var stateInfo = animator.GetCurrentAnimatorStateInfo(0);
            var isInTransition = animator.IsInTransition(0);

            Debug.Log("next = " + nextStateInfo.normalizedTime + ", current = " + stateInfo.normalizedTime + " transition = " + animator.IsInTransition(0));

            timer -= Time.deltaTime;

            yield return null;            
        }

I’ve got following logs as outputs;

next = 0, current = 0.6624006 transition = False
OnStateEnter: 0
OnStateUpdate: 0.009795918
OnStateUpdate: 0.01959184
....
OnStateUpdate: 0.1567347
next = 0, current = 0.1567347 transition = False

So in my setup, it seems like something was blocking the coroutine until the state transition completed. I leave this here for someone who is having similar trouble but …umm, maybe I did something wrong in the other part of my codes.

It seems like that I found a correct solution for this;

if (useCrossFade)
        {
            animator.CrossFade(triggerName, crossFadeDuration);
        }
        else
        {
            animator.SetTrigger(triggerName);
        }

        // this is very important !!!
        yield return null;

        while(animator.IsInTransition(0))
        {
            var stateInfo = animator.GetNextAnimatorStateInfo(0);
            Debug.Log("normalizedTime = " + stateInfo.normalizedTime);
            if (stateInfo.normalizedTime > 1f) break;
            yield return null; 
        }
        
        while(!animator.IsInTransition(0))
        {            
            var stateInfo = animator.GetCurrentAnimatorStateInfo(0);
            Debug.Log("normalizedTime = " + stateInfo.normalizedTime);
            if (stateInfo.normalizedTime > 1f) break;
            yield return null;            
        }

        while(animator.IsInTransition(0))
        {
            var stateInfo = animator.GetCurrentAnimatorStateInfo(0);
            Debug.Log("normalizedTime = " + stateInfo.normalizedTime);
            if (stateInfo.normalizedTime > 1f) break;
            yield return null; 
        }

If you called “SetTrigger” in coroutine, just wait one frame before you start to access your animation state info. I don’t know why but if you don’t do that, the coroutine will be “blocked” for fraction of seconds (enough to give you 0.15 or 0.4 for your “normalizedTime” variable) and doesn’t give you normalizedTime == 0.0 moment.

This is wired but I think that how unity works.