342 lines
14 KiB
C#
342 lines
14 KiB
C#
|
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 = L10n.TextContent("Rotation");
|
||
|
public static readonly GUIContent AnimClipText = L10n.TextContent("Animation Clip");
|
||
|
public static readonly GUIContent TransformOffsetTitle = L10n.TextContent("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 = L10n.TextContent("Animation Clip Name");
|
||
|
public static readonly GUIContent MatchTargetFieldsTitle = L10n.TextContent("Offsets Match Fields", "Fields to apply when matching offsets on clips. The defaults can be set on the track.");
|
||
|
public static readonly GUIContent UseDefaults = L10n.TextContent("Use defaults");
|
||
|
public static readonly GUIContent RemoveStartOffset = L10n.TextContent("Remove Start Offset", "Makes playback of the clip play relative to first key of the root transform");
|
||
|
public static readonly GUIContent ApplyFootIK = L10n.TextContent("Foot IK", "Enable to apply foot IK to the AnimationClip when the target is humanoid.");
|
||
|
public static readonly GUIContent Loop = L10n.TextContent("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.GetParentTrack() != null)
|
||
|
Undo.RegisterCompleteObjectUndo(c.clip.GetParentTrack(), L10n.Tr("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.GetParentTrack());
|
||
|
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.GetParentTrack() as AnimationTrack;
|
||
|
Transform transform = GetTransform();
|
||
|
|
||
|
if (transform != null && animationPlayable != null && m_OffsetEditMode != TimelineAnimationUtilities.OffsetEditMode.None && track != null)
|
||
|
{
|
||
|
UndoExtensions.RegisterPlayableAsset(animationPlayable, L10n.Tr("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();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|