// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2023 Kybernetik //
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
namespace Animancer.Editor
{
/// [Editor-Only] A custom Inspector for s.
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimancerComponentEditor
///
[CustomEditor(typeof(AnimancerComponent), true), CanEditMultipleObjects]
public class AnimancerComponentEditor : BaseAnimancerComponentEditor
{
/************************************************************************************************************************/
private bool _ShowResetOnDisableWarning;
protected override bool DoOverridePropertyGUI(string path, SerializedProperty property, GUIContent label)
{
if (path == Targets[0].AnimatorFieldName)
{
DoAnimatorGUI(property, label);
return true;
}
if (path == Targets[0].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();
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 (AnimancerPlayable.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();
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();
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;
}
}
}
}
/************************************************************************************************************************/
}
}
#endif