using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using BaseGames.Dialogue;
namespace BaseGames.Editor.Dialogue
{
///
/// 对话变体预览窗口。
/// 给定一个 DialogueSequenceSO,模拟世界状态标志的开关组合,
/// 实时显示各条件变体是否满足,并高亮胜出的变体。
/// 菜单:BaseGames/Dialogue/Variant Preview
///
public class DialogueVariantPreviewWindow : EditorWindow
{
private DialogueSequenceSO _target;
private readonly HashSet _enabledFlags = new(System.StringComparer.Ordinal);
private readonly List _allFlags = new();
private ObjectField _targetField;
private VisualElement _flagContainer;
private VisualElement _resultContainer;
private static readonly Color ColWin = new(0.20f, 0.75f, 0.35f, 1f);
private static readonly Color ColFail = new(0.55f, 0.55f, 0.55f, 1f);
private static readonly Color ColOverride = new(0.70f, 0.70f, 0.25f, 1f);
private static readonly Color ColBlocked = new(0.85f, 0.35f, 0.30f, 1f);
[MenuItem("BaseGames/Dialogue/Variant Preview")]
public static void Open()
{
var win = GetWindow("对话变体预览");
win.minSize = new Vector2(480, 400);
}
/// 从外部打开并预填目标 SO。
public static void OpenWith(DialogueSequenceSO target)
{
var win = GetWindow("对话变体预览");
win.minSize = new Vector2(480, 400);
win.SetTarget(target);
}
private void CreateGUI()
{
_mockReader = new MockFlagReader(_enabledFlags);
rootVisualElement.style.paddingLeft = 10;
rootVisualElement.style.paddingRight = 10;
rootVisualElement.style.paddingTop = 10;
rootVisualElement.style.paddingBottom = 10;
// ── 标题栏 ──
var header = new Label("对话变体预览工具");
header.style.fontSize = 14;
header.style.unityFontStyleAndWeight = FontStyle.Bold;
header.style.marginBottom = 8;
rootVisualElement.Add(header);
var desc = new Label("在模拟的世界状态标志组合下,预览哪个条件变体会被选中。");
desc.style.fontSize = 11;
desc.style.opacity = 0.6f;
desc.style.marginBottom = 10;
rootVisualElement.Add(desc);
// ── 目标选择器 ──
_targetField = new ObjectField("对话序列 SO")
{
objectType = typeof(DialogueSequenceSO),
allowSceneObjects = false
};
_targetField.value = _target;
_targetField.RegisterValueChangedCallback(evt =>
{
SetTarget(evt.newValue as DialogueSequenceSO);
});
rootVisualElement.Add(_targetField);
rootVisualElement.Add(MakeDivider());
// ── 标志模拟区 ──
var flagHeader = new Label("模拟世界状态标志");
flagHeader.style.fontSize = 12;
flagHeader.style.unityFontStyleAndWeight = FontStyle.Bold;
flagHeader.style.marginBottom = 4;
rootVisualElement.Add(flagHeader);
_flagContainer = new VisualElement();
rootVisualElement.Add(_flagContainer);
rootVisualElement.Add(MakeDivider());
// ── 变体结果区 ──
var resultHeader = new Label("变体求值结果");
resultHeader.style.fontSize = 12;
resultHeader.style.unityFontStyleAndWeight = FontStyle.Bold;
resultHeader.style.marginBottom = 4;
rootVisualElement.Add(resultHeader);
var scrollView = new ScrollView(ScrollViewMode.Vertical);
scrollView.style.flexGrow = 1;
rootVisualElement.Add(scrollView);
_resultContainer = new VisualElement();
scrollView.Add(_resultContainer);
Rebuild();
}
private void SetTarget(DialogueSequenceSO target)
{
_target = target;
_enabledFlags.Clear();
if (_targetField != null && _targetField.value != target)
_targetField.SetValueWithoutNotify(target);
Rebuild();
}
// ── 重建 ─────────────────────────────────────────────────────────────
private void Rebuild()
{
RebuildFlagToggles();
RebuildResults();
}
private void RebuildFlagToggles()
{
if (_flagContainer == null) return;
_flagContainer.Clear();
_allFlags.Clear();
if (_target == null || _target.variants == null || _target.variants.Length == 0)
{
var empty = new Label(_target == null
? "(请选择一个 DialogueSequenceSO)"
: "(该序列无条件变体,无需模拟)");
empty.style.opacity = 0.5f;
empty.style.fontSize = 11;
_flagContainer.Add(empty);
return;
}
// 收集所有变体中涉及的 Flag
var flagSet = new HashSet(System.StringComparer.Ordinal);
foreach (var v in _target.variants)
{
if (v.requiredFlags != null)
foreach (var f in v.requiredFlags)
if (!string.IsNullOrEmpty(f)) flagSet.Add(f);
}
_allFlags.AddRange(flagSet.OrderBy(x => x));
if (_allFlags.Count == 0)
{
var empty = new Label("(变体未使用任何 requiredFlags)");
empty.style.opacity = 0.5f;
empty.style.fontSize = 11;
_flagContainer.Add(empty);
return;
}
// 全选 / 全不选 快速按钮
var btnRow = new VisualElement();
btnRow.style.flexDirection = FlexDirection.Row;
btnRow.style.marginBottom = 4;
var btnAll = new Button(() =>
{
foreach (var f in _allFlags) _enabledFlags.Add(f);
Rebuild();
}) { text = "全选" };
btnAll.style.fontSize = 10;
btnAll.style.height = 18;
btnRow.Add(btnAll);
var btnNone = new Button(() =>
{
_enabledFlags.Clear();
Rebuild();
}) { text = "全不选" };
btnNone.style.fontSize = 10;
btnNone.style.height = 18;
btnRow.Add(btnNone);
_flagContainer.Add(btnRow);
// 每个 Flag 对应一个 Toggle
foreach (var flag in _allFlags)
{
bool isOn = _enabledFlags.Contains(flag);
var toggle = new Toggle(flag) { value = isOn };
toggle.style.fontSize = 11;
toggle.RegisterValueChangedCallback(evt =>
{
if (evt.newValue) _enabledFlags.Add(flag);
else _enabledFlags.Remove(flag);
RebuildResults();
});
_flagContainer.Add(toggle);
}
}
private void RebuildResults()
{
if (_resultContainer == null) return;
_resultContainer.Clear();
if (_target == null)
return;
if (_target.variants == null || _target.variants.Length == 0)
{
var msg = new Label("(序列无条件变体,直接使用本序列默认台词)");
msg.style.opacity = 0.5f;
msg.style.fontSize = 11;
_resultContainer.Add(msg);
return;
}
bool winnerFound = false;
for (int i = 0; i < _target.variants.Length; i++)
{
var variant = _target.variants[i];
var row = BuildVariantRow(i, variant, winnerFound);
_resultContainer.Add(row);
if (!winnerFound && EvaluateVariant(variant))
winnerFound = true;
}
// 若无变体胜出,提示将回退到本序列默认台词
if (!winnerFound)
{
var fallback = new Label("↳ 无变体满足,将使用本序列默认台词(无变体覆盖)");
fallback.style.fontSize = 11;
fallback.style.opacity = 0.6f;
fallback.style.marginTop = 4;
_resultContainer.Add(fallback);
}
}
private VisualElement BuildVariantRow(int index, DialogueSequenceSO.ConditionalVariant variant, bool higherWon)
{
bool condMet = EvaluateVariant(variant);
bool isWinner = condMet && !higherWon;
var card = new VisualElement();
card.style.borderLeftWidth = 3;
card.style.paddingLeft = 8;
card.style.paddingRight = 8;
card.style.paddingTop = 5;
card.style.paddingBottom = 5;
card.style.marginBottom = 4;
card.style.backgroundColor = new StyleColor(new Color(0.18f, 0.18f, 0.18f, 1f));
Color borderColor;
string statusText;
Color statusColor;
if (isWinner)
{
borderColor = ColWin;
statusText = "✓ 胜出";
statusColor = ColWin;
}
else if (condMet)
{
borderColor = ColOverride;
statusText = "⏩ 被更高优先级覆盖";
statusColor = ColOverride;
}
else
{
borderColor = ColFail;
statusText = "✗ 条件不满足";
statusColor = ColFail;
}
card.style.borderLeftColor = new StyleColor(borderColor);
// 标题行
var titleRow = new VisualElement();
titleRow.style.flexDirection = FlexDirection.Row;
titleRow.style.alignItems = Align.Center;
titleRow.style.marginBottom = 3;
var idxLabel = new Label($"变体 {index}");
idxLabel.style.fontSize = 11;
idxLabel.style.flexGrow = 1;
idxLabel.style.unityFontStyleAndWeight = isWinner ? FontStyle.Bold : FontStyle.Normal;
titleRow.Add(idxLabel);
var seqName = new Label(variant.sequence != null ? variant.sequence.name : "(未设置序列)");
seqName.style.fontSize = 10;
seqName.style.opacity = 0.6f;
seqName.style.width = 160;
titleRow.Add(seqName);
var statusLabel = new Label(statusText);
statusLabel.style.fontSize = 10;
statusLabel.style.color = new StyleColor(statusColor);
statusLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
titleRow.Add(statusLabel);
card.Add(titleRow);
// 逻辑类型
var logicLabel = new Label($"逻辑:{variant.logic}");
logicLabel.style.fontSize = 10;
logicLabel.style.opacity = 0.5f;
card.Add(logicLabel);
// 条件详情
if (variant.requiredFlags != null && variant.requiredFlags.Length > 0)
{
foreach (var flag in variant.requiredFlags)
{
if (string.IsNullOrEmpty(flag)) continue;
bool flagOn = _enabledFlags.Contains(flag);
var flagRow = new VisualElement();
flagRow.style.flexDirection = FlexDirection.Row;
flagRow.style.alignItems = Align.Center;
flagRow.style.marginTop = 1;
var icon = new Label(flagOn ? "✓" : "✗");
icon.style.fontSize = 10;
icon.style.color = new StyleColor(flagOn ? ColWin : ColBlocked);
icon.style.width = 16;
flagRow.Add(icon);
var flagLabel = new Label(flag);
flagLabel.style.fontSize = 10;
flagRow.Add(flagLabel);
card.Add(flagRow);
}
}
else
{
var noFlags = new Label("(无 requiredFlags — 无条件激活)");
noFlags.style.fontSize = 10;
noFlags.style.opacity = 0.5f;
card.Add(noFlags);
}
return card;
}
private bool EvaluateVariant(DialogueSequenceSO.ConditionalVariant variant)
{
// 使用 DialogueSequenceSO.CheckVariant 统一变体求值逻辑,避免重复实现
return _target != null && _target.CheckVariant(variant, _mockReader);
}
/// 将 _enabledFlags 包装为 IWorldStateReader,供 CheckVariant 调用。
private MockFlagReader _mockReader;
private sealed class MockFlagReader : BaseGames.Core.IWorldStateReader
{
private readonly System.Collections.Generic.HashSet _flags;
public MockFlagReader(System.Collections.Generic.HashSet flags) => _flags = flags;
public bool HasFlag(string key) => _flags.Contains(key);
}
// ── 辅助 ─────────────────────────────────────────────────────────────
private static VisualElement MakeDivider()
{
var d = new VisualElement();
d.style.height = 1;
d.style.backgroundColor = new StyleColor(new Color(0.35f, 0.35f, 0.35f, 0.5f));
d.style.marginTop = 6;
d.style.marginBottom = 6;
return d;
}
}
}