- QuestSO: Add ValidateBranchCycles() DFS detection for branches[].nextQuest loop - QuestSO: Mark three legacy prerequisite fields with v2.0 removal warning in Tooltip - IQuestManager: Add QuestLockReason enum + QuestLockInfo struct (strongly-typed lock info) - IQuestManager: Add GetQuestLockInfo() method to interface; GetQuestLockReason() now delegates to it - IQuestEventSource: Add OnQuestStateChanged(questId, oldState, newState) unified event - QuestManager: Implement GetQuestLockInfo(); fire OnQuestStateChanged on all state transitions - DialogueManager: Add one-frame yield in HandleChoices before ShowChoices (skip-debounce fix) - DialogueManager: Increment _playbackId in ForceEnd() to invalidate residual choice callbacks - DialogueSequenceSO: Add UNITY_EDITOR debug log in TryGetActiveVariant on variant match - WorldStateRegistry: Add OnBatchStateChanged event + BatchMark() batch-write API - DialogueModule: List badge shows warning indicator for unconditional-shadowing variants - DialogueModule: BuildVariantsCard shows logic mode (AND/OR) alongside flag conditions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
172 lines
7.6 KiB
C#
172 lines
7.6 KiB
C#
using System.Collections.Generic;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
|
||
namespace BaseGames.Editor.Modules
|
||
{
|
||
/// <summary>
|
||
/// 批量验证结果窗口:以可滚动列表展示各项配置问题,
|
||
/// 每条记录附带"选中"按钮,点击后高亮并选中对应资产(EditorGUIUtility.PingObject)。
|
||
/// 支持 Error/Warning Tab 切换与文本搜索过滤。
|
||
/// 由各验证模块(QuestModule、DialogueModule、NpcModule)在检测到问题时弹出。
|
||
/// </summary>
|
||
internal class QuestValidationResultWindow : EditorWindow
|
||
{
|
||
internal struct Issue
|
||
{
|
||
public string message;
|
||
public bool isError;
|
||
public UnityEngine.Object asset; // null = 无对应资产(如孤儿触发器)
|
||
}
|
||
|
||
private List<Issue> _issues;
|
||
private int _errorCount;
|
||
private int _warnCount;
|
||
private int _totalItems;
|
||
private string _itemLabel = "资产";
|
||
private Vector2 _scroll;
|
||
|
||
// ── 过滤状态 ─────────────────────────────────────────────────────────
|
||
private enum TabMode { All, ErrorsOnly, WarnsOnly }
|
||
private TabMode _tab = TabMode.All;
|
||
private string _filter = "";
|
||
|
||
private static readonly GUIStyle s_tabActive = null; // 延迟初始化
|
||
private static readonly GUIStyle s_tabInactive = null;
|
||
|
||
// ── 打开入口 ──────────────────────────────────────────────────────────
|
||
|
||
internal static void Show(List<Issue> issues, int errorCount, int warnCount, int totalItems, string windowTitle = "批量验证结果", string itemLabel = "资产")
|
||
{
|
||
var win = GetWindow<QuestValidationResultWindow>(true, windowTitle, true);
|
||
win._issues = issues;
|
||
win._errorCount = errorCount;
|
||
win._warnCount = warnCount;
|
||
win._totalItems = totalItems;
|
||
win._itemLabel = itemLabel;
|
||
win._tab = TabMode.All;
|
||
win._filter = "";
|
||
win.minSize = new Vector2(560, 380);
|
||
win.Show();
|
||
}
|
||
|
||
// ── 绘制 ──────────────────────────────────────────────────────────────
|
||
|
||
private void OnGUI()
|
||
{
|
||
if (_issues == null) { EditorGUILayout.LabelField("无数据。"); return; }
|
||
|
||
bool clean = _errorCount == 0 && _warnCount == 0;
|
||
|
||
// ── 标题摘要 ──
|
||
EditorGUILayout.Space(6);
|
||
var summaryStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 12 };
|
||
if (clean) summaryStyle.normal.textColor = new Color(0.2f, 0.75f, 0.2f);
|
||
|
||
string prefix = clean ? "✅ " : (_errorCount > 0 ? "❌ " : "⚠ ");
|
||
string summary = $"{prefix}验证完成:{_totalItems} 个{_itemLabel} · {_errorCount} 个错误 · {_warnCount} 个警告";
|
||
EditorGUILayout.LabelField(summary, summaryStyle);
|
||
EditorGUILayout.Space(4);
|
||
|
||
if (clean)
|
||
{
|
||
EditorGUILayout.LabelField($"所有 {_itemLabel} 配置均合法!", EditorStyles.centeredGreyMiniLabel);
|
||
return;
|
||
}
|
||
|
||
// ── 分隔线 ──
|
||
var divRect = EditorGUILayout.GetControlRect(false, 1);
|
||
EditorGUI.DrawRect(divRect, new Color(0.35f, 0.35f, 0.35f));
|
||
EditorGUILayout.Space(4);
|
||
|
||
// ── Tab 切换 + 搜索框 ──
|
||
EditorGUILayout.BeginHorizontal();
|
||
DrawTab("全部", TabMode.All, _issues.Count);
|
||
DrawTab("错误", TabMode.ErrorsOnly, _errorCount);
|
||
DrawTab("警告", TabMode.WarnsOnly, _warnCount);
|
||
GUILayout.FlexibleSpace();
|
||
GUILayout.Label("🔍", GUILayout.Width(18));
|
||
_filter = EditorGUILayout.TextField(_filter, GUILayout.Width(180));
|
||
if (GUILayout.Button("×", GUILayout.Width(22))) _filter = "";
|
||
EditorGUILayout.EndHorizontal();
|
||
EditorGUILayout.Space(4);
|
||
|
||
// ── 问题列表 ──
|
||
_scroll = EditorGUILayout.BeginScrollView(_scroll);
|
||
int shown = 0;
|
||
foreach (var issue in _issues)
|
||
{
|
||
if (!MatchesFilter(issue)) continue;
|
||
shown++;
|
||
DrawIssueRow(issue);
|
||
EditorGUILayout.Space(2);
|
||
}
|
||
if (shown == 0)
|
||
EditorGUILayout.LabelField("(当前过滤条件下无结果)", EditorStyles.centeredGreyMiniLabel);
|
||
EditorGUILayout.EndScrollView();
|
||
|
||
// ── 底部状态栏 ──
|
||
EditorGUILayout.Space(2);
|
||
var statusRect = EditorGUILayout.GetControlRect(false, 1);
|
||
EditorGUI.DrawRect(statusRect, new Color(0.3f, 0.3f, 0.3f));
|
||
EditorGUILayout.LabelField(
|
||
$"显示 {shown} / {_issues.Count} 条",
|
||
EditorStyles.centeredGreyMiniLabel);
|
||
}
|
||
|
||
// ── 辅助方法 ─────────────────────────────────────────────────────────
|
||
|
||
private bool MatchesFilter(Issue issue)
|
||
{
|
||
if (_tab == TabMode.ErrorsOnly && !issue.isError) return false;
|
||
if (_tab == TabMode.WarnsOnly && issue.isError) return false;
|
||
if (!string.IsNullOrEmpty(_filter))
|
||
{
|
||
bool msgMatch = issue.message.Contains(_filter, System.StringComparison.OrdinalIgnoreCase);
|
||
bool assetMatch = issue.asset != null &&
|
||
issue.asset.name.Contains(_filter, System.StringComparison.OrdinalIgnoreCase);
|
||
if (!msgMatch && !assetMatch) return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
private void DrawTab(string label, TabMode mode, int count)
|
||
{
|
||
bool active = _tab == mode;
|
||
var style = active
|
||
? new GUIStyle(EditorStyles.miniButtonMid) { fontStyle = FontStyle.Bold }
|
||
: EditorStyles.miniButtonMid;
|
||
if (active) style.normal.textColor = new Color(0.4f, 0.8f, 1f);
|
||
string text = $"{label} ({count})";
|
||
if (GUILayout.Button(text, style, GUILayout.MinWidth(72)))
|
||
_tab = mode;
|
||
}
|
||
|
||
private static void DrawIssueRow(Issue issue)
|
||
{
|
||
EditorGUILayout.BeginHorizontal(EditorStyles.helpBox);
|
||
|
||
// 图标
|
||
var iconContent = issue.isError
|
||
? EditorGUIUtility.IconContent("console.erroricon.sml")
|
||
: EditorGUIUtility.IconContent("console.warnicon.sml");
|
||
GUILayout.Label(iconContent, GUILayout.Width(20), GUILayout.Height(18));
|
||
|
||
// 消息文字
|
||
EditorGUILayout.LabelField(issue.message, EditorStyles.wordWrappedLabel);
|
||
|
||
// 定位按钮(有资产引用时显示)
|
||
if (issue.asset != null)
|
||
{
|
||
if (GUILayout.Button("选中", GUILayout.Width(40), GUILayout.Height(18)))
|
||
{
|
||
EditorGUIUtility.PingObject(issue.asset);
|
||
Selection.activeObject = issue.asset;
|
||
}
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
}
|
||
}
|