AnimancerComponentEditor.cs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_EDITOR
  3. using UnityEditor;
  4. using UnityEngine;
  5. namespace Animancer.Editor
  6. {
  7. /// <summary>[Editor-Only] A custom Inspector for <see cref="AnimancerComponent"/>s.</summary>
  8. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimancerComponentEditor
  9. ///
  10. [CustomEditor(typeof(AnimancerComponent), true), CanEditMultipleObjects]
  11. public class AnimancerComponentEditor : BaseAnimancerComponentEditor
  12. {
  13. /************************************************************************************************************************/
  14. private bool _ShowResetOnDisableWarning;
  15. /// <inheritdoc/>
  16. protected override bool DoOverridePropertyGUI(string path, SerializedProperty property, GUIContent label)
  17. {
  18. var target = Targets[0];
  19. if (path == target.AnimatorFieldName)
  20. {
  21. DoAnimatorGUI(property, label);
  22. return true;
  23. }
  24. if (path == target.ActionOnDisableFieldName)
  25. {
  26. DoActionOnDisableGUI(property, label);
  27. return true;
  28. }
  29. return base.DoOverridePropertyGUI(path, property, label);
  30. }
  31. /************************************************************************************************************************/
  32. private void DoAnimatorGUI(SerializedProperty property, GUIContent label)
  33. {
  34. var animator = property.objectReferenceValue as Animator;
  35. var color = GUI.color;
  36. if (animator == null)
  37. GUI.color = AnimancerGUI.WarningFieldColor;
  38. EditorGUILayout.PropertyField(property, label);
  39. if (animator == null)
  40. {
  41. GUI.color = color;
  42. EditorGUILayout.HelpBox($"An {nameof(Animator)} is required in order to play animations." +
  43. " Click here to search for one nearby.",
  44. MessageType.Warning);
  45. if (AnimancerGUI.TryUseClickEventInLastRect())
  46. {
  47. Serialization.ForEachTarget(property, (targetProperty) =>
  48. {
  49. var target = (IAnimancerComponent)targetProperty.serializedObject.targetObject;
  50. animator = target.gameObject.GetComponentInParentOrChildren<Animator>();
  51. if (animator == null)
  52. {
  53. Debug.Log($"No {nameof(Animator)} found on '{target.gameObject.name}' or any of its parents or children." +
  54. " You must assign one manually.", target.gameObject);
  55. return;
  56. }
  57. targetProperty.objectReferenceValue = animator;
  58. });
  59. }
  60. }
  61. else
  62. {
  63. if (!animator.enabled)
  64. {
  65. EditorGUILayout.HelpBox(Strings.AnimatorDisabledMessage, MessageType.Warning);
  66. if (AnimancerGUI.TryUseClickEventInLastRect())
  67. {
  68. Undo.RecordObject(animator, "Inspector");
  69. animator.enabled = true;
  70. }
  71. }
  72. if (animator.gameObject != Targets[0].gameObject)
  73. {
  74. EditorGUILayout.HelpBox(
  75. $"It is recommended that you keep this component on the same {nameof(GameObject)}" +
  76. $" as its target {nameof(Animator)} so that they get enabled and disabled at the same time.",
  77. MessageType.Info);
  78. }
  79. var initialUpdateMode = Targets[0].InitialUpdateMode;
  80. var updateMode = animator.updateMode;
  81. if (AnimancerGraphCleanup.HasChangedToOrFromAnimatePhysics(initialUpdateMode, updateMode))
  82. {
  83. EditorGUILayout.HelpBox(
  84. $"Changing to or from " +
  85. #if UNITY_2023_1_OR_NEWER
  86. $"{nameof(AnimatorUpdateMode.Fixed)}" +
  87. #else
  88. $"{nameof(AnimatorUpdateMode.AnimatePhysics)}" +
  89. #endif
  90. $" mode at runtime has no effect when using the Playables API." +
  91. $" It will continue using the original mode it had on startup.",
  92. MessageType.Warning);
  93. if (AnimancerGUI.TryUseClickEventInLastRect())
  94. EditorUtility.OpenWithDefaultApp(Strings.DocsURLs.UpdateModes);
  95. }
  96. }
  97. }
  98. /************************************************************************************************************************/
  99. private void DoActionOnDisableGUI(SerializedProperty property, GUIContent label)
  100. {
  101. EditorGUILayout.PropertyField(property, label, true);
  102. if (property.enumValueIndex == (int)AnimancerComponent.DisableAction.Reset)
  103. {
  104. // Since getting all the components creates garbage, only do it during layout events.
  105. if (Event.current.type == EventType.Layout)
  106. {
  107. _ShowResetOnDisableWarning = !AreAllResettingTargetsAboveTheirAnimator();
  108. }
  109. if (_ShowResetOnDisableWarning)
  110. {
  111. EditorGUILayout.HelpBox("Reset only works if this component is above the Animator" +
  112. " so OnDisable can perform the Reset before the Animator actually gets disabled." +
  113. " Click here to fix." +
  114. "\n\nOtherwise you can use Stop and call Animator.Rebind before disabling this GameObject.",
  115. MessageType.Error);
  116. if (AnimancerGUI.TryUseClickEventInLastRect())
  117. MoveResettingTargetsAboveTheirAnimator();
  118. }
  119. }
  120. }
  121. /************************************************************************************************************************/
  122. private bool AreAllResettingTargetsAboveTheirAnimator()
  123. {
  124. for (int i = 0; i < Targets.Length; i++)
  125. {
  126. var target = Targets[i];
  127. if (!target.ResetOnDisable)
  128. continue;
  129. var animator = target.Animator;
  130. if (animator == null ||
  131. target.gameObject != animator.gameObject)
  132. continue;
  133. var targetObject = (Object)target;
  134. var components = target.gameObject.GetComponents<Component>();
  135. for (int j = 0; j < components.Length; j++)
  136. {
  137. var component = components[j];
  138. if (component == targetObject)
  139. break;
  140. else if (component == animator)
  141. return false;
  142. }
  143. }
  144. return true;
  145. }
  146. /************************************************************************************************************************/
  147. private void MoveResettingTargetsAboveTheirAnimator()
  148. {
  149. for (int i = 0; i < Targets.Length; i++)
  150. {
  151. var target = Targets[i];
  152. if (!target.ResetOnDisable)
  153. continue;
  154. var animator = target.Animator;
  155. if (animator == null ||
  156. target.gameObject != animator.gameObject)
  157. continue;
  158. int animatorIndex = -1;
  159. var targetObject = (Object)target;
  160. var components = target.gameObject.GetComponents<Component>();
  161. for (int j = 0; j < components.Length; j++)
  162. {
  163. var component = components[j];
  164. if (component == targetObject)
  165. {
  166. if (animatorIndex >= 0)
  167. {
  168. var count = j - animatorIndex;
  169. while (count-- > 0)
  170. UnityEditorInternal.ComponentUtility.MoveComponentUp((Component)target);
  171. }
  172. break;
  173. }
  174. else if (component == animator)
  175. {
  176. animatorIndex = j;
  177. }
  178. }
  179. }
  180. }
  181. /************************************************************************************************************************/
  182. private const string InitializeGraphFunction =
  183. "CONTEXT/" + nameof(AnimancerComponent) + "/Initialize Animancer Graph";
  184. /// <summary>Context menu function to call <see cref="AnimancerComponent.InitializeGraph"/>.</summary>
  185. [MenuItem(InitializeGraphFunction)]
  186. private static void InitializeGraph(MenuCommand command)
  187. {
  188. if (command.context is AnimancerComponent animancer &&
  189. animancer.Graph.Layers.Count < 1)
  190. animancer.Graph.Layers.Count = 1;
  191. }
  192. /// <summary>Should <see cref="InitializeGraph"/> be enabled?</summary>
  193. [MenuItem(InitializeGraphFunction, validate = true)]
  194. private static bool InitializeGraphValidate(MenuCommand command)
  195. => command.context is AnimancerComponent animancer
  196. && (!animancer.IsGraphInitialized || animancer.Graph.Layers.Count < 1);
  197. /************************************************************************************************************************/
  198. }
  199. }
  200. #endif