AnimancerComponentEditor.cs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 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. protected override bool DoOverridePropertyGUI(string path, SerializedProperty property, GUIContent label)
  16. {
  17. if (path == Targets[0].AnimatorFieldName)
  18. {
  19. DoAnimatorGUI(property, label);
  20. return true;
  21. }
  22. if (path == Targets[0].ActionOnDisableFieldName)
  23. {
  24. DoActionOnDisableGUI(property, label);
  25. return true;
  26. }
  27. return base.DoOverridePropertyGUI(path, property, label);
  28. }
  29. /************************************************************************************************************************/
  30. private void DoAnimatorGUI(SerializedProperty property, GUIContent label)
  31. {
  32. var hasAnimator = property.objectReferenceValue != null;
  33. var color = GUI.color;
  34. if (!hasAnimator)
  35. GUI.color = AnimancerGUI.WarningFieldColor;
  36. EditorGUILayout.PropertyField(property, label);
  37. if (!hasAnimator)
  38. {
  39. GUI.color = color;
  40. EditorGUILayout.HelpBox($"An {nameof(Animator)} is required in order to play animations." +
  41. " Click here to search for one nearby.",
  42. MessageType.Warning);
  43. if (AnimancerGUI.TryUseClickEventInLastRect())
  44. {
  45. Serialization.ForEachTarget(property, (targetProperty) =>
  46. {
  47. var target = (IAnimancerComponent)targetProperty.serializedObject.targetObject;
  48. var animator = target.gameObject.GetComponentInParentOrChildren<Animator>();
  49. if (animator == null)
  50. {
  51. Debug.Log($"No {nameof(Animator)} found on '{target.gameObject.name}' or any of its parents or children." +
  52. " You must assign one manually.", target.gameObject);
  53. return;
  54. }
  55. targetProperty.objectReferenceValue = animator;
  56. });
  57. }
  58. }
  59. else if (property.objectReferenceValue is Animator animator)
  60. {
  61. if (animator.gameObject != Targets[0].gameObject)
  62. {
  63. EditorGUILayout.HelpBox(
  64. $"It is recommended that you keep this component on the same {nameof(GameObject)}" +
  65. $" as its target {nameof(Animator)} so that they get enabled and disabled at the same time.",
  66. MessageType.Info);
  67. }
  68. var initialUpdateMode = Targets[0].InitialUpdateMode;
  69. var updateMode = animator.updateMode;
  70. if (AnimancerPlayable.HasChangedToOrFromAnimatePhysics(initialUpdateMode, updateMode))
  71. {
  72. EditorGUILayout.HelpBox(
  73. $"Changing to or from {nameof(AnimatorUpdateMode.AnimatePhysics)} mode at runtime has no effect" +
  74. $" when using the Playables API. It will continue using the original mode it had on startup.",
  75. MessageType.Warning);
  76. if (AnimancerGUI.TryUseClickEventInLastRect())
  77. EditorUtility.OpenWithDefaultApp(Strings.DocsURLs.UpdateModes);
  78. }
  79. }
  80. }
  81. /************************************************************************************************************************/
  82. private void DoActionOnDisableGUI(SerializedProperty property, GUIContent label)
  83. {
  84. EditorGUILayout.PropertyField(property, label, true);
  85. if (property.enumValueIndex == (int)AnimancerComponent.DisableAction.Reset)
  86. {
  87. // Since getting all the components creates garbage, only do it during layout events.
  88. if (Event.current.type == EventType.Layout)
  89. {
  90. _ShowResetOnDisableWarning = !AreAllResettingTargetsAboveTheirAnimator();
  91. }
  92. if (_ShowResetOnDisableWarning)
  93. {
  94. EditorGUILayout.HelpBox("Reset only works if this component is above the Animator" +
  95. " so OnDisable can perform the Reset before the Animator actually gets disabled." +
  96. " Click here to fix." +
  97. "\n\nOtherwise you can use Stop and call Animator.Rebind before disabling this GameObject.",
  98. MessageType.Error);
  99. if (AnimancerGUI.TryUseClickEventInLastRect())
  100. MoveResettingTargetsAboveTheirAnimator();
  101. }
  102. }
  103. }
  104. /************************************************************************************************************************/
  105. private bool AreAllResettingTargetsAboveTheirAnimator()
  106. {
  107. for (int i = 0; i < Targets.Length; i++)
  108. {
  109. var target = Targets[i];
  110. if (!target.ResetOnDisable)
  111. continue;
  112. var animator = target.Animator;
  113. if (animator == null ||
  114. target.gameObject != animator.gameObject)
  115. continue;
  116. var targetObject = (Object)target;
  117. var components = target.gameObject.GetComponents<Component>();
  118. for (int j = 0; j < components.Length; j++)
  119. {
  120. var component = components[j];
  121. if (component == targetObject)
  122. break;
  123. else if (component == animator)
  124. return false;
  125. }
  126. }
  127. return true;
  128. }
  129. /************************************************************************************************************************/
  130. private void MoveResettingTargetsAboveTheirAnimator()
  131. {
  132. for (int i = 0; i < Targets.Length; i++)
  133. {
  134. var target = Targets[i];
  135. if (!target.ResetOnDisable)
  136. continue;
  137. var animator = target.Animator;
  138. if (animator == null ||
  139. target.gameObject != animator.gameObject)
  140. continue;
  141. int animatorIndex = -1;
  142. var targetObject = (Object)target;
  143. var components = target.gameObject.GetComponents<Component>();
  144. for (int j = 0; j < components.Length; j++)
  145. {
  146. var component = components[j];
  147. if (component == targetObject)
  148. {
  149. if (animatorIndex >= 0)
  150. {
  151. var count = j - animatorIndex;
  152. while (count-- > 0)
  153. UnityEditorInternal.ComponentUtility.MoveComponentUp((Component)target);
  154. }
  155. break;
  156. }
  157. else if (component == animator)
  158. {
  159. animatorIndex = j;
  160. }
  161. }
  162. }
  163. }
  164. /************************************************************************************************************************/
  165. }
  166. }
  167. #endif