AnimancerLayerDrawer.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_EDITOR && UNITY_IMGUI
  3. using System;
  4. using System.Collections.Generic;
  5. using UnityEditor;
  6. using UnityEngine;
  7. using static Animancer.Editor.AnimancerGUI;
  8. using Object = UnityEngine.Object;
  9. namespace Animancer.Editor
  10. {
  11. /// <summary>[Editor-Only]
  12. /// A custom Inspector for an <see cref="AnimancerLayer"/> which sorts and exposes some of its internal values.
  13. /// </summary>
  14. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimancerLayerDrawer
  15. ///
  16. [CustomGUI(typeof(AnimancerLayer))]
  17. public class AnimancerLayerDrawer : AnimancerNodeDrawer<AnimancerLayer>
  18. {
  19. /************************************************************************************************************************/
  20. /// <summary>The states in the target layer which have non-zero <see cref="AnimancerNode.Weight"/>.</summary>
  21. public readonly List<AnimancerState> ActiveStates = new();
  22. /// <summary>The states in the target layer which have zero <see cref="AnimancerNode.Weight"/>.</summary>
  23. public readonly List<AnimancerState> InactiveStates = new();
  24. /************************************************************************************************************************/
  25. #region Gathering
  26. /************************************************************************************************************************/
  27. /// <summary>Initializes an editor in the list for each layer in the `graph`.</summary>
  28. /// <remarks>
  29. /// The `count` indicates the number of elements actually being used.
  30. /// Spare elements are kept in the list in case they need to be used again later.
  31. /// </remarks>
  32. internal static void GatherLayerEditors(
  33. AnimancerGraph graph,
  34. List<AnimancerLayerDrawer> editors,
  35. out int count)
  36. {
  37. count = graph.Layers.Count;
  38. for (int i = 0; i < count; i++)
  39. {
  40. AnimancerLayerDrawer editor;
  41. if (editors.Count <= i)
  42. {
  43. editor = new();
  44. editors.Add(editor);
  45. }
  46. else
  47. {
  48. editor = editors[i];
  49. }
  50. editor.GatherStates(graph.Layers[i]);
  51. }
  52. }
  53. /************************************************************************************************************************/
  54. /// <summary>
  55. /// Sets the target `layer` and sorts its states and their keys into the active/inactive lists.
  56. /// </summary>
  57. private void GatherStates(AnimancerLayer layer)
  58. {
  59. Value = layer;
  60. ActiveStates.Clear();
  61. InactiveStates.Clear();
  62. foreach (var state in layer)
  63. {
  64. if (state.IsActive || !AnimancerGraphDrawer.SeparateActiveFromInactiveStates)
  65. {
  66. ActiveStates.Add(state);
  67. continue;
  68. }
  69. if (AnimancerGraphDrawer.ShowInactiveStates)
  70. InactiveStates.Add(state);
  71. }
  72. SortAndGatherKeys(ActiveStates);
  73. SortAndGatherKeys(InactiveStates);
  74. }
  75. /************************************************************************************************************************/
  76. /// <summary>
  77. /// Sorts any entries that use another state as their key to come right after that state.
  78. /// See <see cref="AnimancerLayer.Play(AnimancerState, float, FadeMode)"/>.
  79. /// </summary>
  80. private static void SortAndGatherKeys(List<AnimancerState> states)
  81. {
  82. var count = states.Count;
  83. if (count == 0)
  84. return;
  85. AnimancerGraphDrawer.ApplySortStatesByName(states);
  86. // Sort any states that use another state as their key to be right after the key.
  87. for (int i = 0; i < count; i++)
  88. {
  89. var state = states[i];
  90. var key = state.Key;
  91. if (key is not AnimancerState keyState)
  92. continue;
  93. var keyStateIndex = states.IndexOf(keyState);
  94. if (keyStateIndex < 0 || keyStateIndex + 1 == i)
  95. continue;
  96. states.RemoveAt(i);
  97. if (keyStateIndex < i)
  98. keyStateIndex++;
  99. states.Insert(keyStateIndex, state);
  100. i--;
  101. }
  102. }
  103. /************************************************************************************************************************/
  104. #endregion
  105. /************************************************************************************************************************/
  106. /// <summary>Draws the layer's name and weight.</summary>
  107. protected override void DoLabelGUI(Rect area)
  108. {
  109. var label = Value.IsAdditive ? "Additive" : "Override";
  110. if (Value._Mask != null)
  111. label = $"{label} ({Value._Mask.GetCachedName()})";
  112. area.xMin += FoldoutIndent;
  113. DoWeightLabel(ref area, Value.Weight, Value.EffectiveWeight);
  114. EditorGUIUtility.labelWidth -= FoldoutIndent;
  115. EditorGUI.LabelField(area, Value.ToString(), label);
  116. EditorGUIUtility.labelWidth += FoldoutIndent;
  117. }
  118. /************************************************************************************************************************/
  119. /// <summary>The number of pixels of indentation required to fit the foldout arrow.</summary>
  120. const float FoldoutIndent = 12;
  121. /// <inheritdoc/>
  122. protected override void DoFoldoutGUI(Rect area)
  123. {
  124. var hierarchyMode = EditorGUIUtility.hierarchyMode;
  125. EditorGUIUtility.hierarchyMode = true;
  126. area.xMin += FoldoutIndent;
  127. IsExpanded = EditorGUI.Foldout(area, IsExpanded, GUIContent.none, true);
  128. EditorGUIUtility.hierarchyMode = hierarchyMode;
  129. }
  130. /************************************************************************************************************************/
  131. /// <inheritdoc/>
  132. protected override void DoDetailsGUI()
  133. {
  134. EditorGUI.indentLevel++;
  135. base.DoDetailsGUI();
  136. if (IsExpanded)
  137. {
  138. GUILayout.BeginHorizontal();
  139. GUILayout.Space(FoldoutIndent);
  140. GUILayout.BeginVertical();
  141. DoLayerDetailsGUI();
  142. DoNodeDetailsGUI();
  143. GUILayout.EndVertical();
  144. GUILayout.EndHorizontal();
  145. }
  146. EditorGUI.indentLevel--;
  147. DoStatesGUI();
  148. }
  149. /************************************************************************************************************************/
  150. /// <summary>
  151. /// Draws controls for <see cref="AnimancerLayer.IsAdditive"/> and <see cref="AnimancerLayer._Mask"/>.
  152. /// </summary>
  153. private void DoLayerDetailsGUI()
  154. {
  155. var area = LayoutSingleLineRect(SpacingMode.Before);
  156. area = EditorGUI.IndentedRect(area);
  157. area.xMin += ExtraLeftPadding;
  158. var labelWidth = EditorGUIUtility.labelWidth;
  159. var indentLevel = EditorGUI.indentLevel;
  160. EditorGUI.indentLevel = 0;
  161. var additiveLabel = "Is Additive";
  162. var additiveWidth = GUI.skin.toggle.CalculateWidth(additiveLabel) + StandardSpacing * 2;
  163. var additiveArea = StealFromLeft(ref area, additiveWidth, StandardSpacing);
  164. var maskArea = area;
  165. // Additive.
  166. EditorGUIUtility.labelWidth = CalculateLabelWidth(additiveLabel);
  167. EditorGUI.BeginChangeCheck();
  168. var isAdditive = EditorGUI.Toggle(additiveArea, additiveLabel, Value.IsAdditive);
  169. if (EditorGUI.EndChangeCheck())
  170. Value.IsAdditive = isAdditive;
  171. // Mask.
  172. using (var label = PooledGUIContent.Acquire("Mask"))
  173. {
  174. EditorGUIUtility.labelWidth = CalculateLabelWidth(label.text);
  175. EditorGUI.BeginChangeCheck();
  176. var mask = DoObjectFieldGUI(maskArea, label, Value.Mask, false);
  177. if (EditorGUI.EndChangeCheck())
  178. Value.Mask = mask;
  179. }
  180. EditorGUI.indentLevel = indentLevel;
  181. EditorGUIUtility.labelWidth = labelWidth;
  182. }
  183. /************************************************************************************************************************/
  184. private void DoStatesGUI()
  185. {
  186. if (!AnimancerGraphDrawer.ShowInactiveStates)
  187. {
  188. DoStatesGUI("Active States", ActiveStates);
  189. }
  190. else if (AnimancerGraphDrawer.SeparateActiveFromInactiveStates)
  191. {
  192. DoStatesGUI("Active States", ActiveStates);
  193. DoStatesGUI("Inactive States", InactiveStates);
  194. }
  195. else
  196. {
  197. DoStatesGUI("States", ActiveStates);
  198. }
  199. if (Value.Weight != 0 &&
  200. !Value.IsAdditive &&
  201. !Mathf.Approximately(Value.GetTotalChildWeight(), 1))
  202. {
  203. var message =
  204. "The total Weight of all states in this layer does not equal 1" +
  205. " which will likely give undesirable results.";
  206. if (AreAllStatesFadingOut())
  207. message +=
  208. " If you no longer want anything playing on a layer," +
  209. " you should fade out that layer instead of fading out its states.";
  210. message += " Click here for more information.";
  211. EditorGUILayout.HelpBox(message, MessageType.Warning);
  212. if (TryUseClickEventInLastRect())
  213. EditorUtility.OpenWithDefaultApp(Strings.DocsURLs.Layers);
  214. }
  215. }
  216. /************************************************************************************************************************/
  217. /// <summary>Are all the target's states fading out to 0?</summary>
  218. private bool AreAllStatesFadingOut()
  219. {
  220. var count = Value.ActiveStates.Count;
  221. if (count == 0)
  222. return false;
  223. for (int i = 0; i < count; i++)
  224. {
  225. var state = Value.ActiveStates[i];
  226. if (state.TargetWeight != 0)
  227. return false;
  228. }
  229. return true;
  230. }
  231. /************************************************************************************************************************/
  232. /// <summary>Draws all `states` in the given list.</summary>
  233. public void DoStatesGUI(string label, List<AnimancerState> states)
  234. {
  235. var area = LayoutSingleLineRect();
  236. const string Label = "Weight";
  237. var width = CalculateLabelWidth(Label);
  238. GUI.Label(StealFromRight(ref area, width), Label);
  239. EditorGUI.LabelField(area, label, states.Count.ToStringCached());
  240. EditorGUI.indentLevel++;
  241. for (int i = 0; i < states.Count; i++)
  242. {
  243. DoStateGUI(states[i]);
  244. }
  245. EditorGUI.indentLevel--;
  246. }
  247. /************************************************************************************************************************/
  248. /// <summary>Cached Inspectors that have already been created for states.</summary>
  249. private readonly Dictionary<AnimancerState, ICustomGUI>
  250. StateInspectors = new();
  251. /// <summary>Draws the Inspector for the given `state`.</summary>
  252. private void DoStateGUI(AnimancerState state)
  253. {
  254. if (!StateInspectors.TryGetValue(state, out var inspector))
  255. {
  256. inspector = CustomGUIFactory.GetOrCreateForObject(state);
  257. StateInspectors.Add(state, inspector);
  258. }
  259. inspector?.DoGUI();
  260. DoChildStatesGUI(state);
  261. }
  262. /************************************************************************************************************************/
  263. /// <summary>Draws all child states of the `state`.</summary>
  264. private void DoChildStatesGUI(AnimancerState state)
  265. {
  266. if (!state._IsInspectorExpanded)
  267. return;
  268. EditorGUI.indentLevel++;
  269. foreach (var child in state)
  270. if (child != null)
  271. DoStateGUI(child);
  272. EditorGUI.indentLevel--;
  273. }
  274. /************************************************************************************************************************/
  275. /// <inheritdoc/>
  276. protected override void DoHeaderGUI()
  277. {
  278. if (!AnimancerGraphDrawer.ShowSingleLayerHeader &&
  279. Value.Graph.Layers.Count == 1 &&
  280. Value.Weight == 1 &&
  281. Value.TargetWeight == 1 &&
  282. Value.Speed == 1 &&
  283. !Value.IsAdditive &&
  284. Value._Mask == null &&
  285. Value.Graph.Component != null &&
  286. Value.Graph.Component.Animator != null &&
  287. Value.Graph.Component.Animator.runtimeAnimatorController == null)
  288. return;
  289. base.DoHeaderGUI();
  290. }
  291. /************************************************************************************************************************/
  292. /// <inheritdoc/>
  293. public override void DoGUI()
  294. {
  295. if (!Value.IsValid())
  296. return;
  297. base.DoGUI();
  298. var area = GUILayoutUtility.GetLastRect();
  299. HandleDragAndDropToPlay(area, Value);
  300. }
  301. /************************************************************************************************************************/
  302. /// <summary>
  303. /// If <see cref="AnimationClip"/>s or <see cref="IAnimationClipSource"/>s are dropped inside the `dropArea`,
  304. /// this method creates a new state in the `target` for each animation.
  305. /// </summary>
  306. public static void HandleDragAndDropToPlay(Rect area, object layerOrGraph)
  307. {
  308. if (layerOrGraph == null)
  309. return;
  310. _DragAndDropPlayTarget = layerOrGraph;
  311. _DragAndDropPlayHandler ??= HandleDragAndDropToPlay;
  312. _DragAndDropPlayHandler.Handle(area);
  313. _DragAndDropPlayTarget = null;
  314. }
  315. private static DragAndDropHandler<Object> _DragAndDropPlayHandler;
  316. private static object _DragAndDropPlayTarget;
  317. private static AnimancerLayer DragAndDropPlayTargetLayer
  318. => _DragAndDropPlayTarget as AnimancerLayer
  319. ?? (_DragAndDropPlayTarget is AnimancerGraph graph ? graph.Layers[0] : null);
  320. /// <summary>Handles drag and drop events to play animations and transitions.</summary>
  321. public static bool HandleDragAndDropToPlay(Object obj, bool isDrop)
  322. {
  323. if (_DragAndDropPlayTarget == null)
  324. return false;
  325. if (obj is AnimationClip clip)
  326. {
  327. if (clip.legacy)
  328. return false;
  329. if (isDrop)
  330. DragAndDropPlayTargetLayer.Play(clip);
  331. return true;
  332. }
  333. if (obj is ITransition transition)
  334. {
  335. if (isDrop)
  336. DragAndDropPlayTargetLayer.Play(transition);
  337. return true;
  338. }
  339. var transitionAsset = TryCreateTransitionAttribute.TryCreateTransitionAsset(obj);
  340. if (transitionAsset != null)
  341. {
  342. if (isDrop)
  343. DragAndDropPlayTargetLayer.Play(transitionAsset);
  344. if (!EditorUtility.IsPersistent(transitionAsset))
  345. Object.DestroyImmediate(transitionAsset);
  346. return true;
  347. }
  348. using (ListPool<AnimationClip>.Instance.Acquire(out var clips))
  349. {
  350. clips.GatherFromSource(obj);
  351. var anyValid = false;
  352. for (int i = 0; i < clips.Count; i++)
  353. {
  354. clip = clips[i];
  355. if (clip.legacy)
  356. continue;
  357. if (!isDrop)
  358. return true;
  359. anyValid = true;
  360. DragAndDropPlayTargetLayer.Play(clip);
  361. }
  362. if (anyValid)
  363. return true;
  364. }
  365. return false;
  366. }
  367. /************************************************************************************************************************/
  368. #region Context Menu
  369. /************************************************************************************************************************/
  370. /// <inheritdoc/>
  371. protected override void PopulateContextMenu(GenericMenu menu)
  372. {
  373. menu.AddDisabledItem(new($"{DetailsPrefix}{nameof(Value.CurrentState)}: {Value.CurrentState}"));
  374. menu.AddDisabledItem(new($"{DetailsPrefix}{nameof(Value.CommandCount)}: {Value.CommandCount}"));
  375. menu.AddFunction("Stop",
  376. HasAnyStates((state) => state.IsPlaying || state.Weight != 0),
  377. () => Value.Stop());
  378. AnimancerEditorUtilities.AddFadeFunction(menu, "Fade In",
  379. Value.Index > 0 && Value.Weight != 1, Value,
  380. (duration) => Value.StartFade(1, duration));
  381. AnimancerEditorUtilities.AddFadeFunction(menu, "Fade Out",
  382. Value.Index > 0 && Value.Weight != 0, Value,
  383. (duration) => Value.StartFade(0, duration));
  384. AnimancerNodeBase.AddContextMenuIK(menu, Value);
  385. menu.AddSeparator("");
  386. menu.AddFunction("Destroy States",
  387. ActiveStates.Count > 0 || InactiveStates.Count > 0,
  388. () => Value.DestroyStates());
  389. AnimancerGraphDrawer.AddRootFunctions(menu, Value.Graph);
  390. menu.AddSeparator("");
  391. AnimancerGraphDrawer.AddDisplayOptions(menu);
  392. AnimancerEditorUtilities.AddDocumentationLink(menu, "Layer Documentation", Strings.DocsURLs.Layers);
  393. menu.ShowAsContext();
  394. }
  395. /************************************************************************************************************************/
  396. private bool HasAnyStates(Func<AnimancerState, bool> condition)
  397. {
  398. foreach (var state in Value)
  399. {
  400. if (condition(state))
  401. return true;
  402. }
  403. return false;
  404. }
  405. /************************************************************************************************************************/
  406. #endregion
  407. /************************************************************************************************************************/
  408. }
  409. }
  410. #endif