AnimationPlayableAssetInspector.cs 14.3 KB
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.Timeline;

namespace UnityEditor.Timeline
{
    [CustomEditor(typeof(AnimationPlayableAsset)), CanEditMultipleObjects]
    class AnimationPlayableAssetInspector : Editor
    {
        static class Styles
        {
            public static readonly GUIContent RotationText = EditorGUIUtility.TrTextContent("Rotation");
            public static readonly GUIContent AnimClipText = EditorGUIUtility.TrTextContent("Animation Clip");
            public static readonly GUIContent TransformOffsetTitle = EditorGUIUtility.TrTextContent("Clip Transform Offsets", "Use this to offset the root transform position and rotation relative to the track when playing this clip");
            public static readonly GUIContent AnimationClipName = EditorGUIUtility.TrTextContent("Animation Clip Name");
            public static readonly GUIContent MatchTargetFieldsTitle = EditorGUIUtility.TrTextContent("Offsets Match Fields", "Fields to apply when matching offsets on clips. The defaults can be set on the track.");
            public static readonly GUIContent UseDefaults = EditorGUIUtility.TrTextContent("Use defaults");
            public static readonly GUIContent RemoveStartOffset = EditorGUIUtility.TrTextContent("Remove Start Offset", "Makes playback of the clip play relative to first key of the root transform");
            public static readonly GUIContent ApplyFootIK = EditorGUIUtility.TrTextContent("Foot IK", "Enable to apply foot IK to the AnimationClip when the target is humanoid.");
            public static readonly GUIContent Loop = EditorGUIUtility.TrTextContent("Loop", "Whether the source Animation Clip loops during playback.");
        }

        TimelineWindow m_TimelineWindow;
        GameObject m_Binding;

        TimelineAnimationUtilities.OffsetEditMode m_OffsetEditMode = TimelineAnimationUtilities.OffsetEditMode.None;
        EditorClip m_EditorClip;
        EditorClip[] m_EditorClips;

        SerializedProperty m_PositionProperty;
        SerializedProperty m_RotationProperty;
        SerializedProperty m_AnimClipProperty;
        SerializedProperty m_UseTrackMatchFieldsProperty;
        SerializedProperty m_MatchTargetFieldsProperty;
        SerializedObject m_SerializedAnimClip;
        SerializedProperty m_SerializedAnimClipName;
        SerializedProperty m_RemoveStartOffsetProperty;
        SerializedProperty m_ApplyFootIK;
        SerializedProperty m_Loop;

        Vector3 m_LastPosition;
        Vector3 m_LastRotation;

        public override void OnInspectorGUI()
        {
            if (target == null)
                return;

            serializedObject.Update();

            if (!m_TimelineWindow) m_TimelineWindow = TimelineWindow.instance;

            ShowAnimationClipField();
            ShowRecordableClipRename();
            ShowAnimationClipWarnings();

            EditorGUI.BeginChangeCheck();

            TransformOffsetsGUI();

            // extra checks are because the context menu may need to cause a re-evaluate
            bool changed = EditorGUI.EndChangeCheck() ||
                m_LastPosition != m_PositionProperty.vector3Value ||
                m_LastRotation != m_RotationProperty.vector3Value;
            m_LastPosition = m_PositionProperty.vector3Value;
            m_LastRotation = m_RotationProperty.vector3Value;

            if (changed)
            {
                // updates the changed properties and pushes them to the active playable
                serializedObject.ApplyModifiedProperties();
                ((AnimationPlayableAsset)target).LiveLink();

                // force an evaluate to happen next frame
                if (TimelineWindow.instance != null && TimelineWindow.instance.state != null)
                {
                    TimelineWindow.instance.state.Evaluate();
                }
            }

            EditorGUI.BeginChangeCheck();
            EditorGUILayout.PropertyField(m_ApplyFootIK, Styles.ApplyFootIK);
            EditorGUILayout.PropertyField(m_Loop, Styles.Loop);
            if (EditorGUI.EndChangeCheck())
                TimelineEditor.Refresh(RefreshReason.ContentsModified);

            serializedObject.ApplyModifiedProperties();
        }

