diff --git a/Assets/_Game/Data/Player/Weapons/Weapon_DiHun.asset b/Assets/_Game/Data/Combat/Weapons/WPN_New.asset similarity index 99% rename from Assets/_Game/Data/Player/Weapons/Weapon_DiHun.asset rename to Assets/_Game/Data/Combat/Weapons/WPN_New.asset index 229e116..03b8473 100644 --- a/Assets/_Game/Data/Player/Weapons/Weapon_DiHun.asset +++ b/Assets/_Game/Data/Combat/Weapons/WPN_New.asset @@ -10,7 +10,7 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: d2443d04d1c179d4d8a4f36e7ca7156e, type: 3} - m_Name: Weapon_DiHun + m_Name: WPN_New m_EditorClassIdentifier: weaponId: displayName: diff --git a/Assets/_Game/Data/Player/Weapons/Weapon_TianHun.asset.meta b/Assets/_Game/Data/Combat/Weapons/WPN_New.asset.meta similarity index 79% rename from Assets/_Game/Data/Player/Weapons/Weapon_TianHun.asset.meta rename to Assets/_Game/Data/Combat/Weapons/WPN_New.asset.meta index f67e9cd..d555971 100644 --- a/Assets/_Game/Data/Player/Weapons/Weapon_TianHun.asset.meta +++ b/Assets/_Game/Data/Combat/Weapons/WPN_New.asset.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 76de8c0ba36dcce4db8990c5b62e9ed8 +guid: ce7bc9bad6f58ec42baff08f5353340e NativeFormatImporter: externalObjects: {} mainObjectFileID: 11400000 diff --git a/Assets/_Game/Data/Enemies/ENM_New_Stats.asset b/Assets/_Game/Data/Enemies/ENM_New_Stats.asset new file mode 100644 index 0000000..6da7a55 --- /dev/null +++ b/Assets/_Game/Data/Enemies/ENM_New_Stats.asset @@ -0,0 +1,28 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: ed4391dfa14c0304c8932f1ef9f8ce63, type: 3} + m_Name: ENM_New_Stats + m_EditorClassIdentifier: + MaxHP: 50 + Defense: 0 + WalkSpeed: 2 + RunSpeed: 4 + AttackDamage: 10 + AttackRange: 1.5 + AttackCooldown: 1 + DetectRange: 6 + KnockbackForce: 5 + HitStunDuration: 0.3 + EyeOffset: {x: 0, y: 0.8} + LOSBlockingMask: + serializedVersion: 2 + m_Bits: 1 diff --git a/Assets/_Game/Data/Player/Weapons/Weapon_DiHun.asset.meta b/Assets/_Game/Data/Enemies/ENM_New_Stats.asset.meta similarity index 79% rename from Assets/_Game/Data/Player/Weapons/Weapon_DiHun.asset.meta rename to Assets/_Game/Data/Enemies/ENM_New_Stats.asset.meta index a4b3032..d0f560c 100644 --- a/Assets/_Game/Data/Player/Weapons/Weapon_DiHun.asset.meta +++ b/Assets/_Game/Data/Enemies/ENM_New_Stats.asset.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: f80486c9cd3d1db459ce6df915b11546 +guid: e0cf93e053ead744fa1876771ba0d081 NativeFormatImporter: externalObjects: {} mainObjectFileID: 11400000 diff --git a/Assets/_Game/Data/Enemies/SKL_Boss_New.asset b/Assets/_Game/Data/Enemies/SKL_Boss_New.asset new file mode 100644 index 0000000..68fc23c --- /dev/null +++ b/Assets/_Game/Data/Enemies/SKL_Boss_New.asset @@ -0,0 +1,49 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: de92221c7c3fb4a42a7cd122a8f97632, type: 3} + m_Name: SKL_Boss_New + m_EditorClassIdentifier: + skillId: + displayName: + designNote: + category: 0 + skillType: 0 + availablePhaseIndices: + attackPatterns: [] + vulnerabilityWindows: [] + interactionTags: 0 + sequenceOnHit: {fileID: 0} + sequenceOnMiss: {fileID: 0} + counterResponses: [] + arenaEvents: [] + resourceCost: + resourceId: + cost: 0 + minRequired: 0 + buildsRage: 0 + poiseWindow: + Level: 0 + NormalizedStart: 0 + NormalizedEnd: 0 + skillAnimation: + _FadeDuration: 0.25 + _Speed: 1 + _Events: + _NormalizedTimes: [] + _Callbacks: [] + _Names: [] + _Clip: {fileID: 0} + _NormalizedStartTime: NaN + cooldown: 0 + references: + version: 2 + RefIds: [] diff --git a/Assets/_Game/Data/Player/Weapons/Weapon_MingHun.asset.meta b/Assets/_Game/Data/Enemies/SKL_Boss_New.asset.meta similarity index 79% rename from Assets/_Game/Data/Player/Weapons/Weapon_MingHun.asset.meta rename to Assets/_Game/Data/Enemies/SKL_Boss_New.asset.meta index f6dbc32..374a497 100644 --- a/Assets/_Game/Data/Player/Weapons/Weapon_MingHun.asset.meta +++ b/Assets/_Game/Data/Enemies/SKL_Boss_New.asset.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: d87ae01ed8a2b4f4cb27d159a52d1a14 +guid: a5d737a5b9641124aafb375d8684e06a NativeFormatImporter: externalObjects: {} mainObjectFileID: 11400000 diff --git a/Assets/_Game/Data/Player/Weapons/Weapon_MingHun.asset b/Assets/_Game/Data/Player/Weapons/Weapon_MingHun.asset deleted file mode 100644 index dcb1e08..0000000 --- a/Assets/_Game/Data/Player/Weapons/Weapon_MingHun.asset +++ /dev/null @@ -1,87 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &11400000 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: d2443d04d1c179d4d8a4f36e7ca7156e, type: 3} - m_Name: Weapon_MingHun - m_EditorClassIdentifier: - weaponId: - displayName: - icon: {fileID: 0} - weaponType: 0 - attack1Clip: - _FadeDuration: 0.25 - _Speed: 1 - _Events: - _NormalizedTimes: [] - _Callbacks: [] - _Names: [] - _Clip: {fileID: 0} - _NormalizedStartTime: NaN - attack2Clip: - _FadeDuration: 0.25 - _Speed: 1 - _Events: - _NormalizedTimes: [] - _Callbacks: [] - _Names: [] - _Clip: {fileID: 0} - _NormalizedStartTime: NaN - attack3Clip: - _FadeDuration: 0.25 - _Speed: 1 - _Events: - _NormalizedTimes: [] - _Callbacks: [] - _Names: [] - _Clip: {fileID: 0} - _NormalizedStartTime: NaN - airAttackClip: - _FadeDuration: 0.25 - _Speed: 1 - _Events: - _NormalizedTimes: [] - _Callbacks: [] - _Names: [] - _Clip: {fileID: 0} - _NormalizedStartTime: NaN - upAttackClip: - _FadeDuration: 0.25 - _Speed: 1 - _Events: - _NormalizedTimes: [] - _Callbacks: [] - _Names: [] - _Clip: {fileID: 0} - _NormalizedStartTime: NaN - downAttackClip: - _FadeDuration: 0.25 - _Speed: 1 - _Events: - _NormalizedTimes: [] - _Callbacks: [] - _Names: [] - _Clip: {fileID: 0} - _NormalizedStartTime: NaN - attack1Source: {fileID: 0} - attack2Source: {fileID: 0} - attack3Source: {fileID: 0} - airAttackSource: {fileID: 0} - upAttackSource: {fileID: 0} - downAttackSource: {fileID: 0} - hitBoxPrefab: {fileID: 0} - vfxConfig: - onEquipPresetId: - weaponTrailPrefab: {fileID: 0} - trailColor: {r: 1, g: 1, b: 1, a: 1} - soulPowerGain: 10 - references: - version: 2 - RefIds: [] diff --git a/Assets/_Game/Data/Player/Weapons/Weapon_TianHun.asset b/Assets/_Game/Data/Player/Weapons/Weapon_TianHun.asset deleted file mode 100644 index 4771d43..0000000 --- a/Assets/_Game/Data/Player/Weapons/Weapon_TianHun.asset +++ /dev/null @@ -1,87 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &11400000 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: d2443d04d1c179d4d8a4f36e7ca7156e, type: 3} - m_Name: Weapon_TianHun - m_EditorClassIdentifier: - weaponId: - displayName: - icon: {fileID: 0} - weaponType: 0 - attack1Clip: - _FadeDuration: 0.25 - _Speed: 1 - _Events: - _NormalizedTimes: [] - _Callbacks: [] - _Names: [] - _Clip: {fileID: 0} - _NormalizedStartTime: NaN - attack2Clip: - _FadeDuration: 0.25 - _Speed: 1 - _Events: - _NormalizedTimes: [] - _Callbacks: [] - _Names: [] - _Clip: {fileID: 0} - _NormalizedStartTime: NaN - attack3Clip: - _FadeDuration: 0.25 - _Speed: 1 - _Events: - _NormalizedTimes: [] - _Callbacks: [] - _Names: [] - _Clip: {fileID: 0} - _NormalizedStartTime: NaN - airAttackClip: - _FadeDuration: 0.25 - _Speed: 1 - _Events: - _NormalizedTimes: [] - _Callbacks: [] - _Names: [] - _Clip: {fileID: 0} - _NormalizedStartTime: NaN - upAttackClip: - _FadeDuration: 0.25 - _Speed: 1 - _Events: - _NormalizedTimes: [] - _Callbacks: [] - _Names: [] - _Clip: {fileID: 0} - _NormalizedStartTime: NaN - downAttackClip: - _FadeDuration: 0.25 - _Speed: 1 - _Events: - _NormalizedTimes: [] - _Callbacks: [] - _Names: [] - _Clip: {fileID: 0} - _NormalizedStartTime: NaN - attack1Source: {fileID: 0} - attack2Source: {fileID: 0} - attack3Source: {fileID: 0} - airAttackSource: {fileID: 0} - upAttackSource: {fileID: 0} - downAttackSource: {fileID: 0} - hitBoxPrefab: {fileID: 0} - vfxConfig: - onEquipPresetId: - weaponTrailPrefab: {fileID: 0} - trailColor: {r: 1, g: 1, b: 1, a: 1} - soulPowerGain: 10 - references: - version: 2 - RefIds: [] diff --git a/Assets/_Game/Data/Progression/Skills/SKL_New.asset b/Assets/_Game/Data/Progression/Skills/SKL_New.asset new file mode 100644 index 0000000..61c132b --- /dev/null +++ b/Assets/_Game/Data/Progression/Skills/SKL_New.asset @@ -0,0 +1,46 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a96f0270221c8444b8719f0f9b14c635, type: 3} + m_Name: SKL_New + m_EditorClassIdentifier: + skillId: + displayNameKey: + descriptionKey: + icon: {fileID: 0} + resourceType: 1 + baseCost: 0 + cooldown: 0 + castAnimation: + _FadeDuration: 0.25 + _Speed: 1 + _Events: + _NormalizedTimes: [] + _Callbacks: [] + _Names: [] + _Clip: {fileID: 0} + _NormalizedStartTime: NaN + castLockDuration: 0 + effectType: 1 + damageSource: {fileID: 0} + projectileConfig: {fileID: 0} + isHoming: 0 + holdForContinuous: 0 + dashForce: 0 + dashDuration: 0 + isInvincibleDuringDash: 0 + explosionDelay: 0 + explosionRadius: 0 + castFeedback: {fileID: 0} + SkillHitBoxPrefab: {fileID: 0} + references: + version: 2 + RefIds: [] diff --git a/Assets/_Game/Data/Progression/Skills/SKL_New.asset.meta b/Assets/_Game/Data/Progression/Skills/SKL_New.asset.meta new file mode 100644 index 0000000..6ead2d9 --- /dev/null +++ b/Assets/_Game/Data/Progression/Skills/SKL_New.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d00e0d6104f281345b8978d3a72eed13 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Scenes/Testings/TestRoomA.unity b/Assets/_Game/Scenes/Testings/TestRoomA.unity index 6fac123..517bda4 100644 --- a/Assets/_Game/Scenes/Testings/TestRoomA.unity +++ b/Assets/_Game/Scenes/Testings/TestRoomA.unity @@ -2400,7 +2400,7 @@ Transform: m_GameObject: {fileID: 123526430} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalPosition: {x: 0, y: -1.94, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -3885,7 +3885,7 @@ Transform: m_GameObject: {fileID: 173935938} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalPosition: {x: 0, y: 0.93, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -5123,7 +5123,7 @@ Transform: m_GameObject: {fileID: 225562483} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalPosition: {x: 2.1, y: 2.25, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -8598,6 +8598,7 @@ MonoBehaviour: _onCharmEquipped: {fileID: 0} _onCharmUnequipped: {fileID: 0} _onEquipmentChanged: {fileID: 0} + _onAchievementNotchGranted: {fileID: 0} --- !u!114 &430284915 MonoBehaviour: m_ObjectHideFlags: 0 @@ -18864,7 +18865,7 @@ Transform: m_GameObject: {fileID: 1108462349} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalPosition: {x: 1.72, y: -1.15, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -18895,7 +18896,7 @@ Transform: m_GameObject: {fileID: 1112393102} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalPosition: {x: 1.09, y: 0.6, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -21066,7 +21067,7 @@ MonoBehaviour: m_EditorClassIdentifier: _linkedDoor: {fileID: 1167097821} _spawnPoint: {fileID: 1655723310} - _facingDirectionOnArrive: 1 + _facingDirectionOnArrive: -1 _autoTrigger: 1 _transitionOut: {fileID: 0} _transitionIn: {fileID: 0} @@ -27623,7 +27624,7 @@ Transform: m_GameObject: {fileID: 1655723309} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalPosition: {x: -1.26, y: -1.09, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] diff --git a/Assets/_Game/Scripts/Editor/Character/CharacterWizardWindow.cs b/Assets/_Game/Scripts/Editor/Character/CharacterWizardWindow.cs index 6576797..da964dc 100644 --- a/Assets/_Game/Scripts/Editor/Character/CharacterWizardWindow.cs +++ b/Assets/_Game/Scripts/Editor/Character/CharacterWizardWindow.cs @@ -182,9 +182,7 @@ namespace BaseGames.Editor root.Add(MakeSectionHeader("▶ 专项编辑器")); var jumpGroup = MakeActionGroup(); - jumpGroup.Add(MakeJumpButton("武器编辑器", () => Combat.WeaponEditorWindow.Open())); - jumpGroup.Add(MakeJumpButton("技能编辑器", () => Skills.SkillEditorWindow.Open())); - jumpGroup.Add(MakeJumpButton("形态编辑器", () => FormEditorWindow.Open())); + jumpGroup.Add(MakeJumpButton("Data Hub(武器/技能/形态)", DataHubWindow.Open)); jumpGroup.Add(MakeJumpButton("SO 全局校验", SOValidationRunner.ValidateMenu)); root.Add(jumpGroup); @@ -248,7 +246,7 @@ namespace BaseGames.Editor root.Add(MakeSectionHeader("▶ 专项编辑器")); var jumpGroup = MakeActionGroup(); - jumpGroup.Add(MakeJumpButton("敌人数据管理", () => Enemies.EnemyDataWindow.Open())); + jumpGroup.Add(MakeJumpButton("Data Hub(敌人数据)", DataHubWindow.Open)); jumpGroup.Add(MakeJumpButton("SO 全局校验", SOValidationRunner.ValidateMenu)); root.Add(jumpGroup); @@ -295,7 +293,7 @@ namespace BaseGames.Editor var jumpGroup = MakeActionGroup(); jumpGroup.Add(MakeJumpButton("Boss 技能序列查看器", BossSkillSequenceWindow.OpenWindow)); - jumpGroup.Add(MakeJumpButton("敌人数据管理", () => Enemies.EnemyDataWindow.Open())); + jumpGroup.Add(MakeJumpButton("Data Hub(Boss技能)", DataHubWindow.Open)); jumpGroup.Add(MakeJumpButton("SO 全局校验", SOValidationRunner.ValidateMenu)); root.Add(jumpGroup); @@ -763,7 +761,7 @@ namespace BaseGames.Editor { var sep = new VisualElement(); sep.style.height = 1; - sep.style.backgroundColor = new Color(0.3f, 0.3f, 0.3f, 0.6f); + sep.style.backgroundColor = new Color(0.5f, 0.5f, 0.5f, 0.25f); sep.style.marginTop = 8; sep.style.marginBottom = 8; return sep; diff --git a/Assets/_Game/Scripts/Editor/Combat/WeaponEditorWindow.cs b/Assets/_Game/Scripts/Editor/Combat/WeaponEditorWindow.cs deleted file mode 100644 index 82c2508..0000000 --- a/Assets/_Game/Scripts/Editor/Combat/WeaponEditorWindow.cs +++ /dev/null @@ -1,334 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Animancer; -using UnityEditor; -using UnityEditor.UIElements; -using UnityEngine; -using UnityEngine.UIElements; -using BaseGames.Combat; -using BaseGames.Player; - -namespace BaseGames.Editor.Combat -{ - /// - /// 武器数据管理窗口(W-02)。 - /// 技术:UI Toolkit TwoPaneSplitView。 - /// 菜单:BaseGames / Data / Weapon Editor - /// - /// 左栏:可搜索的 WeaponSO 列表 + [新建] 按钮。 - /// 右栏:选中武器的完整属性编辑 + HitBox Prefab 结构校验 + 快速操作。 - /// - public class WeaponEditorWindow : EditorWindow - { - private static readonly StyleSheet _sharedUSS; - - static WeaponEditorWindow() - { - _sharedUSS = AssetDatabase.LoadAssetAtPath( - "Assets/_Game/Scripts/Editor/UIToolkit/Editor.uss"); - } - - [MenuItem("BaseGames/Data/Weapon Editor", priority = 100)] - public static void Open() - { - var wnd = GetWindow(); - wnd.titleContent = new GUIContent("Weapon Editor"); - wnd.minSize = new Vector2(680, 400); - } - - // ── 状态 ───────────────────────────────────────────────────────────── - private List _weapons = new(); - private List _filtered = new(); - private ListView _listView; - private VisualElement _detailRoot; - private string _searchText = ""; - private InspectorElement _currentInspector; - - // ── 生命周期 ────────────────────────────────────────────────────────── - - public void CreateGUI() - { - if (_sharedUSS != null) - rootVisualElement.styleSheets.Add(_sharedUSS); - - // Toolbar - var toolbar = new Toolbar(); - var searchField = new ToolbarSearchField { style = { flexGrow = 1 } }; - searchField.RegisterValueChangedCallback(e => - { - _searchText = e.newValue; - RefreshFilter(); - }); - toolbar.Add(searchField); - - var btnCreate = new ToolbarButton(CreateNewWeapon) { text = "+ 新建武器" }; - toolbar.Add(btnCreate); - - var btnRefresh = new ToolbarButton(RefreshAll) { text = "↺" }; - btnRefresh.tooltip = "重新扫描 Project 中的 WeaponSO 资产"; - toolbar.Add(btnRefresh); - - rootVisualElement.Add(toolbar); - - // Split view - var split = new TwoPaneSplitView(0, 220, TwoPaneSplitViewOrientation.Horizontal); - - // ── 左栏 ────────────────────────────────────────────────────── - var leftPane = new VisualElement { style = { minWidth = 140 } }; - - _listView = new ListView - { - selectionType = SelectionType.Single, - fixedItemHeight = 22, - makeItem = MakeListItem, - bindItem = BindListItem, - style = { flexGrow = 1 }, - }; - _listView.selectionChanged += OnSelectionChanged; - leftPane.Add(_listView); - split.Add(leftPane); - - // ── 右栏 ────────────────────────────────────────────────────── - _detailRoot = new ScrollView { style = { flexGrow = 1 } }; - _detailRoot.AddToClassList("detail-panel"); - split.Add(_detailRoot); - - rootVisualElement.Add(split); - - RefreshAll(); - } - - private void OnFocus() => RefreshAll(); - - // ── 列表构建 ────────────────────────────────────────────────────────── - - private void RefreshAll() - { - _weapons = EditorScaffoldUtils.FindAllAssetsOfType(); - _weapons.Sort((a, b) => string.Compare( - a.weaponId, b.weaponId, StringComparison.OrdinalIgnoreCase)); - RefreshFilter(); - } - - private void RefreshFilter() - { - if (string.IsNullOrEmpty(_searchText)) - { - _filtered = new List(_weapons); - } - else - { - string s = _searchText; - _filtered = _weapons.Where(w => w != null && - (w.weaponId?.Contains(s, StringComparison.OrdinalIgnoreCase) == true || - w.displayName?.Contains(s, StringComparison.OrdinalIgnoreCase) == true)).ToList(); - } - - _listView.itemsSource = _filtered; - _listView.Rebuild(); - } - - private static VisualElement MakeListItem() - { - var label = new Label(); - label.AddToClassList("list-item"); - return label; - } - - private void BindListItem(VisualElement element, int index) - { - var label = (Label)element; - var weapon = _filtered.Count > index ? _filtered[index] : null; - if (weapon == null) { label.text = "(null)"; return; } - - label.text = string.IsNullOrEmpty(weapon.displayName) - ? weapon.weaponId - : $"{weapon.weaponId} ({weapon.displayName})"; - } - - // ── 详情面板 ────────────────────────────────────────────────────────── - - private void OnSelectionChanged(IEnumerable items) - { - _detailRoot.Clear(); - _currentInspector = null; - - var weapon = items.FirstOrDefault() as WeaponSO; - if (weapon == null) return; - - // 标题 - var title = new Label( - string.IsNullOrEmpty(weapon.displayName) ? weapon.weaponId : $"{weapon.weaponId} · {weapon.displayName}") - { - style = - { - fontSize = 14, - unityFontStyleAndWeight = FontStyle.Bold, - marginBottom = 6, - } - }; - _detailRoot.Add(title); - - // HitBox Prefab 状态 - BuildHitBoxStatus(weapon); - - // 连击链预览 - BuildComboPreview(weapon); - - // Inspector 完整属性编辑 - _currentInspector = new InspectorElement(weapon); - _detailRoot.Add(_currentInspector); - - // 操作按钮 - var btnRow = new VisualElement(); - btnRow.AddToClassList("action-buttons"); - - var btnSelect = new Button(() => EditorScaffoldUtils.PingAndSelect(weapon)) - { text = "在 Project 中定位" }; - var btnInspector = new Button(() => Selection.activeObject = weapon) - { text = "在 Inspector 中打开" }; - var btnWizard = new Button(WeaponHitBoxWizard.Open) - { text = "HitBox Prefab 向导…" }; - - btnRow.Add(btnSelect); - btnRow.Add(btnInspector); - btnRow.Add(btnWizard); - _detailRoot.Add(btnRow); - } - - /// 连击序列数值横排预览。 - private void BuildComboPreview(WeaponSO weapon) - { - if (weapon.groundComboSteps == null || weapon.groundComboSteps.Length == 0) - return; - - var section = new Label("连击链预览") { style = { unityFontStyleAndWeight = FontStyle.Bold, marginBottom = 4 } }; - _detailRoot.Add(section); - - var chain = new VisualElement(); - chain.AddToClassList("stats-preview"); - - for (int i = 0; i < weapon.groundComboSteps.Length; i++) - { - var step = weapon.groundComboSteps[i]; - bool addArrow = i < weapon.groundComboSteps.Length - 1; - - var cell = new VisualElement - { - style = - { - alignItems = Align.Center, - marginRight = 4, - paddingLeft = 6, - paddingRight = 6, - paddingTop = 3, - paddingBottom = 3, - backgroundColor = new Color(0.25f, 0.25f, 0.28f, 1f), - borderTopLeftRadius = 3, - borderTopRightRadius = 3, - borderBottomLeftRadius = 3, - borderBottomRightRadius = 3, - } - }; - - cell.Add(new Label($"攻击{i + 1}") - { - style = { fontSize = 10, color = new Color(0.65f, 0.65f, 0.65f) } - }); - - string clipName = step.clip?.Clip != null ? step.clip.Clip.name : "<无动画>"; - cell.Add(new Label(clipName) - { - style = { fontSize = 11, unityFontStyleAndWeight = FontStyle.Bold } - }); - - if (step.damageSource != null) - { - int dmg = Mathf.RoundToInt(step.damageSource.BaseDamage * step.damageSource.DamageMultiplier); - cell.Add(new Label($"伤害 {dmg} [{step.damageSource.BreakLevel}]") - { - style = { fontSize = 10, color = new Color(1f, 0.7f, 0.3f) } - }); - } - else - { - cell.Add(new Label("(无 DamageSource)") - { - style = { fontSize = 10, color = new Color(0.8f, 0.3f, 0.3f) } - }); - } - - chain.Add(cell); - - if (addArrow) - chain.Add(new Label("→") { style = { alignSelf = Align.Center, marginLeft = 2, marginRight = 2 } }); - } - - _detailRoot.Add(chain); - - // 追加空中/上/下攻击的简要行 - var extraRow = new VisualElement - { - style = { flexDirection = FlexDirection.Row, flexWrap = Wrap.Wrap, marginBottom = 6, paddingLeft = 6 } - }; - - void ExtraStat(string label, DamageSourceSO src) - { - if (src == null) return; - int dmg = Mathf.RoundToInt(src.BaseDamage * src.DamageMultiplier); - extraRow.Add(new Label($"{label}:{dmg} [{src.BreakLevel}]") - { - style = { marginRight = 14, fontSize = 11, color = new Color(0.7f, 0.7f, 0.7f) } - }); - } - - ExtraStat("空中", weapon.airComboSteps?[0].damageSource); - ExtraStat("上劈", weapon.upStep.damageSource); - ExtraStat("下劈", weapon.downStep.damageSource); - - if (extraRow.childCount > 0) - _detailRoot.Add(extraRow); - } - - private void BuildHitBoxStatus(WeaponSO weapon) - { - HelpBoxMessageType msgType; - string msg; - - if (weapon.hitBoxPrefab == null) - { - msgType = HelpBoxMessageType.Warning; - msg = "hitBoxPrefab 未赋值!请创建并关联武器 HitBox Prefab。"; - } - else if (weapon.hitBoxPrefab.GetComponent() == null) - { - msgType = HelpBoxMessageType.Error; - msg = $"hitBoxPrefab「{weapon.hitBoxPrefab.name}」缺少 WeaponHitBoxInstance 组件!"; - } - else - { - msgType = HelpBoxMessageType.Info; - msg = $"HitBox Prefab 结构正常:{weapon.hitBoxPrefab.name}"; - } - - _detailRoot.Add(new HelpBox(msg, msgType) { style = { marginBottom = 6 } }); - } - - // ── 新建武器 ────────────────────────────────────────────────────────── - - private void CreateNewWeapon() - { - var asset = EditorScaffoldUtils.CreateSOAsset( - "Assets/_Game/Data/Combat/Weapons", "WPN_New"); - - if (asset != null) - { - RefreshAll(); - int idx = _filtered.IndexOf(asset); - if (idx >= 0) - _listView.SetSelection(idx); - } - } - } -} diff --git a/Assets/_Game/Scripts/Editor/Enemies/EnemyDataWindow.cs b/Assets/_Game/Scripts/Editor/Enemies/EnemyDataWindow.cs deleted file mode 100644 index f1ddd63..0000000 --- a/Assets/_Game/Scripts/Editor/Enemies/EnemyDataWindow.cs +++ /dev/null @@ -1,347 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEditor; -using UnityEditor.UIElements; -using UnityEngine; -using UnityEngine.UIElements; -using BaseGames.Enemies; - -namespace BaseGames.Editor.Enemies -{ - /// - /// 敌人数据管理窗口(W-05)。 - /// 技术:UI Toolkit TwoPaneSplitView + 手动标签页。 - /// 菜单:BaseGames / Data / Enemy Data Manager - /// - /// 左栏:可搜索的 EnemyStatsSO 列表 + [新建] 按钮。 - /// 右栏两个标签页: - /// Stats — EnemyStatsSO 完整属性编辑 - /// Loot — LootTableSO 浏览与编辑 - /// - public class EnemyDataWindow : EditorWindow - { - private static readonly StyleSheet _sharedUSS; - - static EnemyDataWindow() - { - _sharedUSS = AssetDatabase.LoadAssetAtPath( - "Assets/_Game/Scripts/Editor/UIToolkit/Editor.uss"); - } - - [MenuItem("BaseGames/Data/Enemy Data Manager", priority = 102)] - public static void Open() - { - var wnd = GetWindow(); - wnd.titleContent = new GUIContent("Enemy Data Manager"); - wnd.minSize = new Vector2(720, 420); - } - - // ── 状态 ───────────────────────────────────────────────────────────── - private List _enemies = new(); - private List _filtered = new(); - private List _lootTables = new(); - private List _lootFiltered = new(); - - private ListView _enemyList; - private ListView _lootList; - private VisualElement _detailRoot; // Stats 标签页 Loot 详情区 - private ScrollView _lootDetailRoot; // Loot 标签页 LootTable 详情区 - private VisualElement _tabStats; - private VisualElement _tabLoot; - private Button _btnStats; - private Button _btnLoot; - private string _searchText = ""; - private string _lootSearchText = ""; - private int _activeTab = 0; // 0=Stats, 1=Loot - - private InspectorElement _statsInspector; - private InspectorElement _lootInspector; - - // ── 生命周期 ────────────────────────────────────────────────────────── - - public void CreateGUI() - { - if (_sharedUSS != null) - rootVisualElement.styleSheets.Add(_sharedUSS); - - // Toolbar - var toolbar = new Toolbar(); - var searchField = new ToolbarSearchField { style = { flexGrow = 1 } }; - searchField.RegisterValueChangedCallback(e => { _searchText = e.newValue; RefreshEnemyFilter(); }); - searchField.tooltip = "按名称 / ID 过滤 EnemyStatsSO 列表"; - toolbar.Add(searchField); - - var btnCreate = new ToolbarButton(CreateNewEnemyStats) { text = "+ 新建敌人" }; - var btnRefresh = new ToolbarButton(RefreshAll) { text = "↺" }; - btnRefresh.tooltip = "重新扫描 Project 中的资产"; - toolbar.Add(btnCreate); - toolbar.Add(btnRefresh); - rootVisualElement.Add(toolbar); - - // Split view - var split = new TwoPaneSplitView(0, 230, TwoPaneSplitViewOrientation.Horizontal); - - // ── 左栏:敌人列表 ──────────────────────────────────────────── - var leftPane = new VisualElement { style = { minWidth = 150 } }; - _enemyList = new ListView - { - selectionType = SelectionType.Single, - fixedItemHeight = 22, - makeItem = MakeEnemyItem, - bindItem = BindEnemyItem, - style = { flexGrow = 1 }, - }; - _enemyList.selectionChanged += OnEnemySelected; - leftPane.Add(_enemyList); - split.Add(leftPane); - - // ── 右栏:标签页 + 内容 ─────────────────────────────────────── - var rightPane = new VisualElement { style = { flexGrow = 1 } }; - - // 标签页按钮栏 - var tabBar = new VisualElement(); - tabBar.AddToClassList("tab-bar"); - - _btnStats = new Button(() => ActivateTab(0)) { text = "Stats" }; - _btnLoot = new Button(() => ActivateTab(1)) { text = "Loot Table" }; - _btnStats.AddToClassList("tab-button"); - _btnLoot.AddToClassList("tab-button"); - tabBar.Add(_btnStats); - tabBar.Add(_btnLoot); - rightPane.Add(tabBar); - - // Stats 面板 - _tabStats = new ScrollView { style = { flexGrow = 1 } }; - _tabStats.AddToClassList("detail-panel"); - rightPane.Add(_tabStats); - - // Loot 面板(初始隐藏) - _tabLoot = BuildLootPanel(); - _tabLoot.style.display = DisplayStyle.None; - rightPane.Add(_tabLoot); - - split.Add(rightPane); - rootVisualElement.Add(split); - - ActivateTab(0); - RefreshAll(); - } - - private void OnFocus() => RefreshAll(); - - // ── 标签页切换 ──────────────────────────────────────────────────────── - - private void ActivateTab(int tab) - { - _activeTab = tab; - - _tabStats.style.display = tab == 0 ? DisplayStyle.Flex : DisplayStyle.None; - _tabLoot.style.display = tab == 1 ? DisplayStyle.Flex : DisplayStyle.None; - - _btnStats.EnableInClassList("tab-button--active", tab == 0); - _btnLoot.EnableInClassList("tab-button--active", tab == 1); - } - - // ── 敌人列表 ────────────────────────────────────────────────────────── - - private void RefreshAll() - { - _enemies = EditorScaffoldUtils.FindAllAssetsOfType(); - _enemies.Sort((a, b) => string.Compare(a.name, b.name, StringComparison.OrdinalIgnoreCase)); - - _lootTables = EditorScaffoldUtils.FindAllAssetsOfType(); - _lootTables.Sort((a, b) => string.Compare(a.name, b.name, StringComparison.OrdinalIgnoreCase)); - - RefreshEnemyFilter(); - RefreshLootFilter(); - } - - private void RefreshEnemyFilter() - { - _filtered = string.IsNullOrEmpty(_searchText) - ? new List(_enemies) - : _enemies.Where(e => e != null && - e.name.Contains(_searchText, StringComparison.OrdinalIgnoreCase)).ToList(); - _enemyList.itemsSource = _filtered; - _enemyList.Rebuild(); - } - - private static VisualElement MakeEnemyItem() - { - var label = new Label(); - label.AddToClassList("list-item"); - return label; - } - - private void BindEnemyItem(VisualElement element, int index) - { - var label = (Label)element; - var enemy = _filtered.Count > index ? _filtered[index] : null; - label.text = enemy != null ? enemy.name : "(null)"; - } - - private void OnEnemySelected(IEnumerable items) - { - _tabStats.Clear(); - _statsInspector = null; - - var enemy = items.FirstOrDefault() as EnemyStatsSO; - if (enemy == null) return; - - // 数值快览条 - BuildStatsPreview(enemy); - - // 完整属性编辑 - _statsInspector = new InspectorElement(enemy); - _tabStats.Add(_statsInspector); - - // 操作按钮 - var btnRow = new VisualElement(); - btnRow.AddToClassList("action-buttons"); - btnRow.Add(new Button(() => EditorScaffoldUtils.PingAndSelect(enemy)) { text = "在 Project 中定位" }); - btnRow.Add(new Button(() => Selection.activeObject = enemy) { text = "在 Inspector 中打开" }); - btnRow.Add(new Button(() => CloneEnemy(enemy)) { text = "克隆为变体…" }); - _tabStats.Add(btnRow); - } - - private void BuildStatsPreview(EnemyStatsSO e) - { - var row = new VisualElement(); - row.AddToClassList("stats-preview"); - - void Stat(string label, string val) - { - row.Add(new Label(label) { style = { color = new Color(0.65f, 0.65f, 0.65f), marginRight = 3 } }); - row.Add(new Label(val) { style = { marginRight = 14, unityFontStyleAndWeight = FontStyle.Bold } }); - } - - Stat("HP:", $"{e.MaxHP}"); - Stat("DEF:", $"{e.Defense}"); - Stat("ATK:", $"{e.AttackDamage}"); - Stat("SPD:", $"{e.WalkSpeed}/{e.RunSpeed}"); - Stat("范围:", $"{e.AttackRange:F1}"); - Stat("视野:", $"{e.DetectRange:F1}"); - - _tabStats.Add(row); - } - - private void CloneEnemy(EnemyStatsSO source) - { - string name = source.name; - string clone = EditorUtility.SaveFilePanelInProject( - "克隆敌人配置", $"{name}_Clone", "asset", - "选择克隆 EnemyStatsSO 的保存路径"); - if (string.IsNullOrEmpty(clone)) return; - - var asset = Instantiate(source); - AssetDatabase.CreateAsset(asset, clone); - AssetDatabase.SaveAssets(); - EditorScaffoldUtils.PingAndSelect(asset); - RefreshAll(); - } - - // ── Loot Table 面板 ─────────────────────────────────────────────────── - - private VisualElement BuildLootPanel() - { - var container = new VisualElement { style = { flexGrow = 1 } }; - - // Loot 搜索栏 - var lootToolbar = new Toolbar(); - var lootSearch = new ToolbarSearchField { style = { flexGrow = 1 } }; - lootSearch.RegisterValueChangedCallback(e => { _lootSearchText = e.newValue; RefreshLootFilter(); }); - lootSearch.tooltip = "过滤 LootTableSO 列表"; - lootToolbar.Add(lootSearch); - - var btnCreateLoot = new ToolbarButton(CreateNewLootTable) { text = "+ 新建 LootTable" }; - lootToolbar.Add(btnCreateLoot); - container.Add(lootToolbar); - - // 左右分割:Loot 列表 + Loot 详情 - var lootSplit = new TwoPaneSplitView(0, 200, TwoPaneSplitViewOrientation.Horizontal); - - var lootLeft = new VisualElement { style = { minWidth = 120 } }; - _lootList = new ListView - { - selectionType = SelectionType.Single, - fixedItemHeight = 22, - makeItem = () => { var l = new Label(); l.AddToClassList("list-item"); return l; }, - bindItem = (el, idx) => - { - var lbl = (Label)el; - var loot = _lootFiltered.Count > idx ? _lootFiltered[idx] : null; - lbl.text = loot?.name ?? "(null)"; - }, - style = { flexGrow = 1 }, - }; - _lootList.selectionChanged += OnLootSelected; - lootLeft.Add(_lootList); - lootSplit.Add(lootLeft); - - _lootDetailRoot = new ScrollView { style = { flexGrow = 1 } }; - _lootDetailRoot.AddToClassList("detail-panel"); - lootSplit.Add(_lootDetailRoot); - - container.Add(lootSplit); - return container; - } - - private void RefreshLootFilter() - { - _lootFiltered = string.IsNullOrEmpty(_lootSearchText) - ? new List(_lootTables) - : _lootTables.Where(l => l != null && - l.name.Contains(_lootSearchText, StringComparison.OrdinalIgnoreCase)).ToList(); - _lootList.itemsSource = _lootFiltered; - _lootList.Rebuild(); - } - - private void OnLootSelected(IEnumerable items) - { - _lootDetailRoot.Clear(); - _lootInspector = null; - - var loot = items.FirstOrDefault() as LootTableSO; - if (loot == null) return; - - var title = new Label($"Loot:{loot.name}") - { - style = { fontSize = 13, unityFontStyleAndWeight = FontStyle.Bold, marginBottom = 6 } - }; - _lootDetailRoot.Add(title); - - // 简要统计 - int entryCount = loot.Entries?.Length ?? 0; - _lootDetailRoot.Add(new Label($"条目数:{entryCount} 保底 LingZhu:{loot.GuaranteedLingZhuMin}–{loot.GuaranteedLingZhuMax}") - { - style = { color = new Color(0.7f, 0.7f, 0.7f), marginBottom = 4 } - }); - - _lootInspector = new InspectorElement(loot); - _lootDetailRoot.Add(_lootInspector); - - var btnRow = new VisualElement(); - btnRow.AddToClassList("action-buttons"); - btnRow.Add(new Button(() => EditorScaffoldUtils.PingAndSelect(loot)) { text = "在 Project 中定位" }); - btnRow.Add(new Button(() => Selection.activeObject = loot) { text = "在 Inspector 中打开" }); - _lootDetailRoot.Add(btnRow); - } - - // ── 新建资产 ────────────────────────────────────────────────────────── - - private void CreateNewEnemyStats() - { - var asset = EditorScaffoldUtils.CreateSOAsset( - "Assets/_Game/Data/Enemies", "ENM_New_Stats"); - if (asset != null) RefreshAll(); - } - - private void CreateNewLootTable() - { - var asset = EditorScaffoldUtils.CreateSOAsset( - "Assets/_Game/Data/Enemies", "ENM_New_Loot"); - if (asset != null) RefreshAll(); - } - } -} diff --git a/Assets/_Game/Scripts/Editor/Hub.meta b/Assets/_Game/Scripts/Editor/Hub.meta new file mode 100644 index 0000000..221c06a --- /dev/null +++ b/Assets/_Game/Scripts/Editor/Hub.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 54a83daf1b31e4c4e98beff7506eecb2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Scripts/Editor/Hub/DataHubWindow.cs b/Assets/_Game/Scripts/Editor/Hub/DataHubWindow.cs new file mode 100644 index 0000000..e3188a4 --- /dev/null +++ b/Assets/_Game/Scripts/Editor/Hub/DataHubWindow.cs @@ -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 +{ + /// + /// 数据管理总枢纽窗口(DataHub)。 + /// 布局:导航侧边栏(120px) | TwoPaneSplitView → 列表区(220px) + 详情区(flex)。 + /// 菜单:BaseGames / Data Hub (priority=50) + /// + 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(); + wnd.titleContent = new GUIContent("Data Hub", EditorGUIUtility.IconContent("d_ScriptableObject Icon").image); + wnd.minSize = new Vector2(MinWinWidth, MinWinHeight); + } + + // ── 状态 ───────────────────────────────────────────────────────────── + + private readonly List _modules = new(); + private readonly HashSet _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(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