Search Unity

Question [SOLVED] Animation variable locking

Discussion in 'Animation' started by theo97490, Jan 29, 2023.

  1. theo97490

    theo97490

    Joined:
    Jun 27, 2018
    Posts:
    5
    Hi there, yesterday and still today i have some headache about the workings on the animation system.

    I have a game where i have a restart button, when i press it restarts the current section the player is in with all the animations synced to the beginning of the section.

    In the current example i have a scrolling texture controlled by a script with two variables "Offset" and "Speed", Speed increments the value offset for convenience. Really just a one line script that sets the offset in the shader.

    I have a animator with 2 states, "Init" which is the default state and sets Offset to 0 (1 keyframe) and last for 1 frame or the shortest amount of time possible.

    The other state i'll call "Anim" simply manipulates the scrolling speed of the script. Init transitions to Anim instantaneously.

    Both states have write defaults off.



    What i expect: Init sets the value to 0 then the next states drives the animation as expected

    What happens: I see that the value Offset always get overwritten to 0 in the beginning of the update function of my script that adds speed to offset, which makes the animation glitch all over the place

    If i directly set "Anim" to the default state (and no other states in the graph), the animation works as expected but doesn't reset the state in the beginning.

    I also found that only the presence of the Init state in the graph (without any connections to others states) is able to mess up the animation.



    Even though the animator shows that Anim is in progress, i think that Init State is still animating which is weird.

    So the questions i am asking myself is:
    1) Why Init state seems to still run and overwrite offset to 0
    2) Why just adding the state to the graph without transitions mess up everything

    I hope i gave enough information, if it needs more context / info or something is not clear i'll answer asap!

    Code (CSharp):
    1. public class ScrollingShader : MonoBehaviour
    2. {
    3.     public float offset;
    4.     public float speed;
    5.     Material mat;
    6.  
    7.     private void Awake()
    8.     {
    9.         mat = GetComponent<SpriteRenderer>().material;
    10.     }
    11.  
    12.     // Update is called once per frame
    13.     void Update()
    14.     {
    15.         offset = (offset + speed * Time.deltaTime) % 1;
    16.         mat.SetFloat("_Offset", offset);
    17.     }
    18. }
    Expected:
    https://cdn.discordapp.com/attachme...469396/Screencast_from_28-01-2023_195835.webm
    Actual:
    https://cdn.discordapp.com/attachme...873172/Screencast_from_28-01-2023_200423.webm
     
    Last edited: Jan 29, 2023
  2. trib_nation

    trib_nation

    Joined:
    Jan 29, 2023
    Posts:
    8
    I don't fully understand what you're trying to do, but if you have a script setting the offset value of a material it might be overwriting the animation result? if you comment out line 16 do you still have the same issues?

    I also noticed your offset value is set to public, I'm guessing this is so you can see it in the inspector but you can use [SerializeField] to ensure no other script is using the variable while being able to see it in the inspector.
    [SerializeField] float offset; instead of public float offset;

    I'm assuming the script is just attached to the relevant Gameobject and will therefore run regardless of whether "init" is added or not. Have you added "init" while this script is disabled and had the same issue?

    If you have any other scripts running which could affect animation you will likely need to include parts of those as well for additional context.
     
  3. theo97490

    theo97490

    Joined:
    Jun 27, 2018
    Posts:
    5
    The purpose of the script ScrollingShader is to allow me to animate the speed of the scrolling animation instead of the offset, It is only there for convienience.

    The animations writes only to the script variables and not to the shaders
    Init: 1 keyframe, set Offset to 0
    Anim: Changes the speed variable over the course of its duration (10 seconds)

    I can replicate the same behaviors described in the post in a blank scene with no other active script and with only the scrolling texture.

    I also tried your suggestion, i disabled the script and added a keyframe to enable it at the beginning of Anim. I also tried to deactivate in Init too then enable it in Anim but doesn't change much.
     
  4. trib_nation

    trib_nation

    Joined:
    Jan 29, 2023
    Posts:
    8
    Right, I think I understand a bit better. So your animations are actually setting variables within this script. Which is then acting according to the update function? Apologies I'm not used to setting script variables in animations.

    Ok! I might have found it. I made my own test function, set it as a public float to 5, made it move to 25 in one keyframe but my animation is 20 keyframes in total then the animator moves on to the next animation. To my surprise the value actually reset to 5 after the animation ended without the next animation even mentioning the variable.

    Here's where it goes wrong - when the animator changes to a new animation it tries to blend two animations together. This usually works well in smoothing out transitions between visual gameobjects like sitting to walking. However in this case this smoothing process is actually resetting the offset to the default value before you modified it. By changing the exit time to 0 it instantly went back to 5 after the animation had finished.

    To see this yourself try setting another public float in that one keyframe that isn't called anywhere else in the same script but don't do anything else with it and you should see it change back in the next animation.

    To fix this my personal suggestion would be to have a boolean variable instead that when set to true, sets the offset to 0 as you want in the update function. Then when the boolean resets in the next animation it wont reset the offset value. I'd advise putting the exit time to zero for this transition as well.
     
  5. theo97490

    theo97490

    Joined:
    Jun 27, 2018
    Posts:
    5
    Looking at it but i forgot to mention my exit time is already at 0. Also when you said that "To my suprise the value actually reset to 5 after the animation ended", isn't it Write Defaults enabled that does that ?
     
  6. trib_nation

    trib_nation

    Joined:
    Jan 29, 2023
    Posts:
    8
    Sorry I didn't see the line where you mentioned write defaults were off. I really thought I'd solved it there!

    Ok dumb question just in case it's the issue - are you sure it's transitioning to "anim" after "initstate" finishes?
     
  7. theo97490

    theo97490

    Joined:
    Jun 27, 2018
    Posts:
    5
    100% it shows anim running fine in the animator
     
  8. trib_nation

    trib_nation

    Joined:
    Jan 29, 2023
    Posts:
    8
    Ok, I've duplicated your problem. That is interesting.

    I think I've found the problem this time! When you change a variable in an animator it essentially locks the variable from being modified via script. You CAN modify the variable in a script and if you debug.log it will confirm this. However if you look at the variable in the inspector it will always revert to the animation default. This is because as soon as your update function ends it's reverting to it's value in the animator (0 for the offset) so the offset will change every frame but it will always revert to zero afterwards.

    When you leave your "initState" animation the animator has not forgotten what has happened. It's effectively identified offset as a value that can be changed by the animations and therefore sets it to the last mentioned animation value. This is why even adding an inactive animation seems to ruin the offset variable.

    How do you solve this? You will need to separate your animation variables from your script variables. This is also the reason why it's commonly recommended to not have your graphics and character controller on the same game object.

    If you want to keep most of this on animation still you will need to make a trigger boolean (I'm going to call resetOffset), which sets the offset to zero while true. Have resetOffset set to true in the first keyframe of initState then have resetOffset set to false in the first frame of "anim".
     
    theo97490 likes this.
  9. theo97490

    theo97490

    Joined:
    Jun 27, 2018
    Posts:
    5
    Wow I wouldn't ever have considered that! especially when write default is off.

    I also searched some similar topics about the variable locking and learned a few things with the LateUpdate usage

    https://forum.unity.com/threads/ani...ate-has-no-curves-keys-for-that-value.440363/

    As for the solution, the animator controller is present in a parent object so i added a c# event that the ScrollingShader script listens to. The InitState calls that event (with a public function).

    It all makes sense now! All i wanted is to learn why it worked like that and now i learned smt today. Thanks a lot dude for your insight!
     
    Last edited: Jan 31, 2023
    trib_nation likes this.