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

313 lines
13 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 System.Linq;
using UnityEditor;
using UnityEngine;
using BaseGames.EventChain;
namespace BaseGames.Editor
{
/// <summary>
/// 事件链可视化编辑器窗口(架构 14_NarrativeModule §13
/// 菜单BaseGames/Tools/Event Chain Viewer
///
/// 功能:
/// - 左侧chainId 分组总览(按完成状态着色)
/// - 右侧:选中链的 Conditions 和 Actions 表格
/// - Play Mode运行时状态着色已完成=绿 / 条件满足=橙 / 未满足=白)
/// - ChainCompletedCondition 依赖链箭头指示
/// - 执行日志(最近 20 条)
/// - 双击 → EditorGUIUtility.PingObject
/// </summary>
public class EventChainEditorWindow : EditorWindow
{
// ── State ──────────────────────────────────────────────────────────
private EventChainSO[] _allChains;
private EventChainSO _selectedChain;
private Vector2 _leftScroll;
private Vector2 _rightScroll;
private Vector2 _logScroll;
private static readonly List<string> _log = new();
private const int MaxLogEntries = 20;
// ── Colors ─────────────────────────────────────────────────────────
private static readonly Color ColCompleted = new Color(0.15f, 0.75f, 0.25f, 0.80f);
private static readonly Color ColActive = new Color(0.95f, 0.60f, 0.10f, 0.80f);
private static readonly Color ColPending = new Color(0.70f, 0.70f, 0.75f, 0.80f);
[MenuItem("BaseGames/Tools/Event Chain Viewer")]
public static void OpenWindow()
{
var win = GetWindow<EventChainEditorWindow>("Event Chain Viewer");
win.minSize = new Vector2(800, 500);
win.Show();
}
/// <summary>外部调用:向执行日志追加一条记录(可在运行时由 EventChainManager 调用)。</summary>
public static void LogExecution(string chainId, string message)
{
_log.Add($"[{System.DateTime.Now:HH:mm:ss}] [{chainId}] {message}");
if (_log.Count > MaxLogEntries)
_log.RemoveAt(0);
}
// ── Lifecycle ─────────────────────────────────────────────────────
private void OnEnable()
{
RefreshChainList();
EditorApplication.playModeStateChanged += OnPlayModeChanged;
EventChainManager.OnChainExecutedInEditor += LogExecution;
}
private void OnDisable()
{
EditorApplication.playModeStateChanged -= OnPlayModeChanged;
EventChainManager.OnChainExecutedInEditor -= LogExecution;
}
private void OnPlayModeChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.EnteredPlayMode
|| state == PlayModeStateChange.ExitingPlayMode)
{
RefreshChainList();
Repaint();
}
}
private void RefreshChainList()
{
var guids = AssetDatabase.FindAssets("t:EventChainSO");
var chains = new List<EventChainSO>(guids.Length);
foreach (var g in guids)
{
var path = AssetDatabase.GUIDToAssetPath(g);
var chain = AssetDatabase.LoadAssetAtPath<EventChainSO>(path);
if (chain != null) chains.Add(chain);
}
_allChains = chains.OrderBy(c => c.chainId).ToArray();
}
// ── GUI ────────────────────────────────────────────────────────────
private void OnGUI()
{
DrawToolbar();
EditorGUILayout.BeginHorizontal();
// 左:链列表
EditorGUILayout.BeginVertical(GUILayout.Width(240));
DrawChainList();
EditorGUILayout.EndVertical();
// 分割线
EditorGUILayout.BeginVertical(GUILayout.Width(2));
EditorGUI.DrawRect(GUILayoutUtility.GetRect(2, position.height), new Color(0.1f, 0.1f, 0.1f));
EditorGUILayout.EndVertical();
// 右:选中链详情
EditorGUILayout.BeginVertical();
if (_selectedChain != null)
DrawChainDetail(_selectedChain);
else
EditorGUILayout.HelpBox("从左侧选择一条事件链查看详情。", MessageType.None);
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
}
// ── Toolbar ───────────────────────────────────────────────────────
private void DrawToolbar()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
if (GUILayout.Button("刷新", EditorStyles.toolbarButton, GUILayout.Width(50)))
RefreshChainList();
GUILayout.FlexibleSpace();
EditorGUILayout.LabelField(
$"共 {_allChains?.Length ?? 0} 条事件链",
EditorStyles.toolbarButton);
EditorGUILayout.EndHorizontal();
}
// ── 左侧链列表 ────────────────────────────────────────────────────
private void DrawChainList()
{
EditorGUILayout.LabelField("事件链列表", EditorStyles.boldLabel);
_leftScroll = EditorGUILayout.BeginScrollView(_leftScroll);
if (_allChains == null || _allChains.Length == 0)
{
EditorGUILayout.HelpBox("未找到 EventChainSO 资产。", MessageType.Info);
EditorGUILayout.EndScrollView();
return;
}
foreach (var chain in _allChains)
{
if (chain == null) continue;
bool isSelected = _selectedChain == chain;
bool isCompleted = IsChainCompleted(chain);
bool isActive = Application.isPlaying && IsChainActive(chain);
Color bgColor = isCompleted ? ColCompleted : isActive ? ColActive : ColPending;
var prevBg = GUI.backgroundColor;
GUI.backgroundColor = isSelected ? bgColor * 1.4f : bgColor * 0.7f;
EditorGUILayout.BeginHorizontal("box");
// 状态图标
string icon = isCompleted ? "✓" : isActive ? "▶" : "○";
EditorGUILayout.LabelField(icon, GUILayout.Width(16));
if (GUILayout.Button(chain.chainId, isSelected ? EditorStyles.boldLabel : EditorStyles.label))
_selectedChain = chain;
// 双击 Ping
if (Event.current.type == EventType.MouseDown && Event.current.clickCount == 2
&& GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition))
{
EditorGUIUtility.PingObject(chain);
Event.current.Use();
}
EditorGUILayout.EndHorizontal();
GUI.backgroundColor = prevBg;
}
EditorGUILayout.EndScrollView();
}
// ── 右侧详情 ──────────────────────────────────────────────────────
private void DrawChainDetail(EventChainSO chain)
{
_rightScroll = EditorGUILayout.BeginScrollView(_rightScroll);
// 标题行
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(
$"事件链:{chain.chainId}",
EditorStyles.boldLabel);
if (GUILayout.Button("↗ Ping", GUILayout.Width(60)))
EditorGUIUtility.PingObject(chain);
EditorGUILayout.EndHorizontal();
EditorGUILayout.LabelField(
$"可重复:{(chain.repeatable ? "" : "")} | " +
$"动作间隔:{chain.actionDelay:F2}s",
EditorStyles.miniLabel);
EditorGUILayout.Space(6);
// Conditions 表格
EditorGUILayout.LabelField("触发条件(全部满足才触发)", EditorStyles.boldLabel);
if (chain.conditions != null && chain.conditions.Length > 0)
{
foreach (var cond in chain.conditions)
{
if (cond == null) continue;
bool met = Application.isPlaying && cond.IsMet();
var prevBg = GUI.backgroundColor;
GUI.backgroundColor = met ? ColCompleted * 0.8f : new Color(0.9f, 0.9f, 0.9f, 0.3f);
EditorGUILayout.BeginHorizontal("box");
string status = Application.isPlaying ? (met ? "✓" : "✗") : "—";
EditorGUILayout.LabelField(status, GUILayout.Width(20));
EditorGUILayout.LabelField(cond.GetType().Name, GUILayout.Width(220));
// 依赖箭头ChainCompletedCondition
if (cond is ChainCompletedCondition depCond)
{
EditorGUILayout.LabelField($"→ 依赖链:{depCond.chainId}",
EditorStyles.miniLabel);
}
if (GUILayout.Button("↗", GUILayout.Width(24)))
EditorGUIUtility.PingObject(cond);
EditorGUILayout.EndHorizontal();
GUI.backgroundColor = prevBg;
}
}
else
{
EditorGUILayout.LabelField("(无条件,立即触发)", EditorStyles.miniLabel);
}
EditorGUILayout.Space(6);
// Actions 表格
EditorGUILayout.LabelField("执行动作(顺序执行)", EditorStyles.boldLabel);
if (chain.actions != null && chain.actions.Length > 0)
{
for (int i = 0; i < chain.actions.Length; i++)
{
var action = chain.actions[i];
if (action == null) continue;
EditorGUILayout.BeginHorizontal("box");
EditorGUILayout.LabelField($"[{i}]", GUILayout.Width(30));
EditorGUILayout.LabelField(action.GetType().Name, GUILayout.Width(200));
EditorGUILayout.LabelField(action.name, EditorStyles.miniLabel);
if (GUILayout.Button("↗", GUILayout.Width(24)))
EditorGUIUtility.PingObject(action);
EditorGUILayout.EndHorizontal();
}
}
else
{
EditorGUILayout.LabelField("(无动作)", EditorStyles.miniLabel);
}
// 执行日志
EditorGUILayout.Space(6);
EditorGUILayout.LabelField($"执行日志(最近 {MaxLogEntries} 条)", EditorStyles.boldLabel);
_logScroll = EditorGUILayout.BeginScrollView(_logScroll, GUILayout.Height(120));
var relevantLogs = _log.Where(l => l.Contains(chain.chainId)).ToList();
if (relevantLogs.Count == 0)
EditorGUILayout.LabelField("—(无日志)", EditorStyles.miniLabel);
else
foreach (var entry in relevantLogs)
EditorGUILayout.LabelField(entry, EditorStyles.miniLabel);
EditorGUILayout.EndScrollView();
EditorGUILayout.EndScrollView();
}
// ── 运行时状态查询 ────────────────────────────────────────────────
private static bool IsChainCompleted(EventChainSO chain)
{
if (!Application.isPlaying) return false;
var manager = FindFirstObjectByType<EventChainManager>();
if (manager == null) return false;
// 通过反射读取 _completedChains
var field = typeof(EventChainManager).GetField(
"_completedChains",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (field?.GetValue(manager) is HashSet<string> completed)
return completed.Contains(chain.chainId);
return false;
}
private static bool IsChainActive(EventChainSO chain)
{
// 链"激活中"= 有任意条件已满足但链未完成
if (chain.conditions == null) return false;
return chain.conditions.Any(c => c != null && c.IsMet());
}
private void Update()
{
// Play Mode 下每秒刷新一次以更新状态颜色
if (Application.isPlaying)
Repaint();
}
}
}