Search Unity

Question Creating backwards animation by multiplying speed with 1 and -1

Discussion in 'Animation' started by Masea1, Jan 8, 2022.

  1. Masea1

    Masea1

    Joined:
    Jun 9, 2018
    Posts:
    41
    Hello. I have an aiming animation which simply is there for when the player aims the weapon by holding a key. When they stop holding the key, the player lowers the weapon. To achieve this, I am using single animation and state and an animator parameter whose value is only 1 or -1. The desired result is when it is -1 it will decelerate the animation from where it was to the beginning. Meaning when the player suddenly stops aiming, the animation will not start from the end but stay where it is currently at.

    This is cool and all but when trying to develop this, I have encountered an awkward issue. Let's say, the player kept aiming for a very long time, when they stop aiming, it will take as much time for animation to start going backward. I was trying to look up ways to see why that happens, turns out the normalized time of the animation state goes beyond its limits (< 0 or > 1). I don't know why that happens but I think it shouldn't be doing that.

    Any idea how to fix this?
     
  2. Unrighteouss

    Unrighteouss

    Joined:
    Apr 24, 2018
    Posts:
    599
    Hello,

    I'm not sure how you're playing the animation backwards, but I'll assume you're controlling the normalized time.

    Normalized time for Unity animations does not stop at 1, it keeps going forever, regardless of whether or not the animation is looping. You need to stop it from going over 1 in your code. To help understand what the value actually is, you can use this code in your Update() method:
    Debug.Log(anim.GetCurrentAnimatorStateInfo(0).normalizedTime);


    Here is some code I wrote that basically does what you want:
    Code (CSharp):
    1.     Animator anim;
    2.  
    3.     float normalizedTime = 0f; // This is the number we use to control the normalized time of the animation.
    4.     float speed = 1f; // This is the number we use to control the speed of the animation.
    5.  
    6.     void Start()
    7.     {
    8.         anim = GetComponent<Animator>();
    9.     }
    10.  
    11.     void Update()
    12.     {
    13.         if (Input.GetKey(KeyCode.E)) // If we press the E key...
    14.         {
    15.             normalizedTime = normalizedTime + speed * Time.deltaTime; // Start aiming.
    16.             if (normalizedTime > 1f) { normalizedTime = 1f; } // Cap the animation's normalized time at 1f.
    17.             anim.Play("Aim", 0, normalizedTime); // Set the animation's normalized time.
    18.         }
    19.         else // If we are not pressing the E key...
    20.         {
    21.             normalizedTime = normalizedTime - speed * Time.deltaTime;
    22.             if (normalizedTime < 0f) { normalizedTime = 0f; }
    23.             anim.Play("Aim", 0, normalizedTime);
    24.         }
    25.  
    26.         Debug.Log(anim.GetCurrentAnimatorStateInfo(0).normalizedTime); // Help visualize what's happening.
    27.     }
    I've included comments explaining what the various lines do. This is just an example, you'll have to adapt this to your own code. If anything is unclear, please let me know.
     
    Zaghor and Masea1 like this.
  3. Masea1

    Masea1

    Joined:
    Jun 9, 2018
    Posts:
    41
    Thank you for your answer.

    I am playing the animation backward by basically multiplying its speed with a negative value like -1 that I set on an animation parameter. I often endeavor to separate animation logic from code as much as possible when I develop on Unity. What you suggested is a little far from what I want but I definitely get the idea that I need to stop the normalized time to go beyond 1 (or 0) which I did by using a state machine behavior attached to the related state where I obstructed normalized time inside an OnStateUpdate() method.

    Moreover, doing that actually displays an error in the console though when the said state also has a transition with has exit time enabled. The error is: "Invalid time range for Transition exit time condition"

    I got rid of it by simply removing the transition and doing the transition in code instead when needed.

    I am not really satisfied with how this was implemented (feels kinda hacky), to be honest. However, at least it is working now. I am all ears if you would advise me any other way to do so. Thanks again.
     
    Last edited: Jan 8, 2022
    Unrighteouss likes this.
  4. Unrighteouss

    Unrighteouss

    Joined:
    Apr 24, 2018
    Posts:
    599
    Hello again,

    I think I have a better idea of how you were implementing this now. I actually didn't know that you could set the animation speed to be negative in mecanim like that. When the Animator speed itself is set to be negative, Unity complains and doesn't allow you to do it, so I was a bit surprised that negative speed works for individual states.

    I'll stop giving advice since I'm still not sure exactly what your implementation looks like, and you have it working now. If you run into any issues, feel free to post some code or screenshots. You may find your current implementation hacky, but honestly, I find everything with mecanim to be a mess. I actually don't even use it anymore; I use an addon called Animancer instead.
     
  5. Masea1

    Masea1

    Joined:
    Jun 9, 2018
    Posts:
    41
    I am not directly setting the animation speed but instead its multiplier by an animation parameter.
     
  6. Masea1

    Masea1

    Joined:
    Jun 9, 2018
    Posts:
    41
    Update: So I figured out I need to stop the normalized time to go beyond 1 when the animation is not looped. Doing that so with a code like this attached to the state as StateMachineBehaviour:
    Code (CSharp):
    1. public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    2.             var normalizedTime = stateInfo.normalizedTime;
    3.        
    4.             if (normalizedTime > 1)
    5.                 animator.Play(0, layerIndex, 1);
    6. }
    However, with this code none of the transitions coming from the state with has exit time enabled will work and when the transitions are present I will constantly get the aforementioned error in the console: "Invalid time range for Transition exit time condition".

    My question is how to stop the normalized time without getting those problems in the first place?
     
  7. Unrighteouss

    Unrighteouss

    Joined:
    Apr 24, 2018
    Posts:
    599
    Can I ask why you want exit time enabled for your transitions? Normally, exit time is used when you have an animation that ends, and then connects to a different animation. From what I understand, your aim animation shouldn't be ending, so why does it need transitions with exit time?
     
  8. Masea1

    Masea1

    Joined:
    Jun 9, 2018
    Posts:
    41
    This is an aiming animation. It should finish when, for example, the player decides to reload their weapon. By the way, exit time is not always used when the animation ends, it could be 0, where the transition is triggered when the animation is at the beginning again (except that it isn't triggered when the animation starts for the first time) somehow.

    I have realized how Mecanim is not practical at all. Transitions with exit times are only triggered when times are exactly at that specified value, not when greater or anything. I don't know how this simple thing could not be considered when developing such a system. So... I manually get the normalized time of the state via script now, put it to an animator parameter, and have conditions with the said parameter in transitions instead of enabling exit time. This way, I am not getting those errors too.
     
    Unrighteouss likes this.