        void ShowAnimationClipField()
        {
            bool disabled = m_EditorClips == null || m_EditorClips.Any(c => c.clip == null || c.clip.recordable);
            using (new EditorGUI.DisabledScope(disabled))
            {
                EditorGUI.BeginChangeCheck();
                EditorGUILayout.PropertyField(m_AnimClipProperty, Styles.AnimClipText);
                if (EditorGUI.EndChangeCheck())
                {
                    // rename the timeline clips to match the animation name if it did previously
                    if (m_AnimClipProperty.objectReferenceValue != null && m_EditorClips != null)
                    {
                        var newName = m_AnimClipProperty.objectReferenceValue.name;
                        foreach (var c in m_EditorClips)
                        {
                            if (c == null || c.clip == null || c.clip.asset == null)
                                continue;

                            var apa = c.clip.asset as AnimationPlayableAsset;
                            if (apa != null && apa.clip != null && c.clip.displayName == apa.clip.name)
                            {
                                if (c.clip.parentTrack != null)
                                    Undo.RegisterCompleteObjectUndo(c.clip.parentTrack, "Inspector");
                                c.clip.displayName = newName;
                            }
                        }
                    }

                    TimelineEditor.Refresh(RefreshReason.ContentsModified);
                }
            }
        }

        void TransformOffsetsMatchFieldsGUI()
        {
            var rect = EditorGUILayout.GetControlRect(true);
            EditorGUI.BeginProperty(rect, Styles.MatchTargetFieldsTitle, m_UseTrackMatchFieldsProperty);

            rect = EditorGUI.PrefixLabel(rect, Styles.MatchTargetFieldsTitle);
            int oldIndent = EditorGUI.indentLevel;
            EditorGUI.indentLevel = 0;
            EditorGUI.BeginChangeCheck();
            bool val = m_UseTrackMatchFieldsProperty.boolValue;
            val = EditorGUI.ToggleLeft(rect, Styles.UseDefaults, val);
            if (EditorGUI.EndChangeCheck())
                m_UseTrackMatchFieldsProperty.boolValue = val;

            EditorGUI.indentLevel = oldIndent;
            EditorGUI.EndProperty();


            if (!val || m_UseTrackMatchFieldsProperty.hasMultipleDifferentValues)
            {
                EditorGUI.indentLevel++;
                AnimationTrackInspector.MatchTargetsFieldGUI(m_MatchTargetFieldsProperty);
                EditorGUI.indentLevel--;
            }
        }

        void TransformOffsetsGUI()
        {
            if (ShouldShowOffsets())
            {
                EditorGUILayout.Space();
                EditorGUILayout.LabelField(Styles.TransformOffsetTitle);
                EditorGUI.indentLevel++;

                using (new EditorGUI.DisabledScope(targets.Length > 1))
                {
                    var previousOffsetMode = m_OffsetEditMode;
                    AnimationTrackInspector.ShowMotionOffsetEditModeToolbar(ref m_OffsetEditMode);
                    if (previousOffsetMode != m_OffsetEditMode)
                    {
                        SetTimeToClip();
                        SceneView.RepaintAll();
                    }
                }

                EditorGUILayout.BeginHorizontal();
                EditorGUILayout.PropertyField(m_PositionProperty);
                EditorGUILayout.EndHorizontal();

                EditorGUILayout.BeginHorizontal();
                EditorGUILayout.PropertyField(m_RotationProperty, Styles.RotationText);
                EditorGUILayout.EndHorizontal();

                EditorGUILayout.Space();

                EditorGUI.indentLevel--;

                TransformOffsetsMatchFieldsGUI();

                EditorGUI.BeginChangeCheck();
                EditorGUILayout.PropertyField(m_RemoveStartOffsetProperty, Styles.RemoveStartOffset);
                if (EditorGUI.EndChangeCheck())
                {
                    TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
                    Repaint();
                }
            }
        }

        void Reevaluate()
        {
            if (m_TimelineWindow != null && m_TimelineWindow.state != null)
            {
                m_TimelineWindow.state.Refresh();
                m_TimelineWindow.state.EvaluateImmediate();
            }
        }

        // Make sure the director time is within the bounds of the clip
        void SetTimeToClip()
        {
            if (m_TimelineWindow != null && m_TimelineWindow.state != null)
            {
                m_TimelineWindow.state.editSequence.time = Math.Min(m_EditorClip.clip.end, Math.Max(m_EditorClip.clip.start, m_TimelineWindow.state.editSequence.time));
            }
        }

        public void OnEnable()
        {
            if (target == null) // case 946080
                return;

            m_EditorClip = UnityEditor.Selection.activeObject as EditorClip;
            m_EditorClips = UnityEditor.Selection.objects.OfType<EditorClip>().ToArray();
            SceneView.duringSceneGui += OnSceneGUI;

            m_PositionProperty = serializedObject.FindProperty("m_Position");
            m_PositionProperty.isExpanded = true;
            m_RotationProperty = serializedObject.FindProperty("m_EulerAngles");
            m_AnimClipProperty = serializedObject.FindProperty("m_Clip");
            m_UseTrackMatchFieldsProperty = serializedObject.FindProperty("m_UseTrackMatchFields");
            m_MatchTargetFieldsProperty = serializedObject.FindProperty("m_MatchTargetFields");
            m_RemoveStartOffsetProperty = serializedObject.FindProperty("m_RemoveStartOffset");
            m_ApplyFootIK = serializedObject.FindProperty("m_ApplyFootIK");
            m_Loop = serializedObject.FindProperty("m_Loop");

            m_LastPosition = m_PositionProperty.vector3Value;
            m_LastRotation = m_RotationProperty.vector3Value;
        }

