Animation editor QoL improvements [Repost]

  • Post comments:0 Comments

Adding new properties to the timeline
Animation event selection
Animation property edition
Animation selector
Properties are blocked by the Animator even if the current animation does not animate them
Animation state creation
Current animation asset selection from timeline
Renaming a property of the root object
Removing the focus from the value box when clicking elsewhere
Calling child objects’ method in animation events
Animator parameters are reset when the object is disabled

[This was originally posted in the Unity forums]

I would like to share with you some ideas about how to greatly improve the usability of the Animation window. I’m pretty sure some of them have already come to your mind at some point because they are quite obvious but I want to put them in writing here anyway:

Adding new properties to the timeline

It would be nice to be able to select more than one property at a time to add them to the Animation timeline instead of having to open the menu and search for the next property every single time. Maybe the property selection popup could stay there until you click outside.

Additionally, or alternatively, it would be good to be able quickly find the next property to add by typing its name in a search box or something. Maybe you could search for the name of the object instead. Sometimes the hierarchy of a prefab is very big and it takes so much time to get to the right place by unfolding the tree.

I know you can add the same property of multiple instances by selecting the desired objects, enabling the animation recording and then right-clicking on the property in the inspector, finally selecting Add Key in the popup, but that’s just a weird workaround.

Animation event selection

I think you all agree with me if I say that the presentation and selection of the animation events at the top of the timeline is HORRIBLE. Specially the event stacking, when there are more than one at the same time, sometimes you can’t select them unless you expand the timeline horizontally and display them separated.
I suggest that only one selector is shown at the top of the timeline (maybe with a number or a sign that tells you that there are more than one event) and, once you select it, all the stacked events appear in the Inspector window, as a vertical list.

The other horrible thing about animation events is the way the methods of the animated object are selected in the Inspector: just a typical selection popup. You cannot search for it, you cannot scroll up and down in the list (yeah of course you can, using that tiny arrow at the bottom… the same thing as when you want to select a script in the Script Execution Order window, BTW).

Animation property edition

Some people don’t know that you can edit the path of an animation property, a typical thing you need when moving or renaming objects in an animated hierarchy. I would add some kind of floating “Edit” button that appears when you hover a property with the mouse cursor, so it is obvious for everybody.

I suggest also an alternative way to update the path of an animation property: dragging and dropping an object of the hierarchy right on the row of the property, so we don’t have to type it.

An alternative could be showing an object selection window (the same that is shown when selecting an object in a field of the Inspector).

Animation selector

When you select an object that has an animator, the Animation window adapts to that context. It shows the list of animations implied in the state machine of the animator and that’s great, but in my opinion it would be more useful to show the name of the states in the dropdown list instead of the name of the animation assets. For example “Idle” instead of “A_CharacterX_Idle”.

Properties are blocked by the Animator even if the current animation does not animate them

I would like to use this post also to remark a problem (by design) that has made many devs waste many hours, and it is how the values of the animation properties are blocked (you cannot modify them in runtime) while the animation that modifies them is not playing. If the Enabled property is in animation of the state “Idle” and is not at all in the animation of the state “Walking”, when the animator is playing “Walking” you still can’t modify Enabled. This is very counter-intuitive. Here you can see all the poor souls that fell in this hole:
https://forum.unity.com/threads/ani…eys-for-that-value.440363/page-3#post-8709834

Animation state creation

When adding a new state in the animator state machine window (right click –> Create state –> Empty), it would be useful to automatically set the focus in the text box of the name of the new state. You will always want to write a name for the state after creating it.

Current animation asset selection from timeline

It would be good to have a button placed somewhere in the Animation timeline window to quickly find in the project browser the Animation asset we are working on.

Renaming a property of the root object

If you have added an animation property for the root object of a prefab to the Animation timeline window, you are not allowed to rename the path of that object in that property. It only happens to the root object, which is inconsistent. Imagine you are animating an Image of the root object but then you decide to move that image to a child object without losing the keyframes of its animation, normally you would edit the path of that animation property and you would be done, but that is not allowed in this case.

Removing the focus from the value box when clicking elsewhere

Imagine you have an animation property for the position of an object, you click on the X value and write a number there and want to move to another instant of the timeline dragging the vertical line. The vertical line that tells the current instant moves there but the box where you wrote the number still shows the same number, the box have not lost the focus when you clicked on the timeline and you cannot see the value of X at the current instant. This is quite annoying sometimes, the textbox should be hidden as soon as you click somewhere else and the number should be refreshed as expected as we move along the timeline.

Calling child objects’ method in animation events

You can call a method of a component in the object that has the Animator component, with some limitations (parameter types). I can understand that you are not allowed to call methods in other objects that belong to other hierarchies or in a parent, but I do not see a reason to not letting to call methods in objects that are UNDER the animated object. Every animation event could have a path (like animation properties do) or an object selector that filters by all the descendants of the animated object.

