killapp/Assets/Editor/UIAnimatorInspector.cs

344 lines
14 KiB
C#
Raw Normal View History

using DG.Tweening;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
/// <summary>
/// UIAnimator 可视化编辑Inspector
/// - ReorderableList 编排动画步骤(增删/拖拽排序)
/// - 各步骤字段按类型动态显示
/// - 播放/反向/停止/重置 预览(编辑器内即时查看效果,不进入播放模式)
/// </summary>
[CustomEditor(typeof(UIAnimator))]
public class UIAnimatorInspector : Editor
{
private ReorderableList stepList;
private SerializedProperty stepsProp;
// 预览状态
private bool previewing;
private bool previewForward = true;
private float previewElapsed;
private float previewTotal;
private double lastPreviewTime;
private const float RowHeight = 18f;
private const float RowGap = 2f;
private void OnEnable()
{
stepsProp = serializedObject.FindProperty("steps");
stepList = new ReorderableList(serializedObject, stepsProp, true, true, true, true)
{
drawHeaderCallback = rect => EditorGUI.LabelField(rect, "动画步骤"),
drawElementCallback = DrawStepElement,
elementHeightCallback = index => RowHeight * 3 + RowGap * 4,
onAddCallback = OnAddStep
};
}
private void OnDisable()
{
StopPreview();
EditorApplication.update -= OnPreviewUpdate;
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(serializedObject.FindProperty("playOnAwake"));
EditorGUILayout.PropertyField(serializedObject.FindProperty("autoAddCanvasGroup"));
// 总时长提示
EditorGUILayout.HelpBox($"动画总时长: {((UIAnimator)target).TotalDuration:F2}s", MessageType.None);
stepList.DoLayoutList();
EditorGUILayout.Space();
DrawPreviewButtons();
serializedObject.ApplyModifiedProperties();
}
// ---------- 步骤绘制 ----------
private void DrawStepElement(Rect rect, int index, bool isActive, bool isFocused)
{
SerializedProperty prop = stepsProp.GetArrayElementAtIndex(index);
SerializedProperty typeProp = prop.FindPropertyRelative("type");
UIAnimator.UIAnimationType type = (UIAnimator.UIAnimationType)typeProp.enumValueIndex;
rect.y += RowGap;
float y = rect.y;
// 第1行类型
typeProp.enumValueIndex = (int)(UIAnimator.UIAnimationType)EditorGUI.EnumPopup(
new Rect(rect.x, y, rect.width, RowHeight), GUIContent.none, type);
y += RowHeight + RowGap;
// 第2行延迟 + 时长
float half = (rect.width - 6) * 0.5f;
EditorGUIUtility.labelWidth = 38f;
EditorGUI.PropertyField(new Rect(rect.x, y, half, RowHeight), prop.FindPropertyRelative("delay"));
EditorGUI.PropertyField(new Rect(rect.x + half + 6, y, half, RowHeight), prop.FindPropertyRelative("duration"));
EditorGUIUtility.labelWidth = 0f;
y += RowHeight + RowGap;
// 第3行缓动 + 类型专属字段
float easeW = half;
EditorGUI.LabelField(new Rect(rect.x, y, easeW, RowHeight), "缓动");
SerializedProperty easeProp = prop.FindPropertyRelative("ease");
easeProp.enumValueIndex = (int)(Ease)EditorGUI.EnumPopup(
new Rect(rect.x + 34f, y, easeW - 34f, RowHeight), GUIContent.none, (Ease)easeProp.enumValueIndex);
Rect valueRect = new Rect(rect.x + half + 6, y, half, RowHeight);
EditorGUIUtility.labelWidth = 42f;
switch (type)
{
case UIAnimator.UIAnimationType.Move:
EditorGUI.PropertyField(valueRect, prop.FindPropertyRelative("moveOffset"));
break;
case UIAnimator.UIAnimationType.Scale:
EditorGUI.PropertyField(valueRect, prop.FindPropertyRelative("targetScale"));
break;
case UIAnimator.UIAnimationType.Rotate:
EditorGUI.PropertyField(valueRect, prop.FindPropertyRelative("targetRotation"));
break;
case UIAnimator.UIAnimationType.Alpha:
EditorGUI.PropertyField(valueRect, prop.FindPropertyRelative("targetAlpha"));
break;
}
EditorGUIUtility.labelWidth = 0f;
}
private void OnAddStep(ReorderableList list)
{
stepsProp.arraySize++;
SerializedProperty newElem = stepsProp.GetArrayElementAtIndex(stepsProp.arraySize - 1);
// 给新步骤赋默认值
newElem.FindPropertyRelative("type").enumValueIndex = 0;
newElem.FindPropertyRelative("delay").floatValue = 0f;
newElem.FindPropertyRelative("duration").floatValue = 0.5f;
newElem.FindPropertyRelative("ease").enumValueIndex = (int)Ease.OutQuad;
newElem.FindPropertyRelative("moveOffset").vector3Value = Vector3.zero;
newElem.FindPropertyRelative("targetScale").vector3Value = Vector3.one;
newElem.FindPropertyRelative("targetRotation").floatValue = 0f;
newElem.FindPropertyRelative("targetAlpha").floatValue = 1f;
serializedObject.ApplyModifiedProperties();
}
// ---------- 预览按钮 ----------
private void DrawPreviewButtons()
{
UIAnimator animator = (UIAnimator)target;
if (animator.steps == null || animator.steps.Count == 0)
{
EditorGUILayout.HelpBox("请先添加动画步骤", MessageType.Warning);
return;
}
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("▶ 播放", GUILayout.Height(26)))
StartPreview(animator, true);
if (GUILayout.Button("◀ 反向", GUILayout.Height(26)))
StartPreview(animator, false);
if (GUILayout.Button("■ 停止", GUILayout.Height(26)))
StopPreview();
if (GUILayout.Button("⟲ 重置", GUILayout.Height(26)))
ResetToInitial(animator);
EditorGUILayout.EndHorizontal();
if (previewing)
EditorGUILayout.HelpBox("预览中… 停止后恢复初始状态", MessageType.Info);
}
// ---------- 预览驱动 ----------
private void StartPreview(UIAnimator animator, bool forward)
{
StopPreview();
if (animator.GetComponent<RectTransform>() == null)
{
EditorUtility.DisplayDialog("UIAnimator", "需要挂载在带有RectTransform的UI对象上", "确定");
return;
}
animator.SaveInitialState();
previewForward = forward;
previewing = true;
previewElapsed = 0f;
previewTotal = Mathf.Max(animator.TotalDuration, 0.001f);
lastPreviewTime = EditorApplication.timeSinceStartup;
EditorApplication.update -= OnPreviewUpdate;
EditorApplication.update += OnPreviewUpdate;
UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
}
private void StopPreview()
{
if (!previewing) return;
previewing = false;
EditorApplication.update -= OnPreviewUpdate;
RestoreInitialState();
UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
}
private void ResetToInitial(UIAnimator animator)
{
StopPreview();
animator.SaveInitialState();
RestoreInitialState();
}
private void OnPreviewUpdate()
{
if (!previewing) return;
UIAnimator animator = (UIAnimator)target;
if (animator == null) { StopPreview(); return; }
double now = EditorApplication.timeSinceStartup;
previewElapsed += (float)(now - lastPreviewTime);
lastPreviewTime = now;
// 每帧重算总时长,预览中修改步骤字段可即时生效
previewTotal = Mathf.Max(animator.TotalDuration, 0.001f);
// 直接使用绝对时间(秒)驱动,避免归一化比例与步骤时间轴错位
float elapsedTime = previewForward
? Mathf.Min(previewElapsed, previewTotal)
: Mathf.Max(previewTotal - previewElapsed, 0f);
ApplyPreview(animator, elapsedTime);
if (previewElapsed >= previewTotal)
{
previewing = false;
EditorApplication.update -= OnPreviewUpdate;
}
Repaint();
UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
}
/// <summary>
/// 按绝对时间(秒)插值应用步骤编辑器预览用与运行时DOTween缓动保持一致的核心曲线
/// </summary>
private void ApplyPreview(UIAnimator animator, float time)
{
RectTransform rt = animator.RectTransform;
CanvasGroup cg = animator.CanvasGroup;
Vector3 curPos = animator.InitialAnchoredPosition3D;
Vector3 curScale = animator.InitialLocalScale;
float curRotZ = animator.InitialRotationZ;
float curAlpha = animator.InitialAlpha;
float t = 0f;
foreach (var step in animator.steps)
{
float stepStart = t + step.delay;
float stepEnd = stepStart + step.duration;
if (time >= stepStart)
{
float local = Mathf.Clamp01((time - stepStart) / Mathf.Max(step.duration, 0.0001f));
float e = EvaluateEase(step.ease, local);
// 记录本步骤起始值(=上一步结束后的值),串行执行时从该值插值到目标值,
// 与运行时 Sequence.Append 的语义一致,避免多步骤"同时执行"
Vector3 startPos = curPos;
Vector3 startScale = curScale;
float startRotZ = curRotZ;
float startAlpha = curAlpha;
switch (step.type)
{
case UIAnimator.UIAnimationType.Move:
curPos = startPos + step.moveOffset * e;
break;
case UIAnimator.UIAnimationType.Scale:
curScale = Vector3.LerpUnclamped(startScale, step.targetScale, e);
break;
case UIAnimator.UIAnimationType.Rotate:
curRotZ = Mathf.LerpUnclamped(startRotZ, step.targetRotation, e);
break;
case UIAnimator.UIAnimationType.Alpha:
if (cg != null)
curAlpha = Mathf.LerpUnclamped(startAlpha, step.targetAlpha, e);
break;
}
}
t = stepEnd;
}
rt.anchoredPosition3D = curPos;
rt.localScale = curScale;
Vector3 rot = rt.localEulerAngles;
rot.z = curRotZ;
rt.localEulerAngles = rot;
if (cg != null) cg.alpha = curAlpha;
}
private void RestoreInitialState()
{
UIAnimator animator = (UIAnimator)target;
if (animator == null || animator.RectTransform == null) return;
animator.RectTransform.anchoredPosition3D = animator.InitialAnchoredPosition3D;
animator.RectTransform.localScale = animator.InitialLocalScale;
Vector3 rot = animator.RectTransform.localEulerAngles;
rot.z = animator.InitialRotationZ;
animator.RectTransform.localEulerAngles = rot;
if (animator.CanvasGroup != null)
animator.CanvasGroup.alpha = animator.InitialAlpha;
}
// ---------- 预览缓动公式与DOTween常用缓动一致 ----------
private static float EvaluateEase(Ease ease, float t)
{
switch (ease)
{
case Ease.Linear: return t;
case Ease.InQuad: return t * t;
case Ease.OutQuad: return t * (2f - t);
case Ease.InOutQuad: return t < 0.5f ? 2f * t * t : -1f + (4f - 2f * t) * t;
case Ease.InCubic: return t * t * t;
case Ease.OutCubic: return 1f + (--t) * t * t;
case Ease.InOutCubic: return t < 0.5f ? 4f * t * t * t : (t - 1f) * (2f * t - 2f) * (2f * t - 2f) + 1f;
case Ease.InQuart: return t * t * t * t;
case Ease.OutQuart: return 1f - (--t) * t * t * t;
case Ease.InQuint: return t * t * t * t * t;
case Ease.OutQuint: return 1f + (--t) * t * t * t * t;
case Ease.InSine: return 1f - Mathf.Cos(t * Mathf.PI * 0.5f);
case Ease.OutSine: return Mathf.Sin(t * Mathf.PI * 0.5f);
case Ease.InOutSine: return -0.5f * (Mathf.Cos(Mathf.PI * t) - 1f);
case Ease.InBack: { float c1 = 1.70158f, c3 = c1 + 1f; return c3 * t * t * t - c1 * t * t; }
case Ease.OutBack: { float c1 = 1.70158f, c3 = c1 + 1f; t -= 1f; return 1f + c3 * t * t * t + c1 * t * t; }
case Ease.InOutBack:
{
float c1 = 1.70158f, c2 = c1 * 1.525f;
return t < 0.5f
? (2f * t) * (2f * t) * ((c2 + 1f) * 2f * t - c2) / 2f
: ((2f * t - 2f) * (2f * t - 2f) * ((c2 + 1f) * (t * 2f - 2f) + c2) + 2f) / 2f;
}
case Ease.OutElastic:
{
float c4 = (2f * Mathf.PI) / 3f;
if (t == 0f) return 0f;
if (t == 1f) return 1f;
return Mathf.Pow(2f, -10f * t) * Mathf.Sin((t * 10f - 0.75f) * c4) + 1f;
}
case Ease.OutBounce:
{
const float n1 = 7.5625f, d1 = 2.75f;
if (t < 1f / d1) return n1 * t * t;
if (t < 2f / d1) { t -= 1.5f / d1; return n1 * t * t + 0.75f; }
if (t < 2.5f / d1) { t -= 2.25f / d1; return n1 * t * t + 0.9375f; }
t -= 2.625f / d1; return n1 * t * t + 0.984375f;
}
default: return t;
}
}
}