using System; using UnityEditor; using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; using BaseGames.Dialogue; namespace BaseGames.Editor.Modules { /// /// DataHub 对话序列模块 —— 管理 DialogueSequenceSO 资产。 /// 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 _listPane; private DetailHeader _header; private DialogueSequenceSO _selected; public void Initialize() { _listPane = new SoListPane( Folder, Prefix, s => { int v = s.variants != null ? s.variants.Length : 0; return v > 0 ? $"{v}变体" : null; }); } public void BuildListPane(VisualElement container, Action 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)); } } }