暂无描述
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

AnimationTrackInspector.cs 21KB

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