Un-loop animation normalized time is higher than 1

Hello guys!

I’m facing some issue at the moment with my character controller. I made a finite state machine to separate each behaviours (idle, walk, attack …). When a condition is met, the character state changes, as well as the animation. The animations are set according to some booleans. When the character exit a state, the current state boolean is set to false and the next state boolean is set to true (which will trigger the next animation).

One of my animation is currently facing some issue (Default Attack), which is trigger when the player click on the left mouse button. When my default animation is done, I will like to return to the previous state. In order to do so, I checked if my normalized time of my animation is higher than 1. If that’s the case, I know that my animation is done.

8994433--1238632--upload_2023-5-5_12-40-59.png

If the player re-click on the left mouse button while he is on the previous state, the default attack animation will immediately transitions to the previous state because the normalized time of the default attack is higher than 1. It seems like the attack default animation is constantly playing (even if the LOOP checkbox is not checked). In order to reset the normalized time of the default attack animation, I have to go to the previous of the previous state and then come back to the attack animation.

For example, if my player is walking and then the player click on the left mouse button to do the default attack, I will have to go the previous state (walking) then I will have to go to Idle, then return to walking and re-click on the left mouse button top reset the normalized time of the default attack animation…

8994433--1238635--upload_2023-5-5_12-44-14.png

I simply don’t understand why the animation normalized time is not reset to 0 when the animation is done.

Same here. When hitting “attack” many times quickly before the attack anim ends, normalized time goes to the sky, not triggering “onAnimationAttackEnd” event, hence blocking the whole system.

In a normal world we shouldn’t be checking in normalizedTime > 1 because it should never be greater than 1 (hence normalized).

To answer your question, as I recollect, if your game drops frames, it may skip the frame where normalizedTime is 1, and it will continue adding, messing up the whole animator crap. I don’t know how, I don’t know why, and I guess Unity employees don’t know either or don’t care (yeah, sorry guys, I’ve been very nice with you in the past, but you were not).

At the moment, the only way I managed to get out of this is by doing the following:

animator.Play(animClipsInfo[×].clip.name, 0, 0f);

But right after that, the following animation will play twice, or will be looped forever (randomly, like everything in Unity). I’ll now try to figure out a way to avoid this.

otherwise this works:

animator.Rebind();
animator.Update(0f);

but will be jerky, as it won’t transition to idle anim but jump to it.

Let me know if you managed to get somewhere!

This is by design. It counts the time since the start of the animation and whether the animation is looping or not has no relevance to that.

“Normalized” doesn’t mean “between 0 and 1”, it means “scaled such that a multiple of 1 is a multiple of the animation’s length”.

  • So a normalized time of 1.1 means it’s 110% of the way into the animation where an un-normalized time of 1.1 would mean 1.1 seconds into the animation.
  • And a normalized time of 1.1 on a non-looping animation means 10% past the end. It’s not telling you the normalized time is 0 because its time is literally not at 0 so it would make no sense to pretend it’s 0.
  • Theoretically it could clamp 1.1 down to 1 for a non-looping animation, but there would be no practical benefit to that and you would lose the ability to check how long it has been at the end of the animation.

OP’s issue is most likely due to one of the following (or possibly a combination of both):

  • Animator Controllers don’t change states immediately. If you set a parameter or tell it to play something then check the current state info before the next animation update (i.e. between Update and LateUpdate) it will still contain the previous state info from before that change.

  • For example, if you have a Walk animation currently at normalized time 1.5 and you call animator.Play("Attack"); then in the same frame your attack script checks animator.GetCurrentAnimatorStateInfo(0).normalizedTime it will still give 1.5 because it’s still referring to the Walk state instead of the Attack state.

  • Your options for avoiding this are:

  • Always wait a frame before checking the state details and also check to make sure it actually entered the state you wanted because it might not. This leads to very messy code.

  • Call animator.Update(); to force it to update immediately. This wastes a considerable amount of performance because it evaluates the whole Animator Controller and applies its outputs to your character in the scene, most of which you don’t care about because the regular animation update for that frame will still have to do it all again.

  • Use a different animation system which doesn’t have this limitation. That’s obviously going to take a bunch of effort to learn the new system and change over.

  • If the Attack state is currently fading out and you tell it to play again without resetting its time, then it would continue playing from its previous time which is still past the end of the animation. It never returned to 0 because it never finished fading out. You wouldn’t want it to snap its time back to 0 while it’s fading out because that would defeat the whole purpose of having smooth fades.

  • As I mentioned above, this is by design. If you don’t want it to continue from its current time, you need to set the time.

  • If you’re starting the animation with any of the animator.Play or CrossFade methods, they take a normalized time parameter.

  • If you’re using a parameter to trigger a transition, take a look at the transition settings. I can’t actually remember if transitions have a setting for that because I haven’t had the misfortune of needing to use Animator Controllers in a long time.

Adding :

yield return null;

to the top of my function:

    public IEnumerator AttackStallEnd()
    {
        yield return null;

        SpeedCurrent = 0;

        while (Anim.GetCurrentAnimatorStateInfo(0).normalizedTime < 1.0f)
        {
            yield return null;
        }

        AttackPhase = 4;
    }

is the end to my struggle on solving this problem, thank you Kybernetik