feat: Add SkillModule and WeaponModule for managing skills and weapons
- Implemented SkillModule to manage FormSkillSO assets with a detailed UI for editing and displaying skill properties. - Implemented WeaponModule to manage WeaponSO assets with a detailed UI for editing and displaying weapon properties. - Created AssetOperations class for centralized CRUD operations on ScriptableObject assets, including create, rename, delete, and clone functionalities. - Added DetailHeader for displaying and renaming asset names in the UI. - Introduced SoListPane for a reusable ScriptableObject list panel with search functionality and context menus. - Added meta files for all new scripts to ensure proper asset management in Unity.
This commit is contained in:
262
Assets/_Game/Scripts/Editor/Hub/DataHubWindow.cs
Normal file
262
Assets/_Game/Scripts/Editor/Hub/DataHubWindow.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
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());
|
||||
}
|
||||
|
||||
// ── 布局 ─────────────────────────────────────────────────────────────
|
||||
|
||||
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;
|
||||
split.Add(_listWrapper);
|
||||
|
||||
// 详情区容器
|
||||
_detailWrapper = new VisualElement();
|
||||
_detailWrapper.style.flexGrow = 1;
|
||||
split.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 icon = new Image { image = EditorGUIUtility.IconContent(module.IconName).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);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Editor/Hub/DataHubWindow.cs.meta
Normal file
11
Assets/_Game/Scripts/Editor/Hub/DataHubWindow.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95a89dac2a3cc7e439be075586617c88
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
28
Assets/_Game/Scripts/Editor/Hub/IDataModule.cs
Normal file
28
Assets/_Game/Scripts/Editor/Hub/IDataModule.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace BaseGames.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据模块接口 —— DataHubWindow 中每个资产管理标签页实现此接口。
|
||||
/// </summary>
|
||||
public interface IDataModule
|
||||
{
|
||||
string ModuleId { get; } // 持久化 EditorPrefs 用唯一 key
|
||||
string DisplayName { get; } // 导航侧边栏显示名称
|
||||
string IconName { get; } // Unity 内置图标名 or null
|
||||
|
||||
/// <summary>初始化模块,加载数据(首次激活时调用一次)。</summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>构建列表区内容,onSelected 在选中资产时由模块调用。</summary>
|
||||
void BuildListPane(VisualElement container, Action<UnityEngine.Object> onSelected);
|
||||
|
||||
/// <summary>构建详情区内容,selected 为当前选中资产(可为 null)。</summary>
|
||||
void BuildDetailPane(VisualElement container, UnityEngine.Object selected);
|
||||
|
||||
/// <summary>切换到本模块时调用,可用于刷新数据。</summary>
|
||||
void OnActivated();
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Editor/Hub/IDataModule.cs.meta
Normal file
11
Assets/_Game/Scripts/Editor/Hub/IDataModule.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2cd7579b2889e0943883000232e468dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user