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(); } } }