com.alicizax.kybernetik.ani.../Editor/GUI/Object Editors/Animancer Component/AnimancerComponentEditor.cs
陈思海 3e7c253249 init
2025-01-08 15:26:57 +08:00

243 lines
9.6 KiB
C#

// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
namespace Animancer.Editor
{
/// <summary>[Editor-Only] A custom Inspector for <see cref="AnimancerComponent"/>s.</summary>
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimancerComponentEditor
///
[CustomEditor(typeof(AnimancerComponent), true), CanEditMultipleObjects]
public class AnimancerComponentEditor : BaseAnimancerComponentEditor
{
/************************************************************************************************************************/
private bool _ShowResetOnDisableWarning;
/// <inheritdoc/>
protected override bool DoOverridePropertyGUI(string path, SerializedProperty property, GUIContent label)
{
var target = Targets[0];
if (path == target.AnimatorFieldName)
{
DoAnimatorGUI(property, label);
return true;
}
if (path == target.ActionOnDisableFieldName)
{
DoActionOnDisableGUI(property, label);
return true;
}
return base.DoOverridePropertyGUI(path, property, label);
}
/************************************************************************************************************************/
private void DoAnimatorGUI(SerializedProperty property, GUIContent label)
{
var animator = property.objectReferenceValue as Animator;
var color = GUI.color;
if (animator == null)
GUI.color = AnimancerGUI.WarningFieldColor;
EditorGUILayout.PropertyField(property, label);
if (animator == null)
{
GUI.color = color;
EditorGUILayout.HelpBox($"An {nameof(Animator)} is required in order to play animations." +
" Click here to search for one nearby.",
MessageType.Warning);
if (AnimancerGUI.TryUseClickEventInLastRect())
{
Serialization.ForEachTarget(property, (targetProperty) =>
{
var target = (IAnimancerComponent)targetProperty.serializedObject.targetObject;
animator = target.gameObject.GetComponentInParentOrChildren<Animator>();
if (animator == null)
{
Debug.Log($"No {nameof(Animator)} found on '{target.gameObject.name}' or any of its parents or children." +
" You must assign one manually.", target.gameObject);
return;
}
targetProperty.objectReferenceValue = animator;
});
}
}
else
{
if (!animator.enabled)
{
EditorGUILayout.HelpBox(Strings.AnimatorDisabledMessage, MessageType.Warning);
if (AnimancerGUI.TryUseClickEventInLastRect())
{
Undo.RecordObject(animator, "Inspector");
animator.enabled = true;
}
}
if (animator.gameObject != Targets[0].gameObject)
{
EditorGUILayout.HelpBox(
$"It is recommended that you keep this component on the same {nameof(GameObject)}" +
$" as its target {nameof(Animator)} so that they get enabled and disabled at the same time.",
MessageType.Info);
}
var initialUpdateMode = Targets[0].InitialUpdateMode;
var updateMode = animator.updateMode;
if (AnimancerGraphCleanup.HasChangedToOrFromAnimatePhysics(initialUpdateMode, updateMode))
{
EditorGUILayout.HelpBox(
$"Changing to or from " +
#if UNITY_2023_1_OR_NEWER
$"{nameof(AnimatorUpdateMode.Fixed)}" +
#else
$"{nameof(AnimatorUpdateMode.AnimatePhysics)}" +
#endif
$" mode at runtime has no effect when using the Playables API." +
$" It will continue using the original mode it had on startup.",
MessageType.Warning);
if (AnimancerGUI.TryUseClickEventInLastRect())
EditorUtility.OpenWithDefaultApp(Strings.DocsURLs.UpdateModes);
}
}
}
/************************************************************************************************************************/
private void DoActionOnDisableGUI(SerializedProperty property, GUIContent label)
{
EditorGUILayout.PropertyField(property, label, true);
if (property.enumValueIndex == (int)AnimancerComponent.DisableAction.Reset)
{
// Since getting all the components creates garbage, only do it during layout events.
if (Event.current.type == EventType.Layout)
{
_ShowResetOnDisableWarning = !AreAllResettingTargetsAboveTheirAnimator();
}
if (_ShowResetOnDisableWarning)
{
EditorGUILayout.HelpBox("Reset only works if this component is above the Animator" +
" so OnDisable can perform the Reset before the Animator actually gets disabled." +
" Click here to fix." +
"\n\nOtherwise you can use Stop and call Animator.Rebind before disabling this GameObject.",
MessageType.Error);
if (AnimancerGUI.TryUseClickEventInLastRect())
MoveResettingTargetsAboveTheirAnimator();
}
}
}
/************************************************************************************************************************/
private bool AreAllResettingTargetsAboveTheirAnimator()
{
for (int i = 0; i < Targets.Length; i++)
{
var target = Targets[i];
if (!target.ResetOnDisable)
continue;
var animator = target.Animator;
if (animator == null ||
target.gameObject != animator.gameObject)
continue;
var targetObject = (Object)target;
var components = target.gameObject.GetComponents<Component>();
for (int j = 0; j < components.Length; j++)
{
var component = components[j];
if (component == targetObject)
break;
else if (component == animator)
return false;
}
}
return true;
}
/************************************************************************************************************************/
private void MoveResettingTargetsAboveTheirAnimator()
{
for (int i = 0; i < Targets.Length; i++)
{
var target = Targets[i];
if (!target.ResetOnDisable)
continue;
var animator = target.Animator;
if (animator == null ||
target.gameObject != animator.gameObject)
continue;
int animatorIndex = -1;
var targetObject = (Object)target;
var components = target.gameObject.GetComponents<Component>();
for (int j = 0; j < components.Length; j++)
{
var component = components[j];
if (component == targetObject)
{
if (animatorIndex >= 0)
{
var count = j - animatorIndex;
while (count-- > 0)
UnityEditorInternal.ComponentUtility.MoveComponentUp((Component)target);
}
break;
}
else if (component == animator)
{
animatorIndex = j;
}
}
}
}
/************************************************************************************************************************/
private const string InitializeGraphFunction =
"CONTEXT/" + nameof(AnimancerComponent) + "/Initialize Animancer Graph";
/// <summary>Context menu function to call <see cref="AnimancerComponent.InitializeGraph"/>.</summary>
[MenuItem(InitializeGraphFunction)]
private static void InitializeGraph(MenuCommand command)
{
if (command.context is AnimancerComponent animancer &&
animancer.Graph.Layers.Count < 1)
animancer.Graph.Layers.Count = 1;
}
/// <summary>Should <see cref="InitializeGraph"/> be enabled?</summary>
[MenuItem(InitializeGraphFunction, validate = true)]
private static bool InitializeGraphValidate(MenuCommand command)
=> command.context is AnimancerComponent animancer
&& (!animancer.IsGraphInitialized || animancer.Graph.Layers.Count < 1);
/************************************************************************************************************************/
}
}
#endif