        void OnDestroy()
        {
            SceneView.duringSceneGui -= OnSceneGUI;
        }

        void OnSceneGUI(SceneView sceneView)
        {
            DoManipulators();
        }

        Transform GetTransform()
        {
            if (m_Binding != null)
                return m_Binding.transform;

            if (m_TimelineWindow != null &&  m_TimelineWindow.state != null &&
                m_TimelineWindow.state.editSequence.director != null &&
                m_EditorClip != null && m_EditorClip.clip != null)
            {
                var obj = TimelineUtility.GetSceneGameObject(m_TimelineWindow.state.editSequence.director,
                    m_EditorClip.clip.parentTrack);
                m_Binding = obj;
                if (obj != null)
                    return obj.transform;
            }
            return null;
        }

        void DoManipulators()
        {
            if (m_EditorClip == null || m_EditorClip.clip == null)
                return;

            AnimationPlayableAsset animationPlayable = m_EditorClip.clip.asset as AnimationPlayableAsset;
            AnimationTrack track = m_EditorClip.clip.parentTrack as AnimationTrack;
            Transform transform = GetTransform();

            if (transform != null && animationPlayable != null && m_OffsetEditMode != TimelineAnimationUtilities.OffsetEditMode.None && track != null)
            {
                UndoExtensions.RegisterPlayableAsset(animationPlayable, "Inspector");
                Vector3 position = transform.position;
                Quaternion rotation = transform.rotation;

                EditorGUI.BeginChangeCheck();
                if (m_OffsetEditMode == TimelineAnimationUtilities.OffsetEditMode.Translation)
                {
                    position = Handles.PositionHandle(position, Tools.pivotRotation == PivotRotation.Global ? Quaternion.identity : rotation);
                }
                else if (m_OffsetEditMode == TimelineAnimationUtilities.OffsetEditMode.Rotation)
                {
                    rotation = Handles.RotationHandle(rotation, position);
                }

                if (EditorGUI.EndChangeCheck())
                {
                    var res = TimelineAnimationUtilities.UpdateClipOffsets(animationPlayable, track, transform, position, rotation);
                    animationPlayable.position = res.position;
                    animationPlayable.eulerAngles = AnimationUtility.GetClosestEuler(res.rotation, animationPlayable.eulerAngles, RotationOrder.OrderZXY);
                    Reevaluate();
                    Repaint();
                }
            }
        }

        void ShowAnimationClipWarnings()
        {
            AnimationClip clip = m_AnimClipProperty.objectReferenceValue as AnimationClip;
            if (clip == null)
            {
                EditorGUILayout.HelpBox(AnimationPlayableAssetEditor.k_NoClipAssignedError, MessageType.Warning);
            }
            else if (clip.legacy)
            {
                EditorGUILayout.HelpBox(AnimationPlayableAssetEditor.k_LegacyClipError, MessageType.Warning);
            }
        }

        bool ShouldShowOffsets()
        {
            return targets.OfType<AnimationPlayableAsset>().All(x => x.hasRootTransforms);
        }

        void ShowRecordableClipRename()
        {
            if (targets.Length > 1 || m_EditorClip == null || m_EditorClip.clip == null || !m_EditorClip.clip.recordable)
                return;

            AnimationClip clip = m_AnimClipProperty.objectReferenceValue as AnimationClip;
            if (clip == null || !AssetDatabase.IsSubAsset(clip))
                return;

            if (m_SerializedAnimClip == null)
            {
                m_SerializedAnimClip = new SerializedObject(clip);
                m_SerializedAnimClipName = m_SerializedAnimClip.FindProperty("m_Name");
            }

            if (m_SerializedAnimClipName != null)
            {
                m_SerializedAnimClip.Update();
                EditorGUI.BeginChangeCheck();
                EditorGUILayout.DelayedTextField(m_SerializedAnimClipName, Styles.AnimationClipName);
                if (EditorGUI.EndChangeCheck())
                    m_SerializedAnimClip.ApplyModifiedProperties();
            }
        }
    }
}