Files
zeling_v2/Assets/Scripts/Editor/EventConfigEditor.cs
2026-05-12 15:34:08 +08:00

174 lines
8.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using BaseGames.Animation;
namespace BaseGames.Editor
{
/// <summary>
/// AnimationEventConfigSO 自定义 Inspector架构 §AnimationModule
/// 功能:
/// - 以时间线色块可视化事件分布
/// - 自动检测 Clip 长度漂移(超过 5 帧则显示警告)
/// - 验证归一化时间范围 [0, 1]
/// - 一键对事件按归一化时间排序
/// </summary>
[CustomEditor(typeof(AnimationEventConfigSO))]
public class EventConfigEditor : UnityEditor.Editor
{
// ── 事件类型 → 色块颜色映射 ────────────────────────────────────────
private static readonly Dictionary<AnimationEventType, Color> _colorMap = new()
{
{ AnimationEventType.EnableHitBox, new Color(0.9f, 0.2f, 0.2f, 0.8f) }, // 红
{ AnimationEventType.DisableHitBox, new Color(0.9f, 0.2f, 0.2f, 0.8f) },
{ AnimationEventType.AttackImpact, new Color(0.9f, 0.2f, 0.2f, 0.8f) },
{ AnimationEventType.EnableIFrame, new Color(0.2f, 0.8f, 0.2f, 0.8f) }, // 绿
{ AnimationEventType.DisableIFrame, new Color(0.2f, 0.8f, 0.2f, 0.8f) },
{ AnimationEventType.Footstep, new Color(0.2f, 0.4f, 0.9f, 0.8f) }, // 蓝
{ AnimationEventType.PlaySFX, new Color(0.2f, 0.4f, 0.9f, 0.8f) },
{ AnimationEventType.LandImpact, new Color(0.2f, 0.4f, 0.9f, 0.8f) },
{ AnimationEventType.JumpLaunch, new Color(0.2f, 0.4f, 0.9f, 0.8f) },
{ AnimationEventType.EnableParryWindow, new Color(0.9f, 0.8f, 0.1f, 0.8f) }, // 黄
{ AnimationEventType.DisableParryWindow,new Color(0.9f, 0.8f, 0.1f, 0.8f) },
{ AnimationEventType.TriggerFeedback, new Color(0.6f, 0.2f, 0.9f, 0.8f) }, // 紫
{ AnimationEventType.CancelWindowOpen, new Color(0.9f, 0.5f, 0.1f, 0.8f) }, // 橙
{ AnimationEventType.CancelWindowClose, new Color(0.9f, 0.5f, 0.1f, 0.8f) },
{ AnimationEventType.SpawnProjectile, new Color(0.9f, 0.9f, 0.9f, 0.8f) }, // 白
{ AnimationEventType.RoarStart, new Color(0.9f, 0.9f, 0.9f, 0.8f) },
{ AnimationEventType.RoarEnd, new Color(0.9f, 0.9f, 0.9f, 0.8f) },
{ AnimationEventType.PhaseTwoStart, new Color(0.9f, 0.9f, 0.9f, 0.8f) },
};
private const float TimelineHeight = 24f;
private const float MarkerWidth = 3f;
private const float DriftThresholdFrames = 5f;
public override void OnInspectorGUI()
{
var config = (AnimationEventConfigSO)target;
serializedObject.Update();
// ── 时间线预览 ───────────────────────────────────────────────
EditorGUILayout.LabelField("事件时间线预览", EditorStyles.boldLabel);
DrawTimeline(config);
EditorGUILayout.Space(4f);
// ── 标准字段 ─────────────────────────────────────────────────
DrawDefaultInspector();
EditorGUILayout.Space(4f);
// ── 验证警告 ─────────────────────────────────────────────────
ValidateEntries(config);
// ── Clip 长度漂移检测 ─────────────────────────────────────────
if (config.targetClip != null && config.ExpectedClipLength > 0f)
{
float fps = config.targetClip.frameRate;
float actualLen = config.targetClip.length;
float drift = Mathf.Abs(actualLen - config.ExpectedClipLength) * fps;
if (drift > DriftThresholdFrames)
{
EditorGUILayout.HelpBox(
$"⚠ Clip 长度已变化 {drift:F1} 帧(期望 {config.ExpectedClipLength:F3}s" +
$"实际 {actualLen:F3}s。\n请检查事件时机是否需要更新。",
MessageType.Warning);
}
}
EditorGUILayout.Space(4f);
// ── 操作按钮 ─────────────────────────────────────────────────
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("按时间排序"))
{
SortEvents(config);
}
if (config.targetClip != null && GUILayout.Button("记录当前 Clip 长度"))
{
Undo.RecordObject(config, "记录 Clip 长度");
config.ExpectedClipLength = config.targetClip.length;
EditorUtility.SetDirty(config);
}
EditorGUILayout.EndHorizontal();
serializedObject.ApplyModifiedProperties();
}
// ── 时间线绘制 ────────────────────────────────────────────────────
private static void DrawTimeline(AnimationEventConfigSO config)
{
Rect rect = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none,
GUILayout.Height(TimelineHeight), GUILayout.ExpandWidth(true));
// 背景轨道
EditorGUI.DrawRect(rect, new Color(0.15f, 0.15f, 0.15f, 1f));
// 标尺刻度(每 10% 一条)
for (int i = 0; i <= 10; i++)
{
float x = rect.x + rect.width * i / 10f;
float h = (i % 5 == 0) ? rect.height * 0.6f : rect.height * 0.3f;
var tick = new Rect(x, rect.y + rect.height - h, 1f, h);
EditorGUI.DrawRect(tick, new Color(0.5f, 0.5f, 0.5f, 0.8f));
}
if (config.events == null) return;
foreach (var entry in config.events)
{
float nx = Mathf.Clamp01(entry.normalizedTime);
float xPos = rect.x + rect.width * nx - MarkerWidth * 0.5f;
var markerRect = new Rect(xPos, rect.y + 2f, MarkerWidth, rect.height - 4f);
Color color = _colorMap.TryGetValue(entry.eventType, out var c)
? c
: new Color(0.8f, 0.8f, 0.8f, 0.8f);
EditorGUI.DrawRect(markerRect, color);
}
}
// ── 验证 ──────────────────────────────────────────────────────────
private static void ValidateEntries(AnimationEventConfigSO config)
{
if (config.events == null) return;
for (int i = 0; i < config.events.Length; i++)
{
float t = config.events[i].normalizedTime;
if (t < 0f || t > 1f)
{
EditorGUILayout.HelpBox(
$"事件 [{i}] {config.events[i].eventType}" +
$"normalizedTime = {t:F3} 超出 [0, 1] 范围。",
MessageType.Error);
}
}
}
// ── 排序 ──────────────────────────────────────────────────────────
private static void SortEvents(AnimationEventConfigSO config)
{
if (config.events == null || config.events.Length < 2) return;
Undo.RecordObject(config, "排序动画事件");
System.Array.Sort(config.events, (a, b) =>
a.normalizedTime.CompareTo(b.normalizedTime));
EditorUtility.SetDirty(config);
}
}
}