Write Defaults Confusion / Bug

Just trying to get something cleared up that doesn’t seem to be working.

Our team has found that when ever we animate something, lets say a simple button, we must animate every property in every state if that property is to change in any state. For example. Let’s say we have a simple controller for a custom button with these states:

Normal
2556145--177897--Normal.PNG
Pressed
2556145--177898--Pressed.PNG

Let’s say that we animate the color of the Image to tint the button in the pressed state. We are finding that unless we animate the Image color in the Normal state back to the original color, the button will often get stuck in the tinted state and start behaving incorrectly. This seems to apply to animations throughout our project. In more complex animations like our main Beast characters this means that our artists must animate every single property in every single state and it becomes quickly unmanageable. We’ve read multiple times about the ‘Write Defaults’ property that exists on each animation state, but it doesn’t seem to behave as defined by Unity. Either we’re not understanding the property or it simple isn’t working.

Here are some quotes I found about how it is supposed to work:

Am I doing something wrong? I would expect that with the ‘Write Defaults’ option checked, when we return to the Normal state the color of the button would return to the untinted normal Color without the need to animate a Color keyframe. Any clarity here would help.

Thanks!

1 Like

I’m surprised, no one else is having this issue? Specifically I’m referring to the ‘Write Defaults’ option in the inspector when selecting an animation state within a controller:

2563537--178598--WriteDefaults.PNG

I expect with this option selected that given 2 animated states, A and B, if I animate a property (like Color) in state B, but DO NOT animate the same property in state A, that when returning to A the default value for color would be used. This unfortunately is not the case, and requires we animated the color property in the A state as well to ensure it returns to normal.

This creates multiple times the work and is super-error prone considering that if you miss a single property in a single state, the animation can mess up at runtime.

Is Write Defaults working for anyone? Am I misunderstanding its use? Or is there an obvious workaround for this inconvenience that I’m missing? Any help here would save our animators a ton of time.

Thanks

2 Likes

Here’s the way Write Defaults works:
1- When the Animator is Initialized, we go through all the clips in the current AnimatorController, and we collect all the animated values of all the clips. It also collects the current value (from the scene) of all those, and saves this as the default value.

2- When you play a state with WriteDefaults that doesn’t animate a value, the default value is written.

What version are you using? The scene in this package seems to work fine in 5.4 beta. When the position is not animated, it goes back to zero, and when the color is not animated, it goes back to the default (green).

2565600–178772–cubePositionAndColor.unitypackage (11.2 KB)

4 Likes

I just enabled Write Defaults on all our animations in my current project and it appears to be working correctly. I’m using it to solve the exact problem you describe. Our UI artist is mixing and matching animations on UI objects, so we may scale something in to show it, then alpha fade it out to hide it. Without Write Defaults, the second time an object was shown it would still have 0 alpha from the previous fade and would be invisible. With it enabled the alpha gets instantly reset to 1 as expected.

So I’d say the documentation and your description appear correct. And at least in 5.3.2p2 it appears to be working properly.

Going from DavidGeoffroy’s response, perhaps your animator controller is picking up the wrong default value? Is the object in a weird state at init time?

I’m having exactly the same issue: when I deactivate an animated gameObject, and I reactivate it again, then default values are changed. Did you managed how to solve it?

2 Likes

I submitted a bug for this problem a year ago (Case 694834). It got closed as “won’t fix”. So there is no way to solve it.

2 Likes

That’s a terrible bug… I don’t understand why Unity developers won’t fix it :frowning:

1 Like

Hi @DavidGeoffroy . I’ve reproduced the issue using your project (cubePositionAndColor.unitypackage). Execute it, and when the cube is at the top position (and green), deactivate it in the editor, then activate it again, and you will see that it won’t go to the bottom anymore. Is this the correct behavior?

Any news regarding this topic?

Based on DavidGeoffroy explanation, I assume that when the game object is deactivated, and reactivated, then the animator read all the current values, and saves them as default values. I think this behavior is not correct, because it will cause unpredictable situations in animations. Is there any way to change this behavior?

