ManualMixerTransition.cs 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. using System;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using Object = UnityEngine.Object;
  6. #if UNITY_EDITOR
  7. using Animancer.Editor;
  8. using UnityEditor;
  9. using UnityEditorInternal;
  10. #endif
  11. namespace Animancer
  12. {
  13. /// <inheritdoc/>
  14. /// https://kybernetik.com.au/animancer/api/Animancer/ManualMixerTransition
  15. [Serializable]
  16. public class ManualMixerTransition : ManualMixerTransition<ManualMixerState>,
  17. ManualMixerState.ITransition, ICopyable<ManualMixerTransition>
  18. {
  19. /************************************************************************************************************************/
  20. /// <inheritdoc/>
  21. public override ManualMixerState CreateState()
  22. {
  23. State = new ManualMixerState();
  24. InitializeState();
  25. return State;
  26. }
  27. /************************************************************************************************************************/
  28. /// <inheritdoc/>
  29. public virtual void CopyFrom(ManualMixerTransition copyFrom)
  30. {
  31. CopyFrom((ManualMixerTransition<ManualMixerState>)copyFrom);
  32. }
  33. /************************************************************************************************************************/
  34. #if UNITY_EDITOR
  35. /************************************************************************************************************************/
  36. /// <inheritdoc/>
  37. [CustomPropertyDrawer(typeof(ManualMixerTransition), true)]
  38. public class Drawer : TransitionDrawer
  39. {
  40. /************************************************************************************************************************/
  41. /// <summary>The property this drawer is currently drawing.</summary>
  42. /// <remarks>Normally each property has its own drawer, but arrays share a single drawer for all elements.</remarks>
  43. public static SerializedProperty CurrentProperty { get; private set; }
  44. /// <summary>The <see cref="ManualMixerTransition{TState}.Animations"/> field.</summary>
  45. public static SerializedProperty CurrentAnimations { get; private set; }
  46. /// <summary>The <see cref="ManualMixerTransition{TState}.Speeds"/> field.</summary>
  47. public static SerializedProperty CurrentSpeeds { get; private set; }
  48. /// <summary>The <see cref="ManualMixerTransition{TState}.SynchronizeChildren"/> field.</summary>
  49. public static SerializedProperty CurrentSynchronizeChildren { get; private set; }
  50. private readonly Dictionary<string, ReorderableList>
  51. PropertyPathToStates = new Dictionary<string, ReorderableList>();
  52. private ReorderableList _MultiSelectDummyList;
  53. /************************************************************************************************************************/
  54. /// <summary>Gather the details of the `property`.</summary>
  55. /// <remarks>
  56. /// This method gets called by every <see cref="GetPropertyHeight"/> and <see cref="OnGUI"/> call since
  57. /// Unity uses the same <see cref="PropertyDrawer"/> instance for each element in a collection, so it
  58. /// needs to gather the details associated with the current property.
  59. /// </remarks>
  60. protected virtual ReorderableList GatherDetails(SerializedProperty property)
  61. {
  62. InitializeMode(property);
  63. GatherSubProperties(property);
  64. if (property.hasMultipleDifferentValues)
  65. {
  66. if (_MultiSelectDummyList == null)
  67. {
  68. _MultiSelectDummyList = new ReorderableList(new List<Object>(), typeof(Object))
  69. {
  70. elementHeight = AnimancerGUI.LineHeight,
  71. displayAdd = false,
  72. displayRemove = false,
  73. footerHeight = 0,
  74. drawHeaderCallback = DoAnimationHeaderGUI,
  75. drawNoneElementCallback = area => EditorGUI.LabelField(area,
  76. "Multi-editing animations is not supported"),
  77. };
  78. }
  79. return _MultiSelectDummyList;
  80. }
  81. if (CurrentAnimations == null)
  82. return null;
  83. var path = property.propertyPath;
  84. if (!PropertyPathToStates.TryGetValue(path, out var states))
  85. {
  86. states = new ReorderableList(CurrentAnimations.serializedObject, CurrentAnimations)
  87. {
  88. drawHeaderCallback = DoChildListHeaderGUI,
  89. elementHeightCallback = GetElementHeight,
  90. drawElementCallback = DoElementGUI,
  91. onAddCallback = OnAddElement,
  92. onRemoveCallback = OnRemoveElement,
  93. onReorderCallbackWithDetails = OnReorderList,
  94. drawFooterCallback = DoChildListFooterGUI,
  95. };
  96. PropertyPathToStates.Add(path, states);
  97. }
  98. states.serializedProperty = CurrentAnimations;
  99. return states;
  100. }
  101. /************************************************************************************************************************/
  102. /// <summary>
  103. /// Called every time a `property` is drawn to find the relevant child properties and store them to be
  104. /// used in <see cref="GetPropertyHeight"/> and <see cref="OnGUI"/>.
  105. /// </summary>
  106. protected virtual void GatherSubProperties(SerializedProperty property)
  107. {
  108. CurrentProperty = property;
  109. CurrentAnimations = property.FindPropertyRelative(AnimationsField);
  110. CurrentSpeeds = property.FindPropertyRelative(SpeedsField);
  111. CurrentSynchronizeChildren = property.FindPropertyRelative(SynchronizeChildrenField);
  112. if (!property.hasMultipleDifferentValues &&
  113. CurrentAnimations != null &&
  114. CurrentSpeeds != null &&
  115. CurrentSpeeds.arraySize != 0)
  116. CurrentSpeeds.arraySize = CurrentAnimations.arraySize;
  117. }
  118. /************************************************************************************************************************/
  119. /// <summary>
  120. /// Adds a menu item that will call <see cref="GatherSubProperties"/> then run the specified
  121. /// `function`.
  122. /// </summary>
  123. protected void AddPropertyModifierFunction(GenericMenu menu, string label,
  124. MenuFunctionState state, Action<SerializedProperty> function)
  125. {
  126. Serialization.AddPropertyModifierFunction(menu, CurrentProperty, label, state, (property) =>
  127. {
  128. GatherSubProperties(property);
  129. function(property);
  130. });
  131. }
  132. /// <summary>
  133. /// Adds a menu item that will call <see cref="GatherSubProperties"/> then run the specified
  134. /// `function`.
  135. /// </summary>
  136. protected void AddPropertyModifierFunction(GenericMenu menu, string label,
  137. Action<SerializedProperty> function)
  138. {
  139. Serialization.AddPropertyModifierFunction(menu, CurrentProperty, label, (property) =>
  140. {
  141. GatherSubProperties(property);
  142. function(property);
  143. });
  144. }
  145. /************************************************************************************************************************/
  146. /// <inheritdoc/>
  147. public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
  148. {
  149. var height = EditorGUI.GetPropertyHeight(property, label);
  150. if (property.isExpanded)
  151. {
  152. var states = GatherDetails(property);
  153. if (states != null)
  154. height += AnimancerGUI.StandardSpacing + states.GetHeight();
  155. if (CurrentAnimations != null)
  156. height -= AnimancerGUI.StandardSpacing + EditorGUI.GetPropertyHeight(CurrentAnimations, label);
  157. if (CurrentSpeeds != null)
  158. height -= AnimancerGUI.StandardSpacing + EditorGUI.GetPropertyHeight(CurrentSpeeds, label);
  159. if (CurrentSynchronizeChildren != null)
  160. height -= AnimancerGUI.StandardSpacing + EditorGUI.GetPropertyHeight(CurrentSynchronizeChildren, label);
  161. }
  162. return height;
  163. }
  164. /************************************************************************************************************************/
  165. private SerializedProperty _RootProperty;
  166. private ReorderableList _CurrentChildList;
  167. /// <inheritdoc/>
  168. public override void OnGUI(Rect area, SerializedProperty property, GUIContent label)
  169. {
  170. _RootProperty = null;
  171. base.OnGUI(area, property, label);
  172. if (_RootProperty == null ||
  173. !_RootProperty.isExpanded)
  174. return;
  175. using (DrawerContext.Get(_RootProperty))
  176. {
  177. if (Context.Transition == null)
  178. return;
  179. _CurrentChildList = GatherDetails(_RootProperty);
  180. if (_CurrentChildList == null)
  181. return;
  182. var indentLevel = EditorGUI.indentLevel;
  183. area.yMin = area.yMax - _CurrentChildList.GetHeight();
  184. EditorGUI.indentLevel++;
  185. area = EditorGUI.IndentedRect(area);
  186. EditorGUI.indentLevel = 0;
  187. _CurrentChildList.DoList(area);
  188. EditorGUI.indentLevel = indentLevel;
  189. TryCollapseArrays();
  190. }
  191. }
  192. /************************************************************************************************************************/
  193. /// <inheritdoc/>
  194. protected override void DoChildPropertyGUI(ref Rect area,
  195. SerializedProperty rootProperty, SerializedProperty property, GUIContent label)
  196. {
  197. if (Context?.Transition != null)
  198. {
  199. area.height = 0;
  200. // If we find the Animations property, hide it to draw it last.
  201. var path = property.propertyPath;
  202. if (path.EndsWith("." + AnimationsField))
  203. {
  204. _RootProperty = rootProperty;
  205. return;
  206. }
  207. else if (_RootProperty != null)
  208. {
  209. // If we already found the Animations property, also hide Speeds and Synchronize Children.
  210. if (path.EndsWith("." + SpeedsField) ||
  211. path.EndsWith("." + SynchronizeChildrenField))
  212. return;
  213. }
  214. }
  215. base.DoChildPropertyGUI(ref area, rootProperty, property, label);
  216. }
  217. /************************************************************************************************************************/
  218. private static float _SpeedLabelWidth;
  219. private static float _SyncLabelWidth;
  220. /// <summary>Splits the specified `area` into separate sections.</summary>
  221. protected static void SplitListRect(Rect area, bool isHeader, out Rect animation, out Rect speed, out Rect sync)
  222. {
  223. if (_SpeedLabelWidth == 0)
  224. _SpeedLabelWidth = AnimancerGUI.CalculateWidth(EditorStyles.popup, "Speed");
  225. if (_SyncLabelWidth == 0)
  226. _SyncLabelWidth = AnimancerGUI.CalculateWidth(EditorStyles.popup, "Sync");
  227. var spacing = AnimancerGUI.StandardSpacing;
  228. var syncWidth = isHeader ?
  229. _SyncLabelWidth :
  230. AnimancerGUI.ToggleWidth - spacing;
  231. var speedWidth = _SpeedLabelWidth + _SyncLabelWidth - syncWidth;
  232. if (!isHeader)
  233. {
  234. // Don't use Clamp because the max might be smaller than the min.
  235. var max = Math.Max(area.height, area.width * 0.25f - 30);
  236. speedWidth = Math.Min(speedWidth, max);
  237. }
  238. area.width += spacing;
  239. sync = AnimancerGUI.StealFromRight(ref area, syncWidth, spacing);
  240. speed = AnimancerGUI.StealFromRight(ref area, speedWidth, spacing);
  241. animation = area;
  242. }
  243. /************************************************************************************************************************/
  244. #region Headers
  245. /************************************************************************************************************************/
  246. /// <summary>Draws the headdings of the child list.</summary>
  247. protected virtual void DoChildListHeaderGUI(Rect area)
  248. {
  249. SplitListRect(area, true, out var animationArea, out var speedArea, out var syncArea);
  250. DoAnimationHeaderGUI(animationArea);
  251. DoSpeedHeaderGUI(speedArea);
  252. DoSyncHeaderGUI(syncArea);
  253. }
  254. /************************************************************************************************************************/
  255. /// <summary>Draws an "Animation" header.</summary>
  256. protected static void DoAnimationHeaderGUI(Rect area)
  257. {
  258. using (ObjectPool.Disposable.AcquireContent(out var label, "Animation",
  259. $"The animations that will be used for each child state" +
  260. $"\n\nCtrl + Click to allow picking Transition Assets (or anything that implements {nameof(ITransition)})"))
  261. {
  262. DoHeaderDropdownGUI(area, CurrentAnimations, label, null);
  263. }
  264. }
  265. /************************************************************************************************************************/
  266. #region Speeds
  267. /************************************************************************************************************************/
  268. /// <summary>Draws a "Speed" header.</summary>
  269. protected void DoSpeedHeaderGUI(Rect area)
  270. {
  271. using (ObjectPool.Disposable.AcquireContent(out var label, "Speed", Strings.Tooltips.Speed))
  272. {
  273. DoHeaderDropdownGUI(area, CurrentSpeeds, label, (menu) =>
  274. {
  275. AddPropertyModifierFunction(menu, "Reset All to 1",
  276. CurrentSpeeds.arraySize == 0 ? MenuFunctionState.Selected : MenuFunctionState.Normal,
  277. (_) => CurrentSpeeds.arraySize = 0);
  278. AddPropertyModifierFunction(menu, "Normalize Durations", MenuFunctionState.Normal, NormalizeDurations);
  279. });
  280. }
  281. }
  282. /************************************************************************************************************************/
  283. /// <summary>
  284. /// Recalculates the <see cref="CurrentSpeeds"/> depending on the <see cref="AnimationClip.length"/> of
  285. /// their animations so that they all take the same amount of time to play fully.
  286. /// </summary>
  287. private static void NormalizeDurations(SerializedProperty property)
  288. {
  289. var speedCount = CurrentSpeeds.arraySize;
  290. var lengths = new float[CurrentAnimations.arraySize];
  291. if (lengths.Length <= 1)
  292. return;
  293. int nonZeroLengths = 0;
  294. float totalLength = 0;
  295. float totalSpeed = 0;
  296. for (int i = 0; i < lengths.Length; i++)
  297. {
  298. var state = CurrentAnimations.GetArrayElementAtIndex(i).objectReferenceValue;
  299. if (AnimancerUtilities.TryGetLength(state, out var length) &&
  300. length > 0)
  301. {
  302. nonZeroLengths++;
  303. totalLength += length;
  304. lengths[i] = length;
  305. if (speedCount > 0)
  306. totalSpeed += CurrentSpeeds.GetArrayElementAtIndex(i).floatValue;
  307. }
  308. }
  309. if (nonZeroLengths == 0)
  310. return;
  311. var averageLength = totalLength / nonZeroLengths;
  312. var averageSpeed = speedCount > 0 ? totalSpeed / nonZeroLengths : 1;
  313. CurrentSpeeds.arraySize = lengths.Length;
  314. InitializeSpeeds(speedCount);
  315. for (int i = 0; i < lengths.Length; i++)
  316. {
  317. if (lengths[i] == 0)
  318. continue;
  319. CurrentSpeeds.GetArrayElementAtIndex(i).floatValue = averageSpeed * lengths[i] / averageLength;
  320. }
  321. TryCollapseArrays();
  322. }
  323. /************************************************************************************************************************/
  324. /// <summary>
  325. /// Initializes every element in the <see cref="CurrentSpeeds"/> array from the `start` to the end of
  326. /// the array to contain a value of 1.
  327. /// </summary>
  328. public static void InitializeSpeeds(int start)
  329. {
  330. var count = CurrentSpeeds.arraySize;
  331. while (start < count)
  332. CurrentSpeeds.GetArrayElementAtIndex(start++).floatValue = 1;
  333. }
  334. /************************************************************************************************************************/
  335. #endregion
  336. /************************************************************************************************************************/
  337. #region Sync
  338. /************************************************************************************************************************/
  339. /// <summary>Draws a "Sync" header.</summary>
  340. protected void DoSyncHeaderGUI(Rect area)
  341. {
  342. using (ObjectPool.Disposable.AcquireContent(out var label, "Sync",
  343. "Determines which child states have their normalized times constantly synchronized"))
  344. {
  345. DoHeaderDropdownGUI(area, CurrentSpeeds, label, (menu) =>
  346. {
  347. var syncCount = CurrentSynchronizeChildren.arraySize;
  348. var allState = syncCount == 0 ? MenuFunctionState.Selected : MenuFunctionState.Normal;
  349. AddPropertyModifierFunction(menu, "All", allState,
  350. (_) => CurrentSynchronizeChildren.arraySize = 0);
  351. var syncNone = syncCount == CurrentAnimations.arraySize;
  352. if (syncNone)
  353. {
  354. for (int i = 0; i < syncCount; i++)
  355. {
  356. if (CurrentSynchronizeChildren.GetArrayElementAtIndex(i).boolValue)
  357. {
  358. syncNone = false;
  359. break;
  360. }
  361. }
  362. }
  363. var noneState = syncNone ? MenuFunctionState.Selected : MenuFunctionState.Normal;
  364. AddPropertyModifierFunction(menu, "None", noneState, (_) =>
  365. {
  366. var count = CurrentSynchronizeChildren.arraySize = CurrentAnimations.arraySize;
  367. for (int i = 0; i < count; i++)
  368. CurrentSynchronizeChildren.GetArrayElementAtIndex(i).boolValue = false;
  369. });
  370. AddPropertyModifierFunction(menu, "Invert", MenuFunctionState.Normal, (_) =>
  371. {
  372. var count = CurrentSynchronizeChildren.arraySize;
  373. for (int i = 0; i < count; i++)
  374. {
  375. var property = CurrentSynchronizeChildren.GetArrayElementAtIndex(i);
  376. property.boolValue = !property.boolValue;
  377. }
  378. var newCount = CurrentSynchronizeChildren.arraySize = CurrentAnimations.arraySize;
  379. for (int i = count; i < newCount; i++)
  380. CurrentSynchronizeChildren.GetArrayElementAtIndex(i).boolValue = false;
  381. });
  382. AddPropertyModifierFunction(menu, "Non-Stationary", MenuFunctionState.Normal, (_) =>
  383. {
  384. var count = CurrentAnimations.arraySize;
  385. for (int i = 0; i < count; i++)
  386. {
  387. var state = CurrentAnimations.GetArrayElementAtIndex(i).objectReferenceValue;
  388. if (state == null)
  389. continue;
  390. if (i >= syncCount)
  391. {
  392. CurrentSynchronizeChildren.arraySize = i + 1;
  393. for (int j = syncCount; j < i; j++)
  394. CurrentSynchronizeChildren.GetArrayElementAtIndex(j).boolValue = true;
  395. syncCount = i + 1;
  396. }
  397. CurrentSynchronizeChildren.GetArrayElementAtIndex(i).boolValue =
  398. AnimancerUtilities.TryGetAverageVelocity(state, out var velocity) &&
  399. velocity != default;
  400. }
  401. TryCollapseSync();
  402. });
  403. });
  404. }
  405. }
  406. /************************************************************************************************************************/
  407. private static void SyncNone()
  408. {
  409. var count = CurrentSynchronizeChildren.arraySize = CurrentAnimations.arraySize;
  410. for (int i = 0; i < count; i++)
  411. CurrentSynchronizeChildren.GetArrayElementAtIndex(i).boolValue = false;
  412. }
  413. /************************************************************************************************************************/
  414. #endregion
  415. /************************************************************************************************************************/
  416. /// <summary>Draws the GUI for a header dropdown button.</summary>
  417. public static void DoHeaderDropdownGUI(Rect area, SerializedProperty property, GUIContent content,
  418. Action<GenericMenu> populateMenu)
  419. {
  420. if (property != null)
  421. EditorGUI.BeginProperty(area, GUIContent.none, property);
  422. if (populateMenu != null)
  423. {
  424. if (EditorGUI.DropdownButton(area, content, FocusType.Passive))
  425. {
  426. var menu = new GenericMenu();
  427. populateMenu(menu);
  428. menu.ShowAsContext();
  429. }
  430. }
  431. else
  432. {
  433. GUI.Label(area, content);
  434. }
  435. if (property != null)
  436. EditorGUI.EndProperty();
  437. }
  438. /************************************************************************************************************************/
  439. /// <summary>Draws the footer of the child list.</summary>
  440. protected virtual void DoChildListFooterGUI(Rect area)
  441. {
  442. ReorderableList.defaultBehaviours.DrawFooter(area, _CurrentChildList);
  443. EditorGUI.BeginChangeCheck();
  444. area.xMax = EditorGUIUtility.labelWidth + AnimancerGUI.IndentSize;
  445. area.y++;
  446. area.height = AnimancerGUI.LineHeight;
  447. using (ObjectPool.Disposable.AcquireContent(out var label, "Count"))
  448. {
  449. var indentLevel = EditorGUI.indentLevel;
  450. EditorGUI.indentLevel = 0;
  451. var labelWidth = EditorGUIUtility.labelWidth;
  452. EditorGUIUtility.labelWidth = AnimancerGUI.CalculateLabelWidth(label.text);
  453. var count = EditorGUI.DelayedIntField(area, label, _CurrentChildList.count);
  454. if (EditorGUI.EndChangeCheck())
  455. ResizeList(count);
  456. EditorGUIUtility.labelWidth = labelWidth;
  457. EditorGUI.indentLevel = indentLevel;
  458. }
  459. }
  460. /************************************************************************************************************************/
  461. #endregion
  462. /************************************************************************************************************************/
  463. /// <summary>Calculates the height of the state at the specified `index`.</summary>
  464. protected virtual float GetElementHeight(int index) => AnimancerGUI.LineHeight;
  465. /************************************************************************************************************************/
  466. /// <summary>Draws the GUI of the state at the specified `index`.</summary>
  467. private void DoElementGUI(Rect area, int index, bool isActive, bool isFocused)
  468. {
  469. if (index < 0 || index > CurrentAnimations.arraySize)
  470. return;
  471. area.height = AnimancerGUI.LineHeight;
  472. var state = CurrentAnimations.GetArrayElementAtIndex(index);
  473. var speed = CurrentSpeeds.arraySize > 0 ? CurrentSpeeds.GetArrayElementAtIndex(index) : null;
  474. DoElementGUI(area, index, state, speed);
  475. }
  476. /************************************************************************************************************************/
  477. /// <summary>Draws the GUI of the state at the specified `index`.</summary>
  478. protected virtual void DoElementGUI(Rect area, int index,
  479. SerializedProperty state, SerializedProperty speed)
  480. {
  481. SplitListRect(area, false, out var animationArea, out var speedArea, out var syncArea);
  482. DoElementGUI(animationArea, speedArea, syncArea, index, state, speed);
  483. }
  484. /// <summary>Draws the GUI of the state at the specified `index`.</summary>
  485. protected void DoElementGUI(Rect animationArea, Rect speedArea, Rect syncArea, int index,
  486. SerializedProperty state, SerializedProperty speed)
  487. {
  488. DoAnimationField(animationArea, state);
  489. if (speed != null)
  490. {
  491. EditorGUI.PropertyField(speedArea, speed, GUIContent.none);
  492. }
  493. else// If this element doesn't have its own speed property, just show 1.
  494. {
  495. EditorGUI.BeginProperty(speedArea, GUIContent.none, CurrentSpeeds);
  496. var value = Units.UnitsAttribute.DoSpecialFloatField(
  497. speedArea, null, 1, Units.AnimationSpeedAttribute.DisplayConverters[0]);
  498. // Middle Click toggles from 1 to -1.
  499. if (AnimancerGUI.TryUseClickEvent(speedArea, 2))
  500. value = -1;
  501. if (value != 1)
  502. {
  503. CurrentSpeeds.InsertArrayElementAtIndex(0);
  504. CurrentSpeeds.GetArrayElementAtIndex(0).floatValue = 1;
  505. CurrentSpeeds.arraySize = CurrentAnimations.arraySize;
  506. CurrentSpeeds.GetArrayElementAtIndex(index).floatValue = value;
  507. }
  508. EditorGUI.EndProperty();
  509. }
  510. DoSyncToggleGUI(syncArea, index);
  511. }
  512. /************************************************************************************************************************/
  513. /// <summary>
  514. /// Draws an <see cref="EditorGUI.ObjectField(Rect, GUIContent, Object, Type, bool)"/> that accepts
  515. /// <see cref="AnimationClip"/>s and <see cref="ITransition"/>s
  516. /// </summary>
  517. public static void DoAnimationField(Rect area, SerializedProperty property)
  518. {
  519. EditorGUI.BeginProperty(area, GUIContent.none, property);
  520. var targetObject = property.serializedObject.targetObject;
  521. var oldReference = property.objectReferenceValue;
  522. var currentEvent = Event.current;
  523. var isDrag =
  524. currentEvent.type == EventType.DragUpdated ||
  525. currentEvent.type == EventType.DragPerform;
  526. var type =
  527. isDrag ||
  528. currentEvent.control ||
  529. currentEvent.commandName == "ObjectSelectorUpdated" ?
  530. typeof(Object) : typeof(AnimationClip);
  531. var allowSceneObjects = targetObject != null && !EditorUtility.IsPersistent(targetObject);
  532. EditorGUI.BeginChangeCheck();
  533. var newReference = EditorGUI.ObjectField(area, GUIContent.none, oldReference, type, allowSceneObjects);
  534. if (EditorGUI.EndChangeCheck())
  535. {
  536. if (newReference == null || (IsClipOrTransition(newReference) && newReference != targetObject))
  537. property.objectReferenceValue = newReference;
  538. }
  539. if (isDrag && area.Contains(currentEvent.mousePosition))
  540. {
  541. var objects = DragAndDrop.objectReferences;
  542. if (objects.Length != 1 ||
  543. !IsClipOrTransition(objects[0]) ||
  544. objects[0] == targetObject)
  545. DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
  546. }
  547. EditorGUI.EndProperty();
  548. }
  549. /// <summary>Is the `clipOrTransition` an <see cref="AnimationClip"/> or <see cref="ITransition"/>?</summary>
  550. public static bool IsClipOrTransition(object clipOrTransition)
  551. => clipOrTransition is AnimationClip || clipOrTransition is ITransition;
  552. /************************************************************************************************************************/
  553. /// <summary>
  554. /// Draws a toggle to enable or disable <see cref="MixerState.SynchronizedChildren"/> for the child at
  555. /// the specified `index`.
  556. /// </summary>
  557. protected void DoSyncToggleGUI(Rect area, int index)
  558. {
  559. var syncProperty = CurrentSynchronizeChildren;
  560. var syncFlagCount = syncProperty.arraySize;
  561. var enabled = true;
  562. if (index < syncFlagCount)
  563. {
  564. syncProperty = syncProperty.GetArrayElementAtIndex(index);
  565. enabled = syncProperty.boolValue;
  566. }
  567. EditorGUI.BeginChangeCheck();
  568. EditorGUI.BeginProperty(area, GUIContent.none, syncProperty);
  569. enabled = GUI.Toggle(area, enabled, GUIContent.none);
  570. EditorGUI.EndProperty();
  571. if (EditorGUI.EndChangeCheck())
  572. {
  573. if (index < syncFlagCount)
  574. {
  575. syncProperty.boolValue = enabled;
  576. }
  577. else
  578. {
  579. syncProperty.arraySize = index + 1;
  580. for (int i = syncFlagCount; i < index; i++)
  581. {
  582. syncProperty.GetArrayElementAtIndex(i).boolValue = true;
  583. }
  584. syncProperty.GetArrayElementAtIndex(index).boolValue = enabled;
  585. }
  586. }
  587. }
  588. /************************************************************************************************************************/
  589. /// <summary>
  590. /// Called when adding a new state to the list to ensure that any other relevant arrays have new
  591. /// elements added as well.
  592. /// </summary>
  593. private void OnAddElement(ReorderableList list)
  594. {
  595. var index = list.index;
  596. if (index < 0 || Event.current.button == 1)// Right Click to add at the end.
  597. {
  598. index = CurrentAnimations.arraySize - 1;
  599. if (index < 0)
  600. index = 0;
  601. }
  602. OnAddElement(index);
  603. }
  604. /// <summary>
  605. /// Called when adding a new state to the list to ensure that any other relevant arrays have new
  606. /// elements added as well.
  607. /// </summary>
  608. protected virtual void OnAddElement(int index)
  609. {
  610. CurrentAnimations.InsertArrayElementAtIndex(index);
  611. if (CurrentSpeeds.arraySize > 0)
  612. CurrentSpeeds.InsertArrayElementAtIndex(index);
  613. if (CurrentSynchronizeChildren.arraySize > index)
  614. CurrentSynchronizeChildren.InsertArrayElementAtIndex(index);
  615. }
  616. /************************************************************************************************************************/
  617. /// <summary>
  618. /// Called when removing a state from the list to ensure that any other relevant arrays have elements
  619. /// removed as well.
  620. /// </summary>
  621. protected virtual void OnRemoveElement(ReorderableList list)
  622. {
  623. var index = list.index;
  624. Serialization.RemoveArrayElement(CurrentAnimations, index);
  625. if (CurrentSpeeds.arraySize > index)
  626. Serialization.RemoveArrayElement(CurrentSpeeds, index);
  627. if (CurrentSynchronizeChildren.arraySize > index)
  628. Serialization.RemoveArrayElement(CurrentSynchronizeChildren, index);
  629. }
  630. /************************************************************************************************************************/
  631. /// <summary>Sets the number of items in the child list.</summary>
  632. protected virtual void ResizeList(int size)
  633. {
  634. CurrentAnimations.arraySize = size;
  635. if (CurrentSpeeds.arraySize > size)
  636. CurrentSpeeds.arraySize = size;
  637. if (CurrentSynchronizeChildren.arraySize > size)
  638. CurrentSynchronizeChildren.arraySize = size;
  639. }
  640. /************************************************************************************************************************/
  641. /// <summary>
  642. /// Called when reordering states in the list to ensure that any other relevant arrays have their
  643. /// corresponding elements reordered as well.
  644. /// </summary>
  645. protected virtual void OnReorderList(ReorderableList list, int oldIndex, int newIndex)
  646. {
  647. CurrentSpeeds.MoveArrayElement(oldIndex, newIndex);
  648. var syncCount = CurrentSynchronizeChildren.arraySize;
  649. if (Math.Max(oldIndex, newIndex) >= syncCount)
  650. {
  651. CurrentSynchronizeChildren.arraySize++;
  652. CurrentSynchronizeChildren.GetArrayElementAtIndex(syncCount).boolValue = true;
  653. CurrentSynchronizeChildren.arraySize = newIndex + 1;
  654. }
  655. CurrentSynchronizeChildren.MoveArrayElement(oldIndex, newIndex);
  656. }
  657. /************************************************************************************************************************/
  658. /// <summary>
  659. /// Calls <see cref="TryCollapseSpeeds"/> and <see cref="TryCollapseSync"/>.
  660. /// </summary>
  661. public static void TryCollapseArrays()
  662. {
  663. if (CurrentProperty == null ||
  664. CurrentProperty.hasMultipleDifferentValues)
  665. return;
  666. TryCollapseSpeeds();
  667. TryCollapseSync();
  668. }
  669. /************************************************************************************************************************/
  670. /// <summary>
  671. /// If every element in the <see cref="CurrentSpeeds"/> array is 1, this method sets the array size to 0.
  672. /// </summary>
  673. public static void TryCollapseSpeeds()
  674. {
  675. var property = CurrentSpeeds;
  676. if (property == null)
  677. return;
  678. var speedCount = property.arraySize;
  679. if (speedCount <= 0)
  680. return;
  681. for (int i = 0; i < speedCount; i++)
  682. {
  683. if (property.GetArrayElementAtIndex(i).floatValue != 1)
  684. return;
  685. }
  686. property.arraySize = 0;
  687. }
  688. /************************************************************************************************************************/
  689. /// <summary>
  690. /// Removes any true elements from the end of the <see cref="CurrentSynchronizeChildren"/> array.
  691. /// </summary>
  692. public static void TryCollapseSync()
  693. {
  694. var property = CurrentSynchronizeChildren;
  695. if (property == null)
  696. return;
  697. var count = property.arraySize;
  698. var changed = false;
  699. for (int i = count - 1; i >= 0; i--)
  700. {
  701. if (property.GetArrayElementAtIndex(i).boolValue)
  702. {
  703. count = i;
  704. changed = true;
  705. }
  706. else
  707. {
  708. break;
  709. }
  710. }
  711. if (changed)
  712. property.arraySize = count;
  713. }
  714. /************************************************************************************************************************/
  715. }
  716. /************************************************************************************************************************/
  717. #endif
  718. /************************************************************************************************************************/
  719. }
  720. }