Files
test/Assets/Plugins/Animancer/Internal/Editor/GUI/NamedAnimancerComponentEditor.cs
2026-03-02 20:38:51 +07:00

325 lines
12 KiB
C#

// Animancer // https://kybernetik.com.au/animancer // Copyright 2021 Kybernetik //
#if UNITY_EDITOR
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Animancer.Editor
{
/// <summary>[Editor-Only] A custom Inspector for <see cref="NamedAnimancerComponent"/>s.</summary>
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/NamedAnimancerComponentEditor
///
[CustomEditor(typeof(NamedAnimancerComponent), true), CanEditMultipleObjects]
public class NamedAnimancerComponentEditor : AnimancerComponentEditor
{
/************************************************************************************************************************/
/// <summary>[Editor-Only]
/// Draws any custom GUI for the `property`. The return value indicates whether the GUI should replace the
/// regular call to <see cref="EditorGUILayout.PropertyField"/> or not.
/// </summary>
protected override bool DoOverridePropertyGUI(string path, SerializedProperty property, GUIContent label)
{
switch (path)
{
case "_PlayAutomatically":
if (ShouldShowAnimationFields())
DoDefaultAnimationField(property);
return true;
case "_Animations":
if (ShouldShowAnimationFields())
{
DoAnimationsField(property);
}
EditorGUILayout.Space();
if (GUILayout.Button("AddAllAnimationClips"))
{
AddAllAnimationClips();
}
return true;
default:
return base.DoOverridePropertyGUI(path, property, label);
}
}
/************************************************************************************************************************/
/// <summary>
/// The <see cref="NamedAnimancerComponent.PlayAutomatically"/> and
/// <see cref="NamedAnimancerComponent.Animations"/> fields are only used on startup, so we don't need to show
/// them in Play Mode after the object is already enabled.
/// </summary>
private bool ShouldShowAnimationFields()
{
if (!EditorApplication.isPlaying)
return true;
for (int i = 0; i < Targets.Length; i++)
{
if (!Targets[i].IsPlayableInitialized)
return true;
}
return false;
}
/************************************************************************************************************************/
private void DoDefaultAnimationField(SerializedProperty playAutomatically)
{
var area = AnimancerGUI.LayoutSingleLineRect();
var playAutomaticallyWidth = EditorGUIUtility.labelWidth + AnimancerGUI.ToggleWidth;
var playAutomaticallyArea = AnimancerGUI.StealFromLeft(ref area, playAutomaticallyWidth);
using (ObjectPool.Disposable.AcquireContent(out var label, playAutomatically))
EditorGUI.PropertyField(playAutomaticallyArea, playAutomatically, label);
SerializedProperty firstElement;
AnimationClip clip;
var animations = serializedObject.FindProperty("_Animations");
if (animations.arraySize > 0)
{
firstElement = animations.GetArrayElementAtIndex(0);
clip = (AnimationClip)firstElement.objectReferenceValue;
EditorGUI.BeginProperty(area, null, firstElement);
}
else
{
firstElement = null;
clip = null;
EditorGUI.BeginProperty(area, null, animations);
}
EditorGUI.BeginChangeCheck();
var indentLevel = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
clip = (AnimationClip)EditorGUI.ObjectField(area, GUIContent.none, clip, typeof(AnimationClip), true);
EditorGUI.indentLevel = indentLevel;
if (EditorGUI.EndChangeCheck())
{
if (clip != null)
{
if (firstElement == null)
{
animations.arraySize = 1;
firstElement = animations.GetArrayElementAtIndex(0);
}
firstElement.objectReferenceValue = clip;
}
else
{
if (firstElement == null || animations.arraySize == 1)
animations.arraySize = 0;
else
firstElement.objectReferenceValue = clip;
}
}
EditorGUI.EndProperty();
}
/************************************************************************************************************************/
private ReorderableList _Animations;
private static int _RemoveAnimationIndex;
private void DoAnimationsField(SerializedProperty property)
{
GUILayout.Space(AnimancerGUI.StandardSpacing - 1);
if (_Animations == null)
{
_Animations = new ReorderableList(property.serializedObject, property.Copy())
{
drawHeaderCallback = DrawAnimationsHeader,
drawElementCallback = DrawAnimationElement,
elementHeight = AnimancerGUI.LineHeight,
onRemoveCallback = RemoveSelectedElement,
};
}
_RemoveAnimationIndex = -1;
GUILayout.BeginVertical();
_Animations.DoLayoutList();
GUILayout.EndVertical();
if (_RemoveAnimationIndex >= 0)
{
property.DeleteArrayElementAtIndex(_RemoveAnimationIndex);
}
AnimancerGUI.HandleDragAndDropAnimations(GUILayoutUtility.GetLastRect(), (clip) =>
{
var index = property.arraySize;
property.arraySize = index + 1;
var element = property.GetArrayElementAtIndex(index);
element.objectReferenceValue = clip;
property.serializedObject.ApplyModifiedProperties();
});
}
/************************************************************************************************************************/
private SerializedProperty _AnimationsArraySize;
private void DrawAnimationsHeader(Rect area)
{
var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth -= 6;
area.width += 5;
var property = _Animations.serializedProperty;
using (ObjectPool.Disposable.AcquireContent(out var label, property))
{
label = EditorGUI.BeginProperty(area, label, property);
if (_AnimationsArraySize == null)
{
_AnimationsArraySize = property.Copy();
_AnimationsArraySize.Next(true);
_AnimationsArraySize.Next(true);
}
EditorGUI.PropertyField(area, _AnimationsArraySize, label);
EditorGUI.EndProperty();
}
EditorGUIUtility.labelWidth = labelWidth;
}
/************************************************************************************************************************/
private static readonly HashSet<Object>
PreviousAnimations = new HashSet<Object>();
private void DrawAnimationElement(Rect area, int index, bool isActive, bool isFocused)
{
if (index == 0)
PreviousAnimations.Clear();
var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth -= 20;
var element = _Animations.serializedProperty.GetArrayElementAtIndex(index);
var color = GUI.color;
var animation = element.objectReferenceValue;
if (animation == null || PreviousAnimations.Contains(animation))
GUI.color = AnimancerGUI.WarningFieldColor;
else
PreviousAnimations.Add(animation);
EditorGUI.BeginChangeCheck();
EditorGUI.ObjectField(area, element, GUIContent.none);
if (EditorGUI.EndChangeCheck() && element.objectReferenceValue == null)
_RemoveAnimationIndex = index;
GUI.color = color;
EditorGUIUtility.labelWidth = labelWidth;
}
/************************************************************************************************************************/
private static void RemoveSelectedElement(ReorderableList list)
{
var property = list.serializedProperty;
var element = property.GetArrayElementAtIndex(list.index);
// Deleting a non-null element sets it to null, so we make sure it's null to actually remove it.
if (element.objectReferenceValue != null)
element.objectReferenceValue = null;
property.DeleteArrayElementAtIndex(list.index);
if (list.index >= property.arraySize - 1)
list.index = property.arraySize - 1;
}
/************************************************************************************************************************/
const string DEFAULT_ANIMATION_CLIP_NAME = "站立";
private void AddAllAnimationClips()
{
AnimationClip defaultAnimationClip = null;
var component = (NamedAnimancerComponent)target;
var animator = component.GetComponent<Animator>();
if (animator == null)
{
animator = component.GetComponentInChildren<Animator>();
if (animator == null)
{
EditorUtility.DisplayDialog("NamedAnimancerComponent", "No Animator found on the same GameObject.", "OK");
return;
}
}
var controller = animator.runtimeAnimatorController;
if (controller == null)
{
EditorUtility.DisplayDialog("NamedAnimancerComponent", "Animator has no RuntimeAnimatorController.", "OK");
return;
}
var clipsFromAnimator = controller.animationClips;
if (clipsFromAnimator == null || clipsFromAnimator.Length == 0)
{
EditorUtility.DisplayDialog("NamedAnimancerComponent", "No AnimationClips found in the Animator.", "OK");
return;
}
var currentClips = component.Animations ?? System.Array.Empty<AnimationClip>();
var combined = new List<AnimationClip>(currentClips.Length + clipsFromAnimator.Length);
combined.AddRange(currentClips.Where(c => c != null));
foreach (var clip in clipsFromAnimator)
{
if (clip == null)
continue;
if (!combined.Contains(clip))
{
combined.Add(clip);
if (clip.name.Contains(DEFAULT_ANIMATION_CLIP_NAME))
{
defaultAnimationClip = clip;
}
}
}
if (defaultAnimationClip != null)
{
// set looping for the default animation clip
AnimancerEditorUtilities.SetLooping(defaultAnimationClip, true);
}
Undo.RecordObject(component, "Add All Animation Clips");
component.Animations = combined.ToArray();
component.DefaultAnimation = defaultAnimationClip;
EditorUtility.SetDirty(component);
}
/************************************************************************************************************************/
}
}
#endif