2 Likes

I agree that the Animator recalculating defaults every time it is enabled is unintuitive and undesirable. I’d much rather have it either calculate defaults on the first enable and then never again or have control over when it recalculates.

It would also be helpful if the intended behavior was more clearly documented in the Scripting API and Manual. I find the existing documentation too vague to be helpful and resorted to searching for random forum posts to understand it. David’s response in this thread was one of the most helpful.

3 Likes

I ended up creating a generic script that saves all values in “Awake”, and restores them in “onDisable”. Attaching it to my objects solves the problem. I feel it’s a bit “tricky”, but it just works.

Anything new on this topic? I believe I am experiencing the same behaviour.

I understand why the behaviour is undesirable, but the default values are stored in dynamic memory (because the number of different possible default values is variable, and you might want to have default values for dynamically added objects), and components in Unity have to deallocate all dynamically allocated memory when the GameObject is disabled.

You can work around this by not disabling the GameObject, only the components. GameObjects themselves take close to zero processing power.

Of course, then your components will still take up memory space, but that’s the tradeoff.

Or, you could always do the following:

  • Add an empty state in your controller called DefaultPose (or a name that makes sense to you), with no transitions
  • Make sure that Write Defaults is enabled for that state
    When disabling the GO:
  • animator.Crossfade(“DefaultPose”, 0f); //Force switch to Default Pose State, instant transition
  • animator.Update(0f); //force transition completion, your object should now be in default pose
  • go.SetActive(false); //disabled in default pose

Now, when your object is reenabled, it will be in the default pose, and the defaults will be read from this pose.

(I think Update(0f) will give the right pose, but if it doesn’t, Update with any positive value should work fine)

Solution 1 takes less processing power on disable, but will take up memory, and requires disabling all components one by one.

Solution 2 takes a bit more CPU on disable, but is a bit simpler to implement, and doesn’t take up memory when the object is disabled.

We’ve got some potential solutions in mind, but none of them is more efficient or flexible than those two.

3 Likes

Just tried this on the OnDisable function for a mono behavior in the same game object and unfortunately doesn’t work. I added logs to check that the transition happens and it it seems like the animator goes to the right state but it never gets updated (I added an internal StateMachineBehaviour to check if update and state enter get called and they don’t) .

This is unfortunately a big deal as we use object pools to recycle some objects. So being able to reset to the right defaults is important for when reusing those objects.

If you already have the test setup, can you file a bug with it? I’m pretty certain I should be able to make it work, but maybe not OnDisable (because of the ordering of the cleanup phase).

This way, you’ll have an answer that’s personalized to your needs, and it cuts down quite a bit on my investigation time

So after playing with this for a while I got a solution that seems to be working based on the original solution proposed 2 posts above:

public void ReturnRewardItem(Item item)
    {
        item.transform.SetParent(null, false);
        Animator itemAnimator = item.GetComponent<Animator>();
        itemAnimator.CrossFade("Base Layer.Default", 0f);
        itemAnimator.Update(0f);

        StartCoroutine(ReturnItemCoroutine(item));
    }

    private IEnumerator ReturnItemCoroutine(Item item)
    {
        Animator itemAnimator = item.GetComponent<Animator>();
        int hashName = Animator.StringToHash("Base Layer.Default");

        do
        {
            yield return new WaitForEndOfFrame();
        }
        while(itemAnimator.GetCurrentAnimatorStateInfo(0).fullPathHash != hashName);

        item.gameObject.SetActive(false);
        itemPool.Return(Item);

        yield break;
    }

What seems to happen as far as I can tell is that we need to wait a frame to deactivate the object, so I don’t think the while loop is that necessary (but have not tested without it).

Anyhow I will still create a custom scene with this and a bug if I can get

This solution is problematic. It requires you to be aware of the possibility an Animator existing anywhere in an object hierarchy. If I disable a UI menu object, I now have to think about whether there are any Animators on it and I have to try to disable the entire UI around this one problematic object. And as soon as someone adds a new Animator, things are broken or I have to go back and spend time supporting this new thing.

