using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using BaseGames.EventChain;
namespace BaseGames.Editor
{
///
/// 事件链可视化编辑器窗口(架构 14_NarrativeModule §13)。
/// 菜单:BaseGames/Tools/Event Chain Viewer
///
/// 功能:
/// - 左侧:chainId 分组总览(按完成状态着色)
/// - 右侧:选中链的 Conditions 和 Actions 表格
/// - Play Mode:运行时状态着色(已完成=绿 / 条件满足=橙 / 未满足=白)
/// - ChainCompletedCondition 依赖链箭头指示
/// - 执行日志(最近 20 条)
/// - 双击 → EditorGUIUtility.PingObject
///
public class EventChainEditorWindow : EditorWindow
{
// ── State ──────────────────────────────────────────────────────────
private EventChainSO[] _allChains;
private EventChainSO _selectedChain;
private Vector2 _leftScroll;
private Vector2 _rightScroll;
private Vector2 _logScroll;
private static readonly List _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("Event Chain Viewer");
win.minSize = new Vector2(800, 500);
win.Show();
}
/// 外部调用:向执行日志追加一条记录(可在运行时由 EventChainManager 调用)。
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(guids.Length);
foreach (var g in guids)
{
var path = AssetDatabase.GUIDToAssetPath(g);
var chain = AssetDatabase.LoadAssetAtPath(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();
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 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();
}
}
}