AnimationTrackInspector.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. //#define PERF_PROFILE
  2. using System;
  3. using System.ComponentModel;
  4. using System.Linq;
  5. using UnityEngine;
  6. using UnityEngine.Timeline;
  7. using UnityEngine.Playables;
  8. namespace UnityEditor.Timeline
  9. {
  10. [CustomEditor(typeof(AnimationTrack)), CanEditMultipleObjects]
  11. class AnimationTrackInspector : TrackAssetInspector
  12. {
  13. static class Styles
  14. {
  15. public static GUIContent MatchTargetFieldsTitle = EditorGUIUtility.TrTextContent("Default Offset Match Fields", "Fields to apply when matching offsets on clips. These are the defaults, and can be overridden for each clip.");
  16. public static readonly GUIContent PositionIcon = EditorGUIUtility.IconContent("MoveTool");
  17. public static readonly GUIContent RotationIcon = EditorGUIUtility.IconContent("RotateTool");
  18. public static GUIContent XTitle = EditorGUIUtility.TextContent("X");
  19. public static GUIContent YTitle = EditorGUIUtility.TextContent("Y");
  20. public static GUIContent ZTitle = EditorGUIUtility.TextContent("Z");
  21. public static GUIContent PositionTitle = EditorGUIUtility.TrTextContent("Position");
  22. public static GUIContent RotationTitle = EditorGUIUtility.TrTextContent("Rotation");
  23. public static readonly GUIContent OffsetModeTitle = EditorGUIUtility.TrTextContent("Track Offsets");
  24. 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.");
  25. 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.");
  26. 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.");
  27. public static readonly string AutoOffsetWarning = L10n.Tr("This mode is deprecated may be removed in a future release.");
  28. public static readonly string InheritedFromParent = L10n.Tr("Inherited");
  29. public static readonly string InheritedToolTip = L10n.Tr("This value is inherited from it's parent track.");
  30. public static readonly GUIContent RecordingOffsets = EditorGUIUtility.TrTextContent("Recorded Offsets", "Offsets applied to recorded position and rotation keys");
  31. public static readonly GUIContent[] OffsetContents;
  32. public static readonly GUIContent[] OffsetInheritContents;
  33. static Styles()
  34. {
  35. var values = Enum.GetValues(typeof(TrackOffset));
  36. OffsetContents = new GUIContent[values.Length];
  37. OffsetInheritContents = new GUIContent[values.Length];
  38. for (var index = 0; index < values.Length; index++)
  39. {
  40. var offset = (TrackOffset)index;
  41. var name = ObjectNames.NicifyVariableName(L10n.Tr(offset.ToString()));
  42. var memInfo = typeof(TrackOffset).GetMember(offset.ToString());
  43. var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
  44. if (attributes.Length > 0)
  45. {
  46. name = ((DescriptionAttribute)attributes[0]).Description;
  47. }
  48. OffsetContents[index] = new GUIContent(name);
  49. OffsetInheritContents[index] = new GUIContent(string.Format("{0} ({1})", InheritedFromParent, name));
  50. }
  51. }
  52. }
  53. TimelineAnimationUtilities.OffsetEditMode m_OffsetEditMode = TimelineAnimationUtilities.OffsetEditMode.None;
  54. SerializedProperty m_MatchFieldsProperty;
  55. SerializedProperty m_TrackPositionProperty;
  56. SerializedProperty m_TrackRotationProperty;
  57. SerializedProperty m_AvatarMaskProperty;
  58. SerializedProperty m_ApplyAvatarMaskProperty;
  59. SerializedProperty m_TrackOffsetProperty;
  60. SerializedProperty m_RecordedOffsetPositionProperty;
  61. SerializedProperty m_RecordedOffsetEulerProperty;
  62. Vector3 m_lastPosition;
  63. Vector3 m_lastRotation;
  64. GUIContent m_TempContent = new GUIContent();
  65. void Evaluate()
  66. {
  67. if (timelineWindow.state != null && timelineWindow.state.editSequence.director != null)
  68. {
  69. // force the update immediately, the deferred doesn't always work with the inspector
  70. timelineWindow.state.editSequence.director.Evaluate();
  71. }
  72. }
  73. void RebuildGraph()
  74. {
  75. if (timelineWindow.state != null)
  76. {
  77. timelineWindow.state.rebuildGraph = true;
  78. timelineWindow.Repaint();
  79. }
  80. }
  81. public override void OnInspectorGUI()
  82. {
  83. using (new EditorGUI.DisabledScope(IsTrackLocked()))
  84. {
  85. serializedObject.Update();
  86. DrawRootTransformOffset();
  87. EditorGUI.BeginChangeCheck();
  88. DrawRecordedOffsetProperties();
  89. if (EditorGUI.EndChangeCheck())
  90. RebuildGraph();
  91. DrawAvatarProperties();
  92. DrawMatchFieldsGUI();
  93. serializedObject.ApplyModifiedProperties();
  94. }
  95. }
  96. bool AnimatesRootTransform()
  97. {
  98. return targets.OfType<AnimationTrack>().All(t => t.AnimatesRootTransform());
  99. }
  100. bool ShouldDrawOffsets()
  101. {
  102. bool hasMultiple;
  103. var offsetMode = GetOffsetMode(out hasMultiple);
  104. if (hasMultiple)
  105. return false;
  106. if (offsetMode == TrackOffset.ApplySceneOffsets)
  107. return false;
  108. if (offsetMode == TrackOffset.ApplyTransformOffsets)
  109. return true;
  110. // Auto mode.
  111. PlayableDirector director = this.m_Context as PlayableDirector;
  112. if (director == null)
  113. return false;
  114. // If any bound animators have controllers don't show
  115. foreach (var track in targets.OfType<AnimationTrack>())
  116. {
  117. var animator = track.GetBinding(director);
  118. if (animator != null && animator.runtimeAnimatorController != null)
  119. return false;
  120. }
  121. return true;
  122. }
  123. void DrawRootTransformOffset()
  124. {
  125. if (!AnimatesRootTransform())
  126. return;
  127. bool showWarning = SetupOffsetTooltip();
  128. DrawRootTransformDropDown();
  129. if (ShouldDrawOffsets())
  130. {
  131. EditorGUI.indentLevel++;
  132. DrawRootMotionToolBar();
  133. DrawRootMotionOffsetFields();
  134. EditorGUI.indentLevel--;
  135. }
  136. if (showWarning)
  137. {
  138. EditorGUI.indentLevel++;
  139. EditorGUILayout.HelpBox(Styles.AutoOffsetWarning, MessageType.Warning, true);
  140. EditorGUI.indentLevel--;
  141. }
  142. }
  143. bool SetupOffsetTooltip()
  144. {
  145. Styles.OffsetModeTitle.tooltip = string.Empty;
  146. bool hasMultiple;
  147. var offsetMode = GetOffsetMode(out hasMultiple);
  148. bool showWarning = false;
  149. if (!hasMultiple)
  150. {
  151. if (offsetMode == TrackOffset.ApplyTransformOffsets)
  152. Styles.OffsetModeTitle.tooltip = Styles.TransformOffsetInfo;
  153. else if (offsetMode == TrackOffset.ApplySceneOffsets)
  154. Styles.OffsetModeTitle.tooltip = Styles.SceneOffsetInfo;
  155. else if (offsetMode == TrackOffset.Auto)
  156. {
  157. Styles.OffsetModeTitle.tooltip = Styles.AutoOffsetInfo;
  158. showWarning = true;
  159. }
  160. }
  161. return showWarning;
  162. }
  163. void DrawRootTransformDropDown()
  164. {
  165. bool anySubTracks = targets.OfType<AnimationTrack>().Any(t => t.isSubTrack);
  166. bool allSubTracks = targets.OfType<AnimationTrack>().All(t => t.isSubTrack);
  167. bool mixed;
  168. var rootOffsetMode = GetOffsetMode(out mixed);
  169. // if we are showing subtracks, we need to show the current mode from the parent
  170. // BUT keep it disabled
  171. if (anySubTracks)
  172. {
  173. m_TempContent.tooltip = string.Empty;
  174. if (mixed)
  175. m_TempContent.text = EditorGUI.mixedValueContent.text;
  176. else if (!allSubTracks)
  177. m_TempContent.text = Styles.OffsetContents[(int)rootOffsetMode].text;
  178. else
  179. {
  180. m_TempContent.text = Styles.OffsetInheritContents[(int)rootOffsetMode].text;
  181. m_TempContent.tooltip = Styles.InheritedToolTip;
  182. }
  183. using (new EditorGUI.DisabledScope(true))
  184. EditorGUILayout.LabelField(Styles.OffsetModeTitle, m_TempContent, EditorStyles.popup);
  185. }
  186. else
  187. {
  188. // We use an enum popup explicitly because it will handle the description attribute on the enum
  189. using (new GUIMixedValueScope(mixed))
  190. {
  191. var rect = EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight);
  192. EditorGUI.BeginProperty(rect, Styles.OffsetModeTitle, m_TrackOffsetProperty);
  193. EditorGUI.BeginChangeCheck();
  194. var result = (TrackOffset)EditorGUI.EnumPopup(rect, Styles.OffsetModeTitle, (TrackOffset)m_TrackOffsetProperty.intValue);
  195. if (EditorGUI.EndChangeCheck())
  196. {
  197. m_TrackOffsetProperty.enumValueIndex = (int)result;
  198. // this property changes the recordable state of the objects, so auto disable recording
  199. if (TimelineWindow.instance != null)
  200. {
  201. if (TimelineWindow.instance.state != null)
  202. TimelineWindow.instance.state.recording = false;
  203. RebuildGraph();
  204. }
  205. }
  206. EditorGUI.EndProperty();
  207. }
  208. }
  209. }
  210. void DrawMatchFieldsGUI()
  211. {
  212. if (!AnimatesRootTransform())
  213. return;
  214. m_MatchFieldsProperty.isExpanded = EditorGUILayout.Foldout(m_MatchFieldsProperty.isExpanded, Styles.MatchTargetFieldsTitle);
  215. if (m_MatchFieldsProperty.isExpanded)
  216. {
  217. EditorGUI.indentLevel++;
  218. MatchTargetsFieldGUI(m_MatchFieldsProperty);
  219. EditorGUI.indentLevel--;
  220. }
  221. }
  222. void DrawRootMotionOffsetFields()
  223. {
  224. EditorGUI.BeginChangeCheck();
  225. EditorGUILayout.BeginHorizontal();
  226. EditorGUILayout.PropertyField(m_TrackPositionProperty);
  227. EditorGUILayout.EndHorizontal();
  228. EditorGUILayout.BeginHorizontal();
  229. EditorGUILayout.PropertyField(m_TrackRotationProperty, Styles.RotationTitle);
  230. EditorGUILayout.EndHorizontal();
  231. EditorGUILayout.Space();
  232. EditorGUILayout.Space();
  233. if (EditorGUI.EndChangeCheck())
  234. {
  235. UpdateOffsets();
  236. }
  237. }
  238. void DrawRootMotionToolBar()
  239. {
  240. bool disable = targets.Length > 1;
  241. bool changed = false;
  242. if (!disable)
  243. {
  244. // detects external changes
  245. changed |= m_lastPosition != m_TrackPositionProperty.vector3Value || m_lastRotation != m_TrackRotationProperty.vector3Value;
  246. m_lastPosition = m_TrackPositionProperty.vector3Value;
  247. m_lastRotation = m_TrackRotationProperty.vector3Value;
  248. SceneView.RepaintAll();
  249. }
  250. EditorGUI.BeginChangeCheck();
  251. using (new EditorGUI.DisabledScope(disable))
  252. ShowMotionOffsetEditModeToolbar(ref m_OffsetEditMode);
  253. changed |= EditorGUI.EndChangeCheck();
  254. if (changed)
  255. {
  256. UpdateOffsets();
  257. }
  258. }
  259. void UpdateOffsets()
  260. {
  261. foreach (var track in targets.OfType<AnimationTrack>())
  262. track.UpdateClipOffsets();
  263. Evaluate();
  264. }
  265. void DrawAvatarProperties()
  266. {
  267. EditorGUILayout.PropertyField(m_ApplyAvatarMaskProperty);
  268. if (m_ApplyAvatarMaskProperty.hasMultipleDifferentValues || m_ApplyAvatarMaskProperty.boolValue)
  269. {
  270. EditorGUI.indentLevel++;
  271. EditorGUILayout.PropertyField(m_AvatarMaskProperty);
  272. EditorGUI.indentLevel--;
  273. }
  274. EditorGUILayout.Space();
  275. }
  276. public static void ShowMotionOffsetEditModeToolbar(ref TimelineAnimationUtilities.OffsetEditMode motionOffset)
  277. {
  278. GUILayout.BeginHorizontal();
  279. GUILayout.FlexibleSpace();
  280. GUILayout.FlexibleSpace();
  281. int newMotionOffsetMode = GUILayout.Toolbar((int)motionOffset, new[] { Styles.PositionIcon, Styles.RotationIcon });
  282. if (GUI.changed)
  283. {
  284. if ((int)motionOffset == newMotionOffsetMode) //untoggle the button
  285. motionOffset = TimelineAnimationUtilities.OffsetEditMode.None;
  286. else
  287. motionOffset = (TimelineAnimationUtilities.OffsetEditMode)newMotionOffsetMode;
  288. }
  289. GUILayout.FlexibleSpace();
  290. GUILayout.EndHorizontal();
  291. GUILayout.Space(3);
  292. }
  293. public override void OnEnable()
  294. {
  295. base.OnEnable();
  296. SceneView.duringSceneGui += OnSceneGUI;
  297. m_MatchFieldsProperty = serializedObject.FindProperty("m_MatchTargetFields");
  298. m_TrackPositionProperty = serializedObject.FindProperty("m_Position");
  299. m_TrackRotationProperty = serializedObject.FindProperty("m_EulerAngles");
  300. m_TrackOffsetProperty = serializedObject.FindProperty("m_TrackOffset");
  301. m_AvatarMaskProperty = serializedObject.FindProperty("m_AvatarMask");
  302. m_ApplyAvatarMaskProperty = serializedObject.FindProperty("m_ApplyAvatarMask");
  303. m_RecordedOffsetPositionProperty = serializedObject.FindProperty("m_InfiniteClipOffsetPosition");
  304. m_RecordedOffsetEulerProperty = serializedObject.FindProperty("m_InfiniteClipOffsetEulerAngles");
  305. m_lastPosition = m_TrackPositionProperty.vector3Value;
  306. m_lastRotation = m_TrackRotationProperty.vector3Value;
  307. }
  308. public void OnDestroy()
  309. {
  310. SceneView.duringSceneGui -= OnSceneGUI;
  311. }
  312. void OnSceneGUI(SceneView sceneView)
  313. {
  314. DoOffsetManipulator();
  315. }
  316. void DoOffsetManipulator()
  317. {
  318. if (targets.Length > 1) //do not edit the track offset on a multiple selection
  319. return;
  320. if (timelineWindow == null || timelineWindow.state == null || timelineWindow.state.editSequence.director == null)
  321. return;
  322. AnimationTrack animationTrack = target as AnimationTrack;
  323. if (animationTrack != null && (animationTrack.trackOffset == TrackOffset.ApplyTransformOffsets) && m_OffsetEditMode != TimelineAnimationUtilities.OffsetEditMode.None)
  324. {
  325. var boundObject = TimelineUtility.GetSceneGameObject(timelineWindow.state.editSequence.director, animationTrack);
  326. var boundObjectTransform = boundObject != null ? boundObject.transform : null;
  327. var offsets = TimelineAnimationUtilities.GetTrackOffsets(animationTrack, boundObjectTransform);
  328. EditorGUI.BeginChangeCheck();
  329. switch (m_OffsetEditMode)
  330. {
  331. case TimelineAnimationUtilities.OffsetEditMode.Translation:
  332. offsets.position = Handles.PositionHandle(offsets.position, (Tools.pivotRotation == PivotRotation.Global)
  333. ? Quaternion.identity
  334. : offsets.rotation);
  335. break;
  336. case TimelineAnimationUtilities.OffsetEditMode.Rotation:
  337. offsets.rotation = Handles.RotationHandle(offsets.rotation, offsets.position);
  338. break;
  339. }
  340. if (EditorGUI.EndChangeCheck())
  341. {
  342. TimelineUndo.PushUndo(animationTrack, "Inspector");
  343. TimelineAnimationUtilities.UpdateTrackOffset(animationTrack, boundObjectTransform, offsets);
  344. Evaluate();
  345. Repaint();
  346. }
  347. }
  348. }
  349. public void DrawRecordedOffsetProperties()
  350. {
  351. // only show if this applies to all targets
  352. foreach (var track in targets)
  353. {
  354. var animationTrack = track as AnimationTrack;
  355. if (animationTrack == null || animationTrack.inClipMode || animationTrack.infiniteClip == null || animationTrack.infiniteClip.empty)
  356. return;
  357. }
  358. GUILayout.Label(Styles.RecordingOffsets);
  359. EditorGUI.indentLevel++;
  360. EditorGUILayout.BeginHorizontal();
  361. EditorGUILayout.PropertyField(m_RecordedOffsetPositionProperty, Styles.PositionTitle);
  362. EditorGUILayout.EndHorizontal();
  363. EditorGUILayout.BeginHorizontal();
  364. EditorGUILayout.PropertyField(m_RecordedOffsetEulerProperty, Styles.RotationTitle);
  365. EditorGUILayout.EndHorizontal();
  366. EditorGUI.indentLevel--;
  367. EditorGUILayout.Space();
  368. }
  369. public static void MatchTargetsFieldGUI(SerializedProperty property)
  370. {
  371. const float ToggleWidth = 20;
  372. int value = 0;
  373. MatchTargetFields enumValue = (MatchTargetFields)property.intValue;
  374. EditorGUI.BeginChangeCheck();
  375. Rect rect = EditorGUILayout.GetControlRect(false, kLineHeight * 2);
  376. Rect itemRect = new Rect(rect.x, rect.y, rect.width, kLineHeight);
  377. EditorGUI.BeginProperty(rect, Styles.MatchTargetFieldsTitle, property);
  378. float minWidth = 0, maxWidth = 0;
  379. EditorStyles.label.CalcMinMaxWidth(Styles.XTitle, out minWidth, out maxWidth);
  380. float width = minWidth + ToggleWidth;
  381. GUILayout.BeginHorizontal();
  382. Rect r = EditorGUI.PrefixLabel(itemRect, Styles.PositionTitle);
  383. int oldIndent = EditorGUI.indentLevel;
  384. EditorGUI.indentLevel = 0;
  385. r.width = width;
  386. value |= EditorGUI.ToggleLeft(r, Styles.XTitle, enumValue.HasAny(MatchTargetFields.PositionX)) ? (int)MatchTargetFields.PositionX : 0;
  387. r.x += width;
  388. value |= EditorGUI.ToggleLeft(r, Styles.YTitle, enumValue.HasAny(MatchTargetFields.PositionY)) ? (int)MatchTargetFields.PositionY : 0;
  389. r.x += width;
  390. value |= EditorGUI.ToggleLeft(r, Styles.ZTitle, enumValue.HasAny(MatchTargetFields.PositionZ)) ? (int)MatchTargetFields.PositionZ : 0;
  391. EditorGUI.indentLevel = oldIndent;
  392. GUILayout.EndHorizontal();
  393. GUILayout.BeginHorizontal();
  394. itemRect.y += kLineHeight;
  395. r = EditorGUI.PrefixLabel(itemRect, Styles.RotationTitle);
  396. EditorGUI.indentLevel = 0;
  397. r.width = width;
  398. value |= EditorGUI.ToggleLeft(r, Styles.XTitle, enumValue.HasAny(MatchTargetFields.RotationX)) ? (int)MatchTargetFields.RotationX : 0;
  399. r.x += width;
  400. value |= EditorGUI.ToggleLeft(r, Styles.YTitle, enumValue.HasAny(MatchTargetFields.RotationY)) ? (int)MatchTargetFields.RotationY : 0;
  401. r.x += width;
  402. value |= EditorGUI.ToggleLeft(r, Styles.ZTitle, enumValue.HasAny(MatchTargetFields.RotationZ)) ? (int)MatchTargetFields.RotationZ : 0;
  403. EditorGUI.indentLevel = oldIndent;
  404. GUILayout.EndHorizontal();
  405. EditorGUI.EndProperty();
  406. if (EditorGUI.EndChangeCheck())
  407. {
  408. property.intValue = value;
  409. }
  410. }
  411. static TrackOffset GetOffsetMode(AnimationTrack track)
  412. {
  413. if (track.isSubTrack)
  414. {
  415. var parent = track.parent as AnimationTrack;
  416. if (parent != null) // fallback to the current track if there is an error
  417. track = parent;
  418. }
  419. return track.trackOffset;
  420. }
  421. // gets the current mode,
  422. TrackOffset GetOffsetMode(out bool hasMultiple)
  423. {
  424. var rootOffsetMode = GetOffsetMode(target as AnimationTrack);
  425. hasMultiple = targets.OfType<AnimationTrack>().Any(t => GetOffsetMode(t) != rootOffsetMode);
  426. return rootOffsetMode;
  427. }
  428. }
  429. }