Animator parameters are reset when the object is disabled

This one surprised me so much and made me waste a lot of time. When you disable an animated object, all the parameters you set previously in its Animator are erased. All the parameters are set to their default values so the object will look very different once you enable it again. This is obviously by design, and it is obviously a decision I do not understand. Such behavior forces you to choose one of the 2 possible strategies to keep the visual aspect of the object:

  1. You keep track of the state of your object and apply a set of animations and FXs for each different state, so when the object is enabled it plays them all for the current state. Each data state will have a corresponding visual state. For example, if it is a button that has an “Activated” state, a “Deactivated” state and a “Focused” state, each of these states may correspond to a visual aspect: maybe the button is bigger and red when it is “Activated”, and its color is blue when it is “Focused”. These properties are set every time the object is enabled.
  2. You can use a special component that saves the values of all the parameters before the object is disabled and restores them when it is enabled. It must appear above the Animator component in the Inspector (yes, I know, the order of the components cannot be trusted, but the truth is that it affects the execution order of the OnDisable method, both in editor and in a build). You are free to use this code:
// Copyright 2023 Alejandro Villalba Avila
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 
// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
// IN THE SOFTWARE.

using Game.Core.Logging;
using Sirenix.OdinInspector;
using UnityEngine;

namespace Game.Utils.Animation
{
    /// <summary>
    /// A component that saves the values of all the parameters of an animation controller while the Animator is disabled and restores them when enabled.
    /// This is necessary due to a weird behavior of the Animator in Unity. All parameters are reset every time the object is disabled.
    /// </summary>
    public class AnimatorParameterRestorer : MonoBehaviour
    {
        protected struct AnimatorParameter
        {
#if UNITY_EDITOR
            public string Name;
#endif
            public AnimatorControllerParameterType Type;
            public object Value;
        }

        [Tooltip("An animator that has an animation controller that provides several parameters.")]
        [SerializeField]
        protected Animator m_Animator;

        [GUIColor(0.0f, 1.0f, 0.0f)]
        [ShowInInspector]
        protected AnimatorParameter[] m_animatorParameters;

        protected virtual void Awake()
        {
            m_animatorParameters = new AnimatorParameter[m_Animator.parameterCount];
        }

        protected virtual void OnEnable()
        {
            int parametersCount = m_Animator.parameterCount;

            if (parametersCount == 0)
            {
                // If the execution order is different (Animator enables after this script) parameters may not be available
                Log.Error($"The number of parameters of the Animator for the object '{name}' is incorrect (OnDisable).");
            }

            for (int i = 0; i < parametersCount; ++i)
            {
                if(m_animatorParameters[i].Value == null)
                {
                    continue;
                }

                switch (m_Animator.parameters[i].type)
                {
                    case AnimatorControllerParameterType.Float:
                        {
                            m_Animator.SetFloat(m_Animator.parameters[i].nameHash, (float)m_animatorParameters[i].Value);
                        }
                        break;
                    case AnimatorControllerParameterType.Int:
                        {
                            m_Animator.SetInteger(m_Animator.parameters[i].nameHash, (int)m_animatorParameters[i].Value);
                        }
                        break;
                    case AnimatorControllerParameterType.Bool:
                        {
                            m_Animator.SetBool(m_Animator.parameters[i].nameHash, (bool)m_animatorParameters[i].Value);
                        }
                        break;
                    default:
                        break;
                }
            }
        }

        protected virtual void OnDisable()
        {
            int parametersCount = m_Animator.parameterCount;

            if (parametersCount == 0)
            {
                // If the execution order is different (Animator disables before this script) parameters may not be available
                Log.Error($"The number of parameters of the Animator for the object '{name}' is incorrect (OnDisable).");
            }

            for (int i = 0; i < parametersCount; ++i)
            {
#if UNITY_EDITOR
                m_animatorParameters[i].Name = m_Animator.parameters[i].name;
#endif
                m_animatorParameters[i].Type = m_Animator.parameters[i].type;

                switch (m_Animator.parameters[i].type)
                {
                    case AnimatorControllerParameterType.Float:
                        {
                            m_animatorParameters[i].Value = m_Animator.GetFloat(m_Animator.parameters[i].nameHash);
                        }
                        break;
                    case AnimatorControllerParameterType.Int:
                        {
                            m_animatorParameters[i].Value = m_Animator.GetInteger(m_Animator.parameters[i].nameHash);
                        }
                        break;
                    case AnimatorControllerParameterType.Bool:
                        {
                            m_animatorParameters[i].Value = m_Animator.GetBool(m_Animator.parameters[i].nameHash);
                        }
                        break;
                    default:
                        break;
                }

            }
        }

        protected virtual void Reset()
        {
            m_Animator = GetComponent<Animator>();
        }
    }
}

Leave a Reply