UnityGame/Library/PackageCache/com.unity.timeline/Editor/inspectors/AnimationTrackInspector.cs
2024-10-27 10:53:47 +03:00

517 lines
22 KiB
C#

//#define PERF_PROFILE
using System;
using System.ComponentModel;
using System.Linq;
using UnityEngine;
using UnityEngine.Timeline;
using UnityEngine.Playables;
namespace UnityEditor.Timeline
{
[CustomEditor(typeof(AnimationTrack)), CanEditMultipleObjects]
class AnimationTrackInspector : TrackAssetInspector
{
static class Styles
{
public static GUIContent MatchTargetFieldsTitle = L10n.TextContent("Default Offset Match Fields", "Fields to apply when matching offsets on clips. These are the defaults, and can be overridden for each clip.");
public static readonly GUIContent PositionIcon = EditorGUIUtility.IconContent("MoveTool");
public static readonly GUIContent RotationIcon = EditorGUIUtility.IconContent("RotateTool");
public static GUIContent XTitle = EditorGUIUtility.TextContent("X");
public static GUIContent YTitle = EditorGUIUtility.TextContent("Y");
public static GUIContent ZTitle = EditorGUIUtility.TextContent("Z");
public static GUIContent PositionTitle = L10n.TextContent("Position");
public static GUIContent RotationTitle = L10n.TextContent("Rotation");
public static readonly GUIContent OffsetModeTitle = L10n.TextContent("Track Offsets");
public static readonly string TransformOffsetInfo = L10n.Tr("Transform offsets are applied to the entire track. Use this mode to play the animation track at a fixed position and rotation.");
public static readonly string SceneOffsetInfo = L10n.Tr("Scene offsets will use the existing transform as initial offsets. Use this to play the track from the gameObjects current position and rotation.");
public static readonly string AutoOffsetInfo = L10n.Tr("Auto will apply scene offsets if there is a controller attached to the animator and transform offsets otherwise.");
public static readonly string AutoOffsetWarning = L10n.Tr("This mode is deprecated may be removed in a future release.");
public static readonly string InheritedFromParent = L10n.Tr("Inherited");
public static readonly string InheritedToolTip = L10n.Tr("This value is inherited from it's parent track.");
public static readonly string AvatarMaskWarning = L10n.Tr("Applying an Avatar Mask to the base track may not properly mask Root Motion or Humanoid bones from an Animator Controller or other Timeline track.");
public static readonly GUIContent RecordingOffsets = L10n.TextContent("Recorded Offsets", "Offsets applied to recorded position and rotation keys");
public static readonly GUIContent RecordingIkApplied = L10n.TextContent("Apply Foot IK", "Applies Foot IK to recorded Animation.");
public static readonly GUIContent[] OffsetContents;
public static readonly GUIContent[] OffsetInheritContents;
static Styles()
{
var values = Enum.GetValues(typeof(TrackOffset));
OffsetContents = new GUIContent[values.Length];
OffsetInheritContents = new GUIContent[values.Length];
for (var index = 0; index < values.Length; index++)
{
var offset = (TrackOffset)index;
var name = ObjectNames.NicifyVariableName(L10n.Tr(offset.ToString()));
var memInfo = typeof(TrackOffset).GetMember(offset.ToString());
var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0)
{
name = ((DescriptionAttribute)attributes[0]).Description;
}
OffsetContents[index] = new GUIContent(name);
OffsetInheritContents[index] = new GUIContent(string.Format("{0} ({1})", InheritedFromParent, name));
}
}
}
TimelineAnimationUtilities.OffsetEditMode m_OffsetEditMode = TimelineAnimationUtilities.OffsetEditMode.None;
SerializedProperty m_MatchFieldsProperty;
SerializedProperty m_TrackPositionProperty;
SerializedProperty m_TrackRotationProperty;
SerializedProperty m_AvatarMaskProperty;
SerializedProperty m_ApplyAvatarMaskProperty;
SerializedProperty m_TrackOffsetProperty;
SerializedProperty m_RecordedOffsetPositionProperty;
SerializedProperty m_RecordedOffsetEulerProperty;
SerializedProperty m_RecordedApplyFootIK;
Vector3 m_lastPosition;
Vector3 m_lastRotation;
GUIContent m_TempContent = new GUIContent();
void Evaluate()
{
if (timelineWindow.state != null && timelineWindow.state.editSequence.director != null)
{
// force the update immediately, the deferred doesn't always work with the inspector
timelineWindow.state.editSequence.director.Evaluate();
}
}
void RebuildGraph()
{
TimelineEditor.Refresh(RefreshReason.ContentsModified);
}
public override void OnInspectorGUI()
{
using (new EditorGUI.DisabledScope(IsTrackLocked()))
{
serializedObject.Update();
DrawRootTransformOffset();
EditorGUI.BeginChangeCheck();
DrawRecordedProperties();
DrawAvatarProperties();
if (EditorGUI.EndChangeCheck())
RebuildGraph();
DrawMatchFieldsGUI();
serializedObject.ApplyModifiedProperties();
}
}
bool AnimatesRootTransform()
{
return targets.OfType<AnimationTrack>().All(t => t.AnimatesRootTransform());
}
bool ShouldDrawOffsets()
{
bool hasMultiple;
var offsetMode = GetOffsetMode(out hasMultiple);
if (hasMultiple)
return false;
if (offsetMode == TrackOffset.ApplySceneOffsets)
return false;
if (offsetMode == TrackOffset.ApplyTransformOffsets)
return true;
// Auto mode.
PlayableDirector director = this.m_Context as PlayableDirector;
if (director == null)
return false;
// If any bound animators have controllers don't show
foreach (var track in targets.OfType<AnimationTrack>())
{
var animator = track.GetBinding(director);
if (animator != null && animator.runtimeAnimatorController != null)
return false;
}
return true;
}
void DrawRootTransformOffset()
{
if (!AnimatesRootTransform())
return;
bool showWarning = SetupOffsetTooltip();
DrawRootTransformDropDown();
if (ShouldDrawOffsets())
{
EditorGUI.indentLevel++;
DrawRootMotionToolBar();
DrawRootMotionOffsetFields();
EditorGUI.indentLevel--;
}
if (showWarning)
{
EditorGUI.indentLevel++;
EditorGUILayout.HelpBox(Styles.AutoOffsetWarning, MessageType.Warning, true);
EditorGUI.indentLevel--;
}
}
bool SetupOffsetTooltip()
{
Styles.OffsetModeTitle.tooltip = string.Empty;
bool hasMultiple;
var offsetMode = GetOffsetMode(out hasMultiple);
bool showWarning = false;
if (!hasMultiple)
{
if (offsetMode == TrackOffset.ApplyTransformOffsets)
Styles.OffsetModeTitle.tooltip = Styles.TransformOffsetInfo;
else if (offsetMode == TrackOffset.ApplySceneOffsets)
Styles.OffsetModeTitle.tooltip = Styles.SceneOffsetInfo;
else if (offsetMode == TrackOffset.Auto)
{
Styles.OffsetModeTitle.tooltip = Styles.AutoOffsetInfo;
showWarning = true;
}
}
return showWarning;
}
void DrawRootTransformDropDown()
{
bool anySubTracks = targets.OfType<AnimationTrack>().Any(t => t.isSubTrack);
bool allSubTracks = targets.OfType<AnimationTrack>().All(t => t.isSubTrack);
bool mixed;
var rootOffsetMode = GetOffsetMode(out mixed);
// if we are showing subtracks, we need to show the current mode from the parent
// BUT keep it disabled
if (anySubTracks)
{
m_TempContent.tooltip = string.Empty;
if (mixed)
m_TempContent.text = EditorGUI.mixedValueContent.text;
else if (!allSubTracks)
m_TempContent.text = Styles.OffsetContents[(int)rootOffsetMode].text;
else
{
m_TempContent.text = Styles.OffsetInheritContents[(int)rootOffsetMode].text;
m_TempContent.tooltip = Styles.InheritedToolTip;
}
using (new EditorGUI.DisabledScope(true))
EditorGUILayout.LabelField(Styles.OffsetModeTitle, m_TempContent, EditorStyles.popup);
}
else
{
// We use an enum popup explicitly because it will handle the description attribute on the enum
using (new GUIMixedValueScope(mixed))
{
var rect = EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight);
EditorGUI.BeginProperty(rect, Styles.OffsetModeTitle, m_TrackOffsetProperty);
EditorGUI.BeginChangeCheck();
var result = (TrackOffset)EditorGUI.EnumPopup(rect, Styles.OffsetModeTitle, (TrackOffset)m_TrackOffsetProperty.intValue);
if (EditorGUI.EndChangeCheck())
{
m_TrackOffsetProperty.enumValueIndex = (int)result;
// this property changes the recordable state of the objects, so auto disable recording
if (TimelineWindow.instance != null)
{
if (TimelineWindow.instance.state != null)
TimelineWindow.instance.state.recording = false;
RebuildGraph();
}
}
EditorGUI.EndProperty();
}
}
}
void DrawMatchFieldsGUI()
{
if (!AnimatesRootTransform())
return;
m_MatchFieldsProperty.isExpanded = EditorGUILayout.Foldout(m_MatchFieldsProperty.isExpanded, Styles.MatchTargetFieldsTitle, true);
if (m_MatchFieldsProperty.isExpanded)
{
EditorGUI.indentLevel++;
MatchTargetsFieldGUI(m_MatchFieldsProperty);
EditorGUI.indentLevel--;
}
}
void DrawRootMotionOffsetFields()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(m_TrackPositionProperty);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(m_TrackRotationProperty, Styles.RotationTitle);
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
EditorGUILayout.Space();
if (EditorGUI.EndChangeCheck())
{
UpdateOffsets();
}
}
void DrawRootMotionToolBar()
{
bool disable = targets.Length > 1;
bool changed = false;
if (!disable)
{
// detects external changes
changed |= m_lastPosition != m_TrackPositionProperty.vector3Value || m_lastRotation != m_TrackRotationProperty.vector3Value;
m_lastPosition = m_TrackPositionProperty.vector3Value;
m_lastRotation = m_TrackRotationProperty.vector3Value;
SceneView.RepaintAll();
}
EditorGUI.BeginChangeCheck();
using (new EditorGUI.DisabledScope(disable))
ShowMotionOffsetEditModeToolbar(ref m_OffsetEditMode);
changed |= EditorGUI.EndChangeCheck();
if (changed)
{
UpdateOffsets();
}
}
void UpdateOffsets()
{
foreach (var track in targets.OfType<AnimationTrack>())
track.UpdateClipOffsets();
Evaluate();
}
void DrawAvatarProperties()
{
EditorGUILayout.PropertyField(m_ApplyAvatarMaskProperty);
if (m_ApplyAvatarMaskProperty.hasMultipleDifferentValues || m_ApplyAvatarMaskProperty.boolValue)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(m_AvatarMaskProperty);
EditorGUI.indentLevel--;
}
if (targets.OfType<AnimationTrack>().Any(x => !x.isSubTrack))
EditorGUILayout.HelpBox(Styles.AvatarMaskWarning, MessageType.Warning);
EditorGUILayout.Space();
}
public static void ShowMotionOffsetEditModeToolbar(ref TimelineAnimationUtilities.OffsetEditMode motionOffset)
{
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUILayout.FlexibleSpace();
int newMotionOffsetMode = GUILayout.Toolbar((int)motionOffset, new[] { Styles.PositionIcon, Styles.RotationIcon });
if (GUI.changed)
{
if ((int)motionOffset == newMotionOffsetMode) //untoggle the button
motionOffset = TimelineAnimationUtilities.OffsetEditMode.None;
else
motionOffset = (TimelineAnimationUtilities.OffsetEditMode)newMotionOffsetMode;
}
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
GUILayout.Space(3);
}
public override void OnEnable()
{
base.OnEnable();
SceneView.duringSceneGui += OnSceneGUI;
m_MatchFieldsProperty = serializedObject.FindProperty("m_MatchTargetFields");
m_TrackPositionProperty = serializedObject.FindProperty("m_Position");
m_TrackRotationProperty = serializedObject.FindProperty("m_EulerAngles");
m_TrackOffsetProperty = serializedObject.FindProperty("m_TrackOffset");
m_AvatarMaskProperty = serializedObject.FindProperty("m_AvatarMask");
m_ApplyAvatarMaskProperty = serializedObject.FindProperty("m_ApplyAvatarMask");
m_RecordedOffsetPositionProperty = serializedObject.FindProperty("m_InfiniteClipOffsetPosition");
m_RecordedOffsetEulerProperty = serializedObject.FindProperty("m_InfiniteClipOffsetEulerAngles");
m_RecordedApplyFootIK = serializedObject.FindProperty("m_InfiniteClipApplyFootIK");
m_lastPosition = m_TrackPositionProperty.vector3Value;
m_lastRotation = m_TrackRotationProperty.vector3Value;
}
public void OnDestroy()
{
SceneView.duringSceneGui -= OnSceneGUI;
}
void OnSceneGUI(SceneView sceneView)
{
DoOffsetManipulator();
}
void DoOffsetManipulator()
{
if (targets.Length > 1) //do not edit the track offset on a multiple selection
return;
if (timelineWindow == null || timelineWindow.state == null || timelineWindow.state.editSequence.director == null)
return;
AnimationTrack animationTrack = target as AnimationTrack;
if (animationTrack != null && (animationTrack.trackOffset == TrackOffset.ApplyTransformOffsets) && m_OffsetEditMode != TimelineAnimationUtilities.OffsetEditMode.None)
{
var boundObject = TimelineUtility.GetSceneGameObject(timelineWindow.state.editSequence.director, animationTrack);
var boundObjectTransform = boundObject != null ? boundObject.transform : null;
var offsets = TimelineAnimationUtilities.GetTrackOffsets(animationTrack, boundObjectTransform);
EditorGUI.BeginChangeCheck();
switch (m_OffsetEditMode)
{
case TimelineAnimationUtilities.OffsetEditMode.Translation:
offsets.position = Handles.PositionHandle(offsets.position, (Tools.pivotRotation == PivotRotation.Global)
? Quaternion.identity
: offsets.rotation);
break;
case TimelineAnimationUtilities.OffsetEditMode.Rotation:
offsets.rotation = Handles.RotationHandle(offsets.rotation, offsets.position);
break;
}
if (EditorGUI.EndChangeCheck())
{
UndoExtensions.RegisterTrack(animationTrack, L10n.Tr("Inspector"));
TimelineAnimationUtilities.UpdateTrackOffset(animationTrack, boundObjectTransform, offsets);
Evaluate();
Repaint();
}
}
}
public void DrawRecordedProperties()
{
// only show if this applies to all targets
foreach (var track in targets)
{
var animationTrack = track as AnimationTrack;
if (animationTrack == null || animationTrack.inClipMode || animationTrack.infiniteClip == null || animationTrack.infiniteClip.empty)
return;
}
GUILayout.Label(Styles.RecordingOffsets);
EditorGUI.indentLevel++;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(m_RecordedOffsetPositionProperty, Styles.PositionTitle);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(m_RecordedOffsetEulerProperty, Styles.RotationTitle);
EditorGUILayout.EndHorizontal();
EditorGUI.indentLevel--;
EditorGUILayout.Space();
EditorGUILayout.PropertyField(m_RecordedApplyFootIK, Styles.RecordingIkApplied);
EditorGUILayout.Space();
}
public static void MatchTargetsFieldGUI(SerializedProperty property)
{
const float ToggleWidth = 20;
int value = 0;
MatchTargetFields enumValue = (MatchTargetFields)property.intValue;
EditorGUI.BeginChangeCheck();
Rect rect = EditorGUILayout.GetControlRect(false, kLineHeight * 2);
Rect itemRect = new Rect(rect.x, rect.y, rect.width, kLineHeight);
EditorGUI.BeginProperty(rect, Styles.MatchTargetFieldsTitle, property);
float minWidth = 0, maxWidth = 0;
EditorStyles.label.CalcMinMaxWidth(Styles.XTitle, out minWidth, out maxWidth);
float width = minWidth + ToggleWidth;
GUILayout.BeginHorizontal();
Rect r = EditorGUI.PrefixLabel(itemRect, Styles.PositionTitle);
int oldIndent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
r.width = width;
value |= EditorGUI.ToggleLeft(r, Styles.XTitle, enumValue.HasAny(MatchTargetFields.PositionX)) ? (int)MatchTargetFields.PositionX : 0;
r.x += width;
value |= EditorGUI.ToggleLeft(r, Styles.YTitle, enumValue.HasAny(MatchTargetFields.PositionY)) ? (int)MatchTargetFields.PositionY : 0;
r.x += width;
value |= EditorGUI.ToggleLeft(r, Styles.ZTitle, enumValue.HasAny(MatchTargetFields.PositionZ)) ? (int)MatchTargetFields.PositionZ : 0;
EditorGUI.indentLevel = oldIndent;
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
itemRect.y += kLineHeight;
r = EditorGUI.PrefixLabel(itemRect, Styles.RotationTitle);
EditorGUI.indentLevel = 0;
r.width = width;
value |= EditorGUI.ToggleLeft(r, Styles.XTitle, enumValue.HasAny(MatchTargetFields.RotationX)) ? (int)MatchTargetFields.RotationX : 0;
r.x += width;
value |= EditorGUI.ToggleLeft(r, Styles.YTitle, enumValue.HasAny(MatchTargetFields.RotationY)) ? (int)MatchTargetFields.RotationY : 0;
r.x += width;
value |= EditorGUI.ToggleLeft(r, Styles.ZTitle, enumValue.HasAny(MatchTargetFields.RotationZ)) ? (int)MatchTargetFields.RotationZ : 0;
EditorGUI.indentLevel = oldIndent;
GUILayout.EndHorizontal();
EditorGUI.EndProperty();
if (EditorGUI.EndChangeCheck())
{
property.intValue = value;
}
}
static TrackOffset GetOffsetMode(AnimationTrack track)
{
if (track.isSubTrack)
{
var parent = track.parent as AnimationTrack;
if (parent != null) // fallback to the current track if there is an error
track = parent;
}
return track.trackOffset;
}
// gets the current mode,
TrackOffset GetOffsetMode(out bool hasMultiple)
{
var rootOffsetMode = GetOffsetMode(target as AnimationTrack);
hasMultiple = targets.OfType<AnimationTrack>().Any(t => GetOffsetMode(t) != rootOffsetMode);
return rootOffsetMode;
}
}
}