refactor(editor): reorganize Editor directory and unify menu hierarchy
File directory changes (mirror Scripts/ module structure): - AbilityTypeDrawer.cs → Equipment/ - CharacterWizardWindow.cs → Character/ - FormEditorWindow.cs → Player/ - GMToolWindow.cs → Tools/ - SOManagerWindow.cs → Tools/ - Map/MapRoomDataEditor.cs → World/Map/ - Navigation/ (root) → Enemies/Navigation/ - Achievements/ → Progression/ Menu hierarchy changes (BaseGames/ top-level): - Data/: +Character Wizard (from Tools/), +Boss Skill Sequence (from Tools/) - Addressables/: +Addressable Batch Tool, +Asset Reference Graph, +Validate Address Keys (from Tools/Verification/) - Scene/Setup/: +Boot Flow Wizard, +Scaffold *, +Auto-Open Persistent (from Tools/) - Scene/: +Camera Area Setup (from Camera/), +Bake All NavSurfaces (from Tools/) - Events/: +Event Bus Monitor, +Event Chain Viewer, +Create/Reimport Event Channels (from Tools/) - Tools/Validation/: +Validate All SOs, +Apply/Validate Script Order (from Tools/ flat) - Tools/Maintenance/: +Missing Scripts/*, +Physics2D Layer Matrix/* (from Tools/ flat) Result: BaseGames/Tools/ reduced from 16 flat items to 4 items + 2 submenus Docs: update AssetFolderSpec §12 editor tool table with new menu paths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
243
Assets/_Game/Scripts/Editor/Equipment/AbilityTypeDrawer.cs
Normal file
243
Assets/_Game/Scripts/Editor/Equipment/AbilityTypeDrawer.cs
Normal file
@@ -0,0 +1,243 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using BaseGames.Player;
|
||||
|
||||
namespace BaseGames.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// AbilityType [Flags] uint 的 PropertyDrawer。
|
||||
/// 将枚举按能力类别分组,以可读的复选框网格呈现,替代默认的 MaskField。
|
||||
///
|
||||
/// 分组:
|
||||
/// 移动能力 — WallCling / WallJump / Dash / AirDash / DoubleJump / SuperJump / Swim / Dive
|
||||
/// 法术能力 — Spell1 / Spell2 / Spell3
|
||||
/// 形态能力 — SpiritForm / SpiritDash
|
||||
/// 战斗能力 — Parry / ChargeAttack / DownSlash
|
||||
/// 互动能力 — Interact / FastTravel
|
||||
/// 能力强化 — InvincibleDash
|
||||
/// </summary>
|
||||
[CustomPropertyDrawer(typeof(AbilityType))]
|
||||
public sealed class AbilityTypeDrawer : PropertyDrawer
|
||||
{
|
||||
// ── 分组定义 ──────────────────────────────────────────────────────────
|
||||
|
||||
private static readonly (string groupLabel, (AbilityType flag, string label)[] members)[] Groups =
|
||||
{
|
||||
("移动能力", new[]
|
||||
{
|
||||
(AbilityType.WallCling, "贴墙悬挂"),
|
||||
(AbilityType.WallJump, "墙跳"),
|
||||
(AbilityType.Dash, "冲刺"),
|
||||
(AbilityType.DoubleJump, "二段跳"),
|
||||
(AbilityType.SuperJump, "超级跳"),
|
||||
(AbilityType.Swim, "游泳"),
|
||||
(AbilityType.Dive, "下劈"),
|
||||
}),
|
||||
("法术能力", new[]
|
||||
{
|
||||
(AbilityType.Spell1, "法术槽 1"),
|
||||
(AbilityType.Spell2, "法术槽 2"),
|
||||
(AbilityType.Spell3, "法术槽 3"),
|
||||
}),
|
||||
("灵魄形态", new[]
|
||||
{
|
||||
(AbilityType.SpiritForm, "灵魄形态"),
|
||||
(AbilityType.SpiritDash, "灵魄冲刺"),
|
||||
}),
|
||||
("战斗能力", new[]
|
||||
{
|
||||
(AbilityType.Parry, "弹反"),
|
||||
(AbilityType.ChargeAttack, "蓄力攻击"),
|
||||
(AbilityType.DownSlash, "下斩"),
|
||||
}),
|
||||
("互动能力", new[]
|
||||
{
|
||||
(AbilityType.Interact, "互动"),
|
||||
(AbilityType.FastTravel, "快速旅行"),
|
||||
}),
|
||||
("能力强化", new[]
|
||||
{
|
||||
(AbilityType.InvincibleDash, "无敌冲刺"),
|
||||
}),
|
||||
};
|
||||
|
||||
// ── 布局常量 ──────────────────────────────────────────────────────────
|
||||
|
||||
private static readonly float RowH = EditorGUIUtility.singleLineHeight;
|
||||
private static readonly float GroupHeaderH = EditorGUIUtility.singleLineHeight + 4f;
|
||||
private static readonly float BtnRowH = EditorGUIUtility.singleLineHeight + 4f;
|
||||
private const float Spacing = 2f;
|
||||
private const float MinToggleW = 100f; // 每列最小宽度,用于动态计算列数
|
||||
private const int MaxColCount = 3; // 列数上限
|
||||
|
||||
// 缓存每个属性路径上次渲染时的列数,供 GetPropertyHeight 使用
|
||||
private static readonly Dictionary<string, int> _colsCache = new();
|
||||
|
||||
private static int ComputeCols(float availableWidth)
|
||||
=> Mathf.Clamp(Mathf.FloorToInt(availableWidth / MinToggleW), 1, MaxColCount);
|
||||
|
||||
// ── 高度计算 ──────────────────────────────────────────────────────────
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
float h = RowH + Spacing; // 属性标签行(含 None / All 按钮)
|
||||
|
||||
// 使用上次 OnGUI 缓存的列数;首次绘制前按视图宽度估算
|
||||
float viewW = EditorGUIUtility.currentViewWidth - EditorGUI.indentLevel * 15f - 18f;
|
||||
int cols = _colsCache.TryGetValue(property.propertyPath, out var cached)
|
||||
? cached
|
||||
: ComputeCols(viewW);
|
||||
|
||||
foreach (var (_, members) in Groups)
|
||||
{
|
||||
h += GroupHeaderH + Spacing;
|
||||
int rows = Mathf.CeilToInt((float)members.Length / cols);
|
||||
h += rows * (RowH + Spacing);
|
||||
}
|
||||
|
||||
return h + 4f;
|
||||
}
|
||||
|
||||
// ── 绘制 ──────────────────────────────────────────────────────────────
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
float y = position.y;
|
||||
float x = position.x;
|
||||
float w = position.width;
|
||||
int cols = ComputeCols(w);
|
||||
_colsCache[property.propertyPath] = cols;
|
||||
|
||||
uint current = (uint)property.longValue;
|
||||
bool changed = false;
|
||||
|
||||
// ── 统计已启用数量 ────────────────────────────────────────────────
|
||||
int enabledCount = 0, totalCount = 0;
|
||||
foreach (var (_, mems) in Groups)
|
||||
foreach (var (flag, _) in mems)
|
||||
{
|
||||
totalCount++;
|
||||
if ((current & (uint)flag) != 0) enabledCount++;
|
||||
}
|
||||
|
||||
// ── 标签行 + None / All 快捷按钮 + 数量提示 ──────────────────────
|
||||
Rect labelRect = new Rect(x, y, EditorGUIUtility.labelWidth, RowH);
|
||||
Rect btnNoneRect = new Rect(x + EditorGUIUtility.labelWidth, y, 50f, RowH);
|
||||
Rect btnAllRect = new Rect(btnNoneRect.xMax + 4f, y, 50f, RowH);
|
||||
Rect countRect = new Rect(btnAllRect.xMax + 8f, y, w - (btnAllRect.xMax + 8f - x), RowH);
|
||||
|
||||
EditorGUI.LabelField(labelRect, label);
|
||||
|
||||
if (GUI.Button(btnNoneRect, "None"))
|
||||
{
|
||||
current = 0;
|
||||
changed = true;
|
||||
}
|
||||
if (GUI.Button(btnAllRect, "All"))
|
||||
{
|
||||
uint all = 0;
|
||||
foreach (var (_, mems) in Groups)
|
||||
foreach (var (flag, _) in mems)
|
||||
all |= (uint)flag;
|
||||
current = all;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
var countStyle = new GUIStyle(EditorStyles.miniLabel)
|
||||
{
|
||||
normal = { textColor = enabledCount > 0
|
||||
? new Color(0.55f, 0.85f, 0.55f)
|
||||
: new Color(0.6f, 0.6f, 0.6f) }
|
||||
};
|
||||
EditorGUI.LabelField(countRect, $"({enabledCount} / {totalCount} 项已解锁)", countStyle);
|
||||
y += RowH + Spacing;
|
||||
|
||||
// ── 各分组 ────────────────────────────────────────────────────────
|
||||
foreach (var (groupLabel, members) in Groups)
|
||||
{
|
||||
// 分组标题(含组级全选/清空按钮)
|
||||
Rect groupRect = new Rect(x, y, w, GroupHeaderH);
|
||||
uint newCurrent = DrawGroupHeader(groupRect, groupLabel, members, current);
|
||||
if (newCurrent != current) { current = newCurrent; changed = true; }
|
||||
y += GroupHeaderH + Spacing;
|
||||
|
||||
// 复选框网格(列宽均分可用宽度,列数随窗口大小自动调整)
|
||||
float toggleW = w / cols;
|
||||
for (int i = 0; i < members.Length; i++)
|
||||
{
|
||||
int col = i % cols;
|
||||
|
||||
if (col == 0 && i > 0)
|
||||
y += RowH + Spacing; // 换行
|
||||
|
||||
Rect togRect = new Rect(x + col * toggleW, y, toggleW, RowH);
|
||||
|
||||
var (flag, toggleLabel) = members[i];
|
||||
bool isOn = (current & (uint)flag) != 0;
|
||||
bool newOn = GUI.Toggle(togRect, isOn, toggleLabel, EditorStyles.toggle);
|
||||
|
||||
if (newOn != isOn)
|
||||
{
|
||||
if (newOn) current |= (uint)flag;
|
||||
else current &= ~(uint)flag;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// 若是最后一个且在本行
|
||||
if (i == members.Length - 1)
|
||||
y += RowH + Spacing;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
property.longValue = (long)(uint)current;
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
// ── 辅助:分组标题绘制(含组级全选/清空按钮与已选数量)────────────────
|
||||
|
||||
private static uint DrawGroupHeader(Rect rect, string text,
|
||||
(AbilityType flag, string label)[] members, uint current)
|
||||
{
|
||||
// 计算本组已选数量
|
||||
int groupEnabled = 0;
|
||||
foreach (var (flag, _) in members)
|
||||
if ((current & (uint)flag) != 0) groupEnabled++;
|
||||
|
||||
Color old = GUI.backgroundColor;
|
||||
GUI.backgroundColor = new Color(0.25f, 0.25f, 0.28f, 1f);
|
||||
GUI.Box(new Rect(rect.x, rect.y, rect.width, rect.height - 2f), GUIContent.none, EditorStyles.helpBox);
|
||||
GUI.backgroundColor = old;
|
||||
|
||||
// 分组标签(含已选/总数)
|
||||
Rect labelRect = new Rect(rect.x + 4f, rect.y + 1f, rect.width - 86f, RowH);
|
||||
EditorGUI.LabelField(labelRect,
|
||||
$"{text} {groupEnabled}/{members.Length}",
|
||||
EditorStyles.boldLabel);
|
||||
|
||||
// 组级按钮:全选 / 清空
|
||||
const float BtnW = 36f;
|
||||
Rect btnAll = new Rect(rect.xMax - BtnW * 2 - 6f, rect.y + 1f, BtnW, RowH);
|
||||
Rect btnNone = new Rect(rect.xMax - BtnW - 4f, rect.y + 1f, BtnW, RowH);
|
||||
|
||||
if (GUI.Button(btnAll, "全选", EditorStyles.miniButton))
|
||||
foreach (var (flag, _) in members) current |= (uint)flag;
|
||||
if (GUI.Button(btnNone, "清空", EditorStyles.miniButton))
|
||||
foreach (var (flag, _) in members) current &= ~(uint)flag;
|
||||
|
||||
// 底部分割线
|
||||
EditorGUI.DrawRect(new Rect(rect.x, rect.yMax - 2f, rect.width, 1f),
|
||||
new Color(0.45f, 0.45f, 0.50f, 0.8f));
|
||||
|
||||
return current;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60df999cbd27df94eb8ffd215c336b27
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user