feat: Round 50 narrative systems improvements
IQuestManager+QuestManager: add FillQuestsInState/FillFilterQuests buffer overloads (no-alloc hot path); remove R49 duplicate implementations. QuestGiver: cache current quest result (_cachedQuest/_cachedState/_cacheDirty) to avoid per-frame foreach in InteractPrompt; invalidate on OnEnable and Interact_Internal state changes. IDialogueService+DialogueManager: add StartDialogue(..., Action onComplete) overload; callback fires once on ForceEnd (covers both normal end and interrupt); supports chained callbacks via += accumulation. DialogueVariantPreviewWindow: add 'Copy CSV' button in matrix section; exports all 2^N flag combinations with winner column; handles N>10 guard and CSV-safe escaping. WorldStateRegistry: add TryGetCategory(id, out category) reverse lookup for debug tools. NpcSOEditor: new CustomEditor for NpcSO showing live nameKey localization preview in Inspector (green label or warning box if Key not found). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -121,6 +121,10 @@ namespace BaseGames.Editor.Dialogue
|
||||
matrixBtn.style.marginBottom = 4;
|
||||
matrixFoldout.Add(matrixBtn);
|
||||
|
||||
var csvBtn = new Button(() => ExportMatrixCsv()) { text = "复制为 CSV" };
|
||||
csvBtn.style.marginBottom = 4;
|
||||
matrixFoldout.Add(csvBtn);
|
||||
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
@@ -381,6 +385,79 @@ namespace BaseGames.Editor.Dialogue
|
||||
|
||||
// ── 矩阵分析 ─────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 将矩阵分析结果复制为 CSV 字符串到系统剪贴板。
|
||||
/// 格式:首行为标志名称列头(各列)+ "胜出变体",后续每行为一个组合及其结果。
|
||||
/// N > 10 时与 <see cref="RebuildMatrix"/> 一致,提示用户先减少标志数量。
|
||||
/// </summary>
|
||||
private void ExportMatrixCsv()
|
||||
{
|
||||
if (_target == null || _target.variants == null || _target.variants.Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("矩阵分析 CSV", "当前无可导出的变体数据,请先选择对话序列 SO。", "确定");
|
||||
return;
|
||||
}
|
||||
|
||||
var matrixFlags = _allFlags.Count > 0 ? _allFlags : new List<string>();
|
||||
if (matrixFlags.Count == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("矩阵分析 CSV", "变体未使用任何 requiredFlags,无数据可导出。", "确定");
|
||||
return;
|
||||
}
|
||||
|
||||
const int MaxFlags = 10;
|
||||
if (matrixFlags.Count > MaxFlags)
|
||||
{
|
||||
EditorUtility.DisplayDialog("矩阵分析 CSV",
|
||||
$"标志数量 ({matrixFlags.Count}) 超过 {MaxFlags},无法导出。\n请先在上方取消勾选不关心的标志,再点击此按钮。", "确定");
|
||||
return;
|
||||
}
|
||||
|
||||
int n = matrixFlags.Count;
|
||||
int combos = 1 << n;
|
||||
var sb = new System.Text.StringBuilder();
|
||||
|
||||
// 表头
|
||||
foreach (var f in matrixFlags)
|
||||
sb.Append(EscapeCsv(f)).Append(',');
|
||||
sb.AppendLine("胜出变体");
|
||||
|
||||
// 数据行
|
||||
for (int mask = 0; mask < combos; mask++)
|
||||
{
|
||||
var combo = new HashSet<string>(System.StringComparer.Ordinal);
|
||||
for (int bit = 0; bit < n; bit++)
|
||||
if ((mask & (1 << bit)) != 0) combo.Add(matrixFlags[bit]);
|
||||
|
||||
var mockReader = new MockFlagReader(combo);
|
||||
int winner = -1;
|
||||
for (int vi = 0; vi < _target.variants.Length; vi++)
|
||||
if (_target.CheckVariant(_target.variants[vi], mockReader)) { winner = vi; break; }
|
||||
|
||||
for (int ci = 0; ci < n; ci++)
|
||||
sb.Append((mask & (1 << ci)) != 0 ? "1" : "0").Append(',');
|
||||
|
||||
string winnerLabel = winner >= 0
|
||||
? $"变体{winner}" +
|
||||
(_target.variants[winner].sequence != null
|
||||
? $"({_target.variants[winner].sequence.name})" : "(无序列)")
|
||||
: "默认台词";
|
||||
sb.AppendLine(EscapeCsv(winnerLabel));
|
||||
}
|
||||
|
||||
EditorGUIUtility.systemCopyBuffer = sb.ToString();
|
||||
Debug.Log($"[DialogueVariantPreviewWindow] 矩阵 CSV({combos} 行)已复制到剪贴板。");
|
||||
ShowNotification(new GUIContent($"✓ 已复制 {combos} 行 CSV 到剪贴板"));
|
||||
}
|
||||
|
||||
private static string EscapeCsv(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s)) return string.Empty;
|
||||
if (s.Contains(',') || s.Contains('"') || s.Contains('\n'))
|
||||
return '"' + s.Replace("\"", "\"\"") + '"';
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 枚举全部 2^N 标志组合(N ≤ 10),以表格形式展示每种组合下胜出的变体索引。
|
||||
/// N > 10 时显示提示,建议手动筛选标志后分析。
|
||||
|
||||
73
Assets/_Game/Scripts/Editor/Dialogue/NpcSOEditor.cs
Normal file
73
Assets/_Game/Scripts/Editor/Dialogue/NpcSOEditor.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using BaseGames.Dialogue;
|
||||
|
||||
namespace BaseGames.Editor.Dialogue
|
||||
{
|
||||
/// <summary>
|
||||
/// NpcSO 自定义 Inspector。
|
||||
/// 在 nameKey 字段下方实时预览本地化管理器解析后的 NPC 名称,
|
||||
/// 让策划无需打开本地化表即可确认 Key 是否拼写正确。
|
||||
/// 仅在编辑器构建中生效;不影响运行时行为。
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(NpcSO))]
|
||||
public class NpcSOEditor : UnityEditor.Editor
|
||||
{
|
||||
private static GUIStyle s_previewStyle;
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
|
||||
var npc = (NpcSO)target;
|
||||
if (string.IsNullOrEmpty(npc.nameKey)) return;
|
||||
|
||||
// ── nameKey 本地化预览 ──────────────────────────────────────────
|
||||
if (s_previewStyle == null)
|
||||
{
|
||||
s_previewStyle = new GUIStyle(EditorStyles.helpBox)
|
||||
{
|
||||
fontSize = 11,
|
||||
alignment = TextAnchor.MiddleLeft,
|
||||
padding = new RectOffset(8, 8, 4, 4),
|
||||
};
|
||||
s_previewStyle.normal.textColor = new Color(0.55f, 0.90f, 0.55f);
|
||||
}
|
||||
|
||||
string resolved = TryResolveNameKey(npc.nameKey);
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
if (string.IsNullOrEmpty(resolved))
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
$"nameKey「{npc.nameKey}」在本地化表中未找到对应文本(或 LocalizationManager 未初始化)。\n" +
|
||||
"请检查本地化表中是否存在此 Key。",
|
||||
MessageType.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField(
|
||||
$"▸ nameKey 解析预览:{resolved}",
|
||||
s_previewStyle);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试通过 LocalizationManager(若已加载)解析 nameKey;
|
||||
/// 如未初始化或找不到 Key,返回 null。
|
||||
/// </summary>
|
||||
private static string TryResolveNameKey(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
// LocalizationManager.Get 在编辑器下可能返回空字符串(未初始化),视为未找到
|
||||
var resolved = BaseGames.Localization.LocalizationManager.Get(key, "UI");
|
||||
return string.IsNullOrEmpty(resolved) ? null : resolved;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user