Or, I could try to implement a general approach. Any object that uses an Animator can be registered with some manager. Maybe I use a helper script. Whenever a compound UI object is going to be disabled this manager can kick in and make sure it resets the object. Now programmers have to remember to talk to this manager, designers need to remember a helper script, and I have all this infrastructure to work around a bad design.

This suffers from the similar problems as above, with the added catch that updating dozens of animators might cause a hiccup. My original solution was almost exactly this. I remember that I had to call Update twice for it to work for reasons unknown. I also remember that this approach randomly stopped working shortly after I implemented it for no reason I was able to discern. I got fed up and ended up just manually resetting all animated values (luckily I know they all start at zero so I don’t have to make it even klugdier and remember the initial values).

You’re assuming it’s reasonable to have to micromanage dozens of Animators in a hierarchy because I want to disable the parent object.

I have a difficult time accepting that this can’t/won’t be completely solved in a clean way. Even just optionally applying defaults when the Animator is disabled would be a vastly better solution than the above.

1 Like

This issue is indeed pretty disturbing. I had some chance with the IsInTransition boolean in Animator when pooling my objects. It is inspired by the solutions above but with a more “generic” touch IMHO.

What it does :

  • If there is not an animator, Pool object simply
  • If GO has an animator wait for the animator to finish transition for all its layers.
  • Moreover if one of the layers has a ABaseStateBehaviour.RESET_STATE_NAME then trigger the ABaseStateBehaviour.RESET_DEFAULT_TRIGGER in order to transition to default state. (not mandatory)
public void Store()
   {
       SendResetAction();

        //store or destroy
        if ( m_Pool && ActivatePooling )
        {
            StartCoroutine(PoolStoreCoroutine());
        }
        else
        {
            Destroy(gameObject);
        }
    }

    public void SendResetAction()
    {
        SendEvent<IHasOnResetAction>(x => x.ResetAction());
    }

    public IEnumerator PoolStoreCoroutine()
    {
        Animator itemAnimator = GetComponent<Animator>();
        if (itemAnimator != null)
        {
            var l_NLayer = itemAnimator.layerCount;
            var l_ResetStateHash = Animator.StringToHash(ABaseStateBehaviour.RESET_STATE_NAME);
            var l_HasReset = false;
            for (int i = 0; i < l_NLayer; i++)
            {
                if (itemAnimator.HasState(i, l_ResetStateHash))
                {
                    l_HasReset = true;
                }
           
            }

            if( l_HasReset ) itemAnimator.SetTrigger(ABaseStateBehaviour.RESET_DEFAULT_TRIGGER);

            for (int i = 0; i < l_NLayer; i++)
            {
                do
                {
                    yield return new WaitForEndOfFrame();
                } while (itemAnimator.IsInTransition(i) );
            }
        }
   
        m_Pool.Store(this);
   
    }

It seems like it does not working with unity 2017.2, someone made it work with an older version (I tried with more than 0f for the update call)

@DavidGeoffroy

Components have to deallocate everything when the GameObject is disabled? Sounds like a serious design flaw in the engine but it doesn’t explain why there is no reasonable workaround for this particular problem or why you guys think that completely trashing the animation states on disable (with no way of ever reseting them again) is an acceptable behaviour.

No the problem is not the insignificant amount of memory that would take in most cases, the problem is that it’s a hacky inelegant solution where we need to go through the whole hierarchy (transform subtree) of game objects and disable all the components on each of them while making a dangerous assumption that there is no other reason any of them need to be disabled in the first place.

Honestly, this would be a wonderful solution… If it worked… Which apparently it doesn’t.

Yeah you guys always have something in the plans. Too bad we have to make games now and not in the year 2050.
I swear, working with Unity is like a death by a thousand cuts. Every single thing is just barely usable enough to make you think you can manage it, but by the time you realise all the deep design flaws it has, it’s too late to go back and your project is already just a collection of ugly unintuitive workarounds.

7 Likes