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:
2026-05-21 07:09:53 +08:00
parent f096105caf
commit bb3afd130f
41 changed files with 2417 additions and 1618 deletions

View File

@@ -35,6 +35,7 @@ namespace BaseGames.Editor
public string TypeName;
public string AssetPath;
public ScriptableObject Asset;
public string Guid;
}
private readonly List<CategoryEntry> _categories = new();
@@ -50,6 +51,7 @@ namespace BaseGames.Editor
private ListView _assetList;
private TextField _searchField;
private Label _statusLabel;
private VisualElement _renameContainer;
// ── 菜单入口 ──────────────────────────────────────────────────────────
@@ -141,12 +143,16 @@ namespace BaseGames.Editor
var rightPane = new VisualElement();
rightPane.style.flexDirection = FlexDirection.Column;
// 列标题行
// 列标题行paddingRight 额外加 13px滚动条宽度确保列对齐
var colHeader = new VisualElement();
colHeader.AddToClassList("pane-header");
colHeader.style.flexDirection = FlexDirection.Row;
colHeader.style.flexDirection = FlexDirection.Row;
colHeader.style.paddingLeft = 8;
colHeader.style.paddingRight = 21; // 8 + 13 (滚动条占位)
colHeader.style.paddingTop = 4;
colHeader.style.paddingBottom = 4;
colHeader.Add(MakeHeaderLabel("资产名", true, 0));
colHeader.Add(MakeHeaderLabel("类型", false, 170));
colHeader.Add(MakeHeaderLabel("类型", false, 190));
colHeader.Add(MakeHeaderLabel("路径", true, 0));
rightPane.Add(colHeader);
@@ -160,19 +166,30 @@ namespace BaseGames.Editor
_assetList.style.flexGrow = 1;
_assetList.selectionChanged += _ => OnAssetPicked();
_assetList.itemsChosen += _ => FocusProjectWindow();
_assetList.AddManipulator(new ContextualMenuManipulator(BuildAssetContextMenu));
rightPane.Add(_assetList);
// 滚动条常显,确保 header 列与内容列对齐
_assetList.schedule.Execute(() =>
{
var sv = _assetList.Q<ScrollView>();
if (sv != null) sv.verticalScrollerVisibility = ScrollerVisibility.AlwaysVisible;
});
// 状态栏
_statusLabel = new Label("—");
_statusLabel.style.paddingLeft = 8;
_statusLabel.style.paddingTop = 3;
_statusLabel.style.paddingBottom = 3;
_statusLabel.style.borderTopWidth = 1;
_statusLabel.style.borderTopColor = new Color(0.15f, 0.15f, 0.15f);
_statusLabel.style.color = new Color(0.58f, 0.58f, 0.58f);
_statusLabel.style.fontSize = 11;
_statusLabel.style.borderTopColor = new Color(0.0f, 0.0f, 0.0f, 0.20f);
_statusLabel.AddToClassList("so-path-label");
rightPane.Add(_statusLabel);
// 重命名容器(选中资产后显示)
_renameContainer = new VisualElement { style = { display = DisplayStyle.None } };
rightPane.Add(_renameContainer);
split.Add(leftPane);
split.Add(rightPane);
rootVisualElement.Add(split);
@@ -182,7 +199,9 @@ namespace BaseGames.Editor
{
var lbl = new Label(text);
lbl.style.unityFontStyleAndWeight = FontStyle.Bold;
lbl.style.overflow = Overflow.Hidden;
lbl.style.unityTextAlign = TextAnchor.MiddleLeft;
lbl.style.overflow = Overflow.Hidden;
lbl.style.minWidth = 0;
if (grow) lbl.style.flexGrow = 1;
if (fixedWidth > 0) lbl.style.width = fixedWidth;
return lbl;
@@ -219,22 +238,22 @@ namespace BaseGames.Editor
var nameEl = new Label { name = "n" };
nameEl.style.flexGrow = 1;
nameEl.style.minWidth = 0;
nameEl.style.overflow = Overflow.Hidden;
nameEl.style.textOverflow = TextOverflow.Ellipsis;
var typeEl = new Label { name = "t" };
typeEl.style.width = 170;
typeEl.style.overflow = Overflow.Hidden;
typeEl.style.width = 190;
typeEl.style.minWidth = 0;
typeEl.style.overflow = Overflow.Hidden;
typeEl.style.textOverflow = TextOverflow.Ellipsis;
typeEl.style.color = new Color(0.52f, 0.80f, 1.00f);
typeEl.style.fontSize = 11;
typeEl.AddToClassList("so-type-label");
var pathEl = new Label { name = "p" };
pathEl.style.flexGrow = 1;
pathEl.style.overflow = Overflow.Hidden;
pathEl.style.flexGrow = 1;
pathEl.style.overflow = Overflow.Hidden;
pathEl.style.textOverflow = TextOverflow.Ellipsis;
pathEl.style.color = new Color(0.48f, 0.48f, 0.48f);
pathEl.style.fontSize = 10;
pathEl.AddToClassList("so-path-label");
row.Add(nameEl);
row.Add(typeEl);
@@ -262,11 +281,38 @@ namespace BaseGames.Editor
private void OnAssetPicked()
{
int idx = _assetList.selectedIndex;
if (idx < 0 || idx >= _filtered.Count) return;
if (idx < 0 || idx >= _filtered.Count)
{
_renameContainer.style.display = DisplayStyle.None;
return;
}
var asset = _filtered[idx].Asset;
if (asset == null) return;
if (asset == null)
{
_renameContainer.style.display = DisplayStyle.None;
return;
}
EditorGUIUtility.PingObject(asset);
Selection.activeObject = asset;
// 更新重命名栏
_renameContainer.Clear();
var (pfx, conv) = EditorScaffoldUtils.GetAssetPrefixInfo(asset.GetType());
var entry = _filtered[idx];
string savedGuid = entry.Guid;
_renameContainer.Add(EditorScaffoldUtils.MakeRenameBar(asset, pfx, conv, _ =>
{
Refresh();
int ni = _filtered.FindIndex(e => e.Guid == savedGuid);
if (ni >= 0) _assetList.SetSelection(ni);
}));
// 删除按钮
var btnDelete = new Button(() => DeleteCurrentAsset(asset)) { text = "删除…" };
btnDelete.AddToClassList("action-button--danger");
_renameContainer.Add(btnDelete);
_renameContainer.style.display = DisplayStyle.Flex;
}
private static void FocusProjectWindow()
@@ -293,6 +339,7 @@ namespace BaseGames.Editor
TypeName = asset.GetType().Name,
AssetPath = path,
Asset = asset,
Guid = guid,
});
}
@@ -331,6 +378,26 @@ namespace BaseGames.Editor
ApplyFilter();
}
private void DeleteCurrentAsset(ScriptableObject asset)
{
if (!EditorScaffoldUtils.DeleteSOAsset(asset)) return;
_renameContainer.Clear();
_renameContainer.style.display = DisplayStyle.None;
Refresh();
}
private void BuildAssetContextMenu(ContextualMenuPopulateEvent evt)
{
int idx = _assetList.selectedIndex;
if (idx < 0 || idx >= _filtered.Count) return;
var asset = _filtered[idx].Asset;
if (asset == null) return;
evt.menu.AppendAction("在 Project 中定位", _ => EditorScaffoldUtils.PingAndSelect(asset));
evt.menu.AppendAction("在 Inspector 中打开", _ => Selection.activeObject = asset);
evt.menu.AppendSeparator();
evt.menu.AppendAction("删除…", _ => DeleteCurrentAsset(asset));
}
private void ApplyFilter()
{
_filtered.Clear();