- 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.
279 lines
11 KiB
C#
279 lines
11 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
using UnityEngine.UIElements;
|
||
using BaseGames.Editor.Modules;
|
||
|
||
namespace BaseGames.Editor
|
||
{
|
||
/// <summary>
|
||
/// 数据管理总枢纽窗口(DataHub)。
|
||
/// 布局:导航侧边栏(120px) | TwoPaneSplitView → 列表区(220px) + 详情区(flex)。
|
||
/// 菜单:BaseGames / Data Hub (priority=50)
|
||
/// </summary>
|
||
public class DataHubWindow : EditorWindow
|
||
{
|
||
private const string UssPath = "Assets/_Game/Scripts/Editor/UIToolkit/Editor.uss";
|
||
private const string PrefKey = "DataHub.ActiveModuleId";
|
||
private const float NavWidth = 120f;
|
||
private const float ListWidth = 220f;
|
||
private const float MinWinWidth = 680f;
|
||
private const float MinWinHeight = 420f;
|
||
|
||
[MenuItem("BaseGames/Data Hub", priority = 50)]
|
||
public static void Open()
|
||
{
|
||
var wnd = GetWindow<DataHubWindow>();
|
||
wnd.titleContent = new GUIContent("Data Hub", EditorGUIUtility.IconContent("d_ScriptableObject Icon").image);
|
||
wnd.minSize = new Vector2(MinWinWidth, MinWinHeight);
|
||
}
|
||
|
||
// ── 状态 ─────────────────────────────────────────────────────────────
|
||
|
||
private readonly List<IDataModule> _modules = new();
|
||
private readonly HashSet<string> _initializedIds = new();
|
||
private IDataModule _activeModule;
|
||
|
||
private VisualElement _navSidebar;
|
||
|
||
// 缓存:列表区和详情区引用(由 TwoPaneSplitView 子节点提供)
|
||
private VisualElement _listWrapper;
|
||
private VisualElement _detailWrapper;
|
||
|
||
// 当前选中资产
|
||
private UnityEngine.Object _selected;
|
||
|
||
// ── 生命周期 ──────────────────────────────────────────────────────────
|
||
|
||
public void CreateGUI()
|
||
{
|
||
// USS
|
||
var uss = AssetDatabase.LoadAssetAtPath<StyleSheet>(UssPath);
|
||
if (uss != null) rootVisualElement.styleSheets.Add(uss);
|
||
|
||
// 注册模块
|
||
RegisterModules();
|
||
|
||
// 构建 UI
|
||
BuildLayout();
|
||
|
||
// 恢复上次激活的模块
|
||
string savedId = EditorPrefs.GetString(PrefKey, string.Empty);
|
||
var toActivate = _modules.Find(m => m.ModuleId == savedId) ?? _modules.FirstOrDefault();
|
||
if (toActivate != null) ActivateModule(toActivate);
|
||
}
|
||
|
||
// ── 模块注册 ──────────────────────────────────────────────────────────
|
||
|
||
private void RegisterModules()
|
||
{
|
||
_modules.Clear();
|
||
_modules.Add(new WeaponModule());
|
||
_modules.Add(new SkillModule());
|
||
_modules.Add(new EnemyModule());
|
||
_modules.Add(new FormModule());
|
||
_modules.Add(new BossSkillModule());
|
||
_modules.Add(new CharmModule());
|
||
_modules.Add(new StreamingModule());
|
||
_modules.Add(new DialogueModule());
|
||
_modules.Add(new QuestModule());
|
||
_modules.Add(new ActorModule());
|
||
}
|
||
|
||
// ── 布局 ─────────────────────────────────────────────────────────────
|
||
|
||
private void BuildLayout()
|
||
{
|
||
var root = rootVisualElement;
|
||
root.style.flexDirection = FlexDirection.Row;
|
||
root.style.flexGrow = 1;
|
||
|
||
// 导航侧边栏
|
||
_navSidebar = BuildNavSidebar();
|
||
root.Add(_navSidebar);
|
||
|
||
// 垂直分隔线
|
||
var divider = new VisualElement();
|
||
divider.style.width = 1;
|
||
divider.style.backgroundColor = new StyleColor(new Color(0.5f, 0.5f, 0.5f, 0.25f));
|
||
root.Add(divider);
|
||
|
||
// TwoPaneSplitView(列表 + 详情)
|
||
var split = new TwoPaneSplitView(0, ListWidth, TwoPaneSplitViewOrientation.Horizontal);
|
||
split.style.flexGrow = 1;
|
||
root.Add(split);
|
||
|
||
// 列表区容器
|
||
_listWrapper = new VisualElement();
|
||
_listWrapper.style.flexGrow = 1;
|
||
_listWrapper.style.overflow = Overflow.Hidden;
|
||
split.Add(_listWrapper);
|
||
|
||
// 详情区:外层 ScrollView 提供滚动视口,_detailWrapper 是内容容器(自然高度)
|
||
var detailScroll = new ScrollView(ScrollViewMode.Vertical);
|
||
detailScroll.style.flexGrow = 1;
|
||
detailScroll.style.overflow = Overflow.Hidden;
|
||
split.Add(detailScroll);
|
||
|
||
_detailWrapper = new VisualElement();
|
||
_detailWrapper.style.flexDirection = FlexDirection.Column;
|
||
_detailWrapper.style.paddingBottom = 16;
|
||
detailScroll.Add(_detailWrapper);
|
||
}
|
||
|
||
private VisualElement BuildNavSidebar()
|
||
{
|
||
var sidebar = new VisualElement();
|
||
sidebar.style.width = NavWidth;
|
||
sidebar.style.flexShrink = 0;
|
||
sidebar.style.flexDirection = FlexDirection.Column;
|
||
sidebar.style.paddingTop = 8;
|
||
|
||
// 标题
|
||
var title = new Label("DATA HUB");
|
||
title.style.fontSize = 10;
|
||
title.style.opacity = 0.5f;
|
||
title.style.paddingLeft = 10;
|
||
title.style.marginBottom = 6;
|
||
title.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||
sidebar.Add(title);
|
||
|
||
foreach (var module in _modules)
|
||
{
|
||
var btn = BuildNavItem(module);
|
||
sidebar.Add(btn);
|
||
}
|
||
|
||
// 弹性填充
|
||
var spacer = new VisualElement();
|
||
spacer.style.flexGrow = 1;
|
||
sidebar.Add(spacer);
|
||
|
||
return sidebar;
|
||
}
|
||
|
||
private Button BuildNavItem(IDataModule module)
|
||
{
|
||
var btn = new Button(() => ActivateModule(module));
|
||
btn.name = "nav-" + module.ModuleId;
|
||
btn.style.flexDirection = FlexDirection.Row;
|
||
btn.style.alignItems = Align.Center;
|
||
btn.style.paddingLeft = 10;
|
||
btn.style.paddingRight = 8;
|
||
btn.style.paddingTop = 8;
|
||
btn.style.paddingBottom = 8;
|
||
btn.style.borderTopLeftRadius = 0;
|
||
btn.style.borderTopRightRadius = 0;
|
||
btn.style.borderBottomLeftRadius = 0;
|
||
btn.style.borderBottomRightRadius = 0;
|
||
btn.style.borderLeftWidth = 0;
|
||
btn.style.borderRightWidth = 0;
|
||
btn.style.borderTopWidth = 0;
|
||
btn.style.borderBottomWidth = 0;
|
||
btn.style.backgroundColor = new StyleColor(Color.clear);
|
||
btn.style.marginBottom = 2;
|
||
|
||
// 图标(容错:图标名无效时跳过,不报错)
|
||
if (!string.IsNullOrEmpty(module.IconName))
|
||
{
|
||
var content = EditorGUIUtility.IconContent(module.IconName);
|
||
if (content?.image != null)
|
||
{
|
||
var icon = new Image { image = content.image };
|
||
icon.style.width = 16;
|
||
icon.style.height = 16;
|
||
icon.style.marginRight = 6;
|
||
btn.Add(icon);
|
||
}
|
||
}
|
||
|
||
var label = new Label(module.DisplayName);
|
||
label.style.flexGrow = 1;
|
||
btn.Add(label);
|
||
|
||
return btn;
|
||
}
|
||
|
||
// ── 模块切换 ──────────────────────────────────────────────────────────
|
||
|
||
private void ActivateModule(IDataModule module)
|
||
{
|
||
if (_activeModule == module) return;
|
||
_activeModule = module;
|
||
_selected = null;
|
||
|
||
// 更新导航项视觉状态
|
||
foreach (var m in _modules)
|
||
{
|
||
var navBtn = _navSidebar.Q<Button>("nav-" + m.ModuleId);
|
||
if (navBtn == null) continue;
|
||
|
||
if (m == module)
|
||
{
|
||
navBtn.style.backgroundColor = new StyleColor(new Color(0.5f, 0.5f, 0.5f, 0.18f));
|
||
navBtn.style.borderLeftWidth = 3;
|
||
navBtn.style.borderLeftColor = new StyleColor(new Color(0.4f, 0.65f, 1f, 1f));
|
||
}
|
||
else
|
||
{
|
||
navBtn.style.backgroundColor = new StyleColor(Color.clear);
|
||
navBtn.style.borderLeftWidth = 0;
|
||
}
|
||
}
|
||
|
||
// 初始化模块(首次激活时调用一次)
|
||
if (!_initializedIds.Contains(module.ModuleId))
|
||
{
|
||
_initializedIds.Add(module.ModuleId);
|
||
module.Initialize();
|
||
}
|
||
module.OnActivated();
|
||
|
||
// 重建列表区
|
||
_listWrapper.Clear();
|
||
module.BuildListPane(_listWrapper, OnModuleSelected);
|
||
|
||
// 清空详情区
|
||
RebuildDetailPane(null);
|
||
|
||
EditorPrefs.SetString(PrefKey, module.ModuleId);
|
||
}
|
||
|
||
private void OnModuleSelected(UnityEngine.Object selected)
|
||
{
|
||
_selected = selected;
|
||
RebuildDetailPane(selected);
|
||
}
|
||
|
||
private void RebuildDetailPane(UnityEngine.Object selected)
|
||
{
|
||
_detailWrapper.Clear();
|
||
|
||
if (_activeModule == null) return;
|
||
|
||
if (selected == null)
|
||
{
|
||
var placeholder = new Label("← 从左侧列表选择一项");
|
||
placeholder.style.opacity = 0.45f;
|
||
placeholder.style.marginTop = 60;
|
||
placeholder.style.unityTextAlign = TextAnchor.MiddleCenter;
|
||
_detailWrapper.Add(placeholder);
|
||
return;
|
||
}
|
||
|
||
_activeModule.BuildDetailPane(_detailWrapper, selected);
|
||
}
|
||
|
||
// ── 公共辅助(供 Module 回调使用)────────────────────────────────────
|
||
|
||
/// <summary>通知 Hub 已完成重命名,需要刷新详情区标题。</summary>
|
||
public void NotifyRenamed(UnityEngine.Object asset)
|
||
{
|
||
if (_activeModule == null || asset == null) return;
|
||
RebuildDetailPane(asset);
|
||
}
|
||
}
|
||
}
|