Files
zeling_v2/Assets/_Game/Scripts/Editor/Modules/DialogueModule.cs
Joywayer 446fd5dcd0 feat: Add WorldStateFlagAttribute and custom property drawer for enhanced dialogue management
- Implemented WorldStateFlagAttribute to mark string fields as world state flags.
- Created NarrativeNPCEditor for custom inspector to visualize dialogue version activation states.
- Developed WorldStateFlagDrawer to provide dropdown menu for known flags in the inspector.
- Introduced ActorModule for managing DialogueActorSO assets, including viewing, creating, and deleting actors.
- Added DialogueModule for managing DialogueSequenceSO assets with detailed previews and action bars.
- Established QuestModule for managing QuestSO assets, including objectives and branches.
- Implemented QuestManagerPostprocessor to automatically refresh QuestManager's quest list on asset changes.
2026-05-24 00:36:11 +08:00

231 lines
8.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using BaseGames.Dialogue;
namespace BaseGames.Editor.Modules
{
/// <summary>
/// DataHub 对话序列模块 —— 管理 DialogueSequenceSO 资产。
/// </summary>
public class DialogueModule : IDataModule
{
private const string Folder = "Assets/_Game/Data/Dialogue";
private const string Prefix = "DLG_";
public string ModuleId => "dialogue";
public string DisplayName => "对话";
public string IconName => "d_UnityEditor.ConsoleWindow";
private SoListPane<DialogueSequenceSO> _listPane;
private DetailHeader _header;
private DialogueSequenceSO _selected;
public void Initialize()
{
_listPane = new SoListPane<DialogueSequenceSO>(
Folder, Prefix,
s =>
{
int v = s.variants != null ? s.variants.Length : 0;
return v > 0 ? $"{v}变体" : null;
});
}
public void BuildListPane(VisualElement container, Action<UnityEngine.Object> onSelected)
{
_listPane.SelectionChanged = sel =>
{
_selected = sel;
onSelected?.Invoke(sel);
};
container.Add(_listPane);
_listPane.Refresh();
}
public void BuildDetailPane(VisualElement container, UnityEngine.Object selected)
{
_selected = selected as DialogueSequenceSO;
_header = new DetailHeader();
_header.SetAsset(_selected);
_header.RenameRequested += OnRenameRequested;
container.Add(_header);
if (_selected == null) return;
container.Add(BuildInfoCard(_selected));
container.Add(BuildLinesPreview(_selected));
if (_selected.variants != null && _selected.variants.Length > 0)
container.Add(BuildVariantsCard(_selected));
container.Add(BuildActionBar(_selected));
container.Add(SkillModule.MakeDivider());
container.Add(new InspectorElement(_selected));
}
public void OnActivated() => _listPane?.Refresh();
// ── 内部 ─────────────────────────────────────────────────────────────
private void OnRenameRequested(string newName)
{
if (_selected == null) return;
var (ok, err) = AssetOperations.Rename(_selected, newName);
if (!ok) EditorUtility.DisplayDialog("重命名失败", err, "确定");
else { _header.SetAsset(_selected); _listPane.Invalidate(); }
}
private static VisualElement BuildInfoCard(DialogueSequenceSO s)
{
var card = SkillModule.MakeCard();
int lineCount = s.lines != null ? s.lines.Length : 0;
int variantCount = s.variants != null ? s.variants.Length : 0;
SkillModule.AddChip(card, "ID", string.IsNullOrEmpty(s.sequenceId) ? "(未设置)" : s.sequenceId);
SkillModule.AddChip(card, "行数", lineCount.ToString());
if (variantCount > 0)
SkillModule.AddChip(card, "变体数", variantCount.ToString());
return card;
}
private static VisualElement BuildLinesPreview(DialogueSequenceSO s)
{
var section = new VisualElement();
section.style.paddingLeft = 12;
section.style.paddingRight = 12;
section.style.paddingTop = 6;
section.style.paddingBottom = 6;
var title = new Label("对话预览(前 5 行)");
title.style.fontSize = 11;
title.style.opacity = 0.55f;
title.style.marginBottom = 4;
title.style.unityFontStyleAndWeight = FontStyle.Bold;
section.Add(title);
if (s.lines == null || s.lines.Length == 0)
{
var empty = new Label("(无对话行)");
empty.style.opacity = 0.4f;
empty.style.fontSize = 11;
section.Add(empty);
return section;
}
int preview = Mathf.Min(5, s.lines.Length);
for (int i = 0; i < preview; i++)
{
var line = s.lines[i];
var row = new VisualElement();
row.style.flexDirection = FlexDirection.Row;
row.style.alignItems = Align.Center;
row.style.marginBottom = 3;
// 头像图标actor 优先,回退到直接字段)
var portrait = line.ResolvedPortrait;
if (portrait != null)
{
var img = new Image { image = portrait.texture };
img.style.width = 18;
img.style.height = 18;
img.style.marginRight = 4;
img.style.borderTopLeftRadius = 2;
img.style.borderTopRightRadius = 2;
img.style.borderBottomLeftRadius = 2;
img.style.borderBottomRightRadius = 2;
row.Add(img);
}
// 语音图标
if (line.voiceClip != null)
{
var ico = EditorGUIUtility.IconContent("d_AudioClip Icon");
if (ico?.image != null)
{
var img = new Image { image = ico.image };
img.style.width = 14;
img.style.height = 14;
img.style.marginRight = 4;
row.Add(img);
}
}
// 说话人actor 优先,回退到直接字段)
string speakerKey = line.ResolvedNameKey;
if (!string.IsNullOrEmpty(speakerKey))
{
var spk = new Label(speakerKey + ":");
spk.style.fontSize = 11;
spk.style.opacity = 0.55f;
spk.style.unityFontStyleAndWeight = FontStyle.Bold;
spk.style.marginRight = 4;
spk.style.flexShrink = 0;
row.Add(spk);
}
// 文本 key尝试显示本地化实际内容回退到 key 本身)
string rawText = string.IsNullOrEmpty(line.textKey) ? "(空)" : line.textKey;
string preview = string.IsNullOrEmpty(line.textKey)
? "(空)"
: (BaseGames.Localization.LocalizationManager.GetEditorPreview(line.textKey, "Dialogue") ?? rawText);
if (preview.Length > 48) preview = preview[..48] + "…";
var lbl = new Label(preview);
lbl.style.fontSize = 11;
lbl.style.overflow = Overflow.Hidden;
row.Add(lbl);
section.Add(row);
}
if (s.lines.Length > preview)
{
var more = new Label($"… 还有 {s.lines.Length - preview} 行");
more.style.opacity = 0.4f;
more.style.fontSize = 10;
section.Add(more);
}
return section;
}
private static VisualElement BuildVariantsCard(DialogueSequenceSO s)
{
var card = SkillModule.MakeCard();
card.style.flexDirection = FlexDirection.Column;
var title = new Label("条件变体");
title.style.fontSize = 11;
title.style.opacity = 0.55f;
title.style.marginBottom = 4;
title.style.unityFontStyleAndWeight = FontStyle.Bold;
card.Add(title);
foreach (var v in s.variants)
{
var row = new VisualElement();
row.style.flexDirection = FlexDirection.Row;
row.style.marginBottom = 2;
string flags = v.requiredFlags != null && v.requiredFlags.Length > 0
? string.Join(", ", v.requiredFlags)
: "(无条件)";
SkillModule.AddChip(row, "条件", flags);
SkillModule.AddChip(row, "替换序列", v.sequence != null ? v.sequence.name : "(未设置)");
card.Add(row);
}
return card;
}
private VisualElement BuildActionBar(DialogueSequenceSO s)
{
return SkillModule.BuildStandardActionBar(
s, Folder, Prefix,
onCreated: c => _listPane.Refresh(c),
onCloned: c => _listPane.Refresh(c),
onDeleted: () => _listPane.Refresh(null));
}
}
}