From 5a0f1548eafe3da404bcf24653f349b3ff8e9328 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Tue, 26 May 2026 13:04:38 +0800 Subject: [PATCH] Refactor code structure for improved readability and maintainability --- Assets/_Game/Data/Enemies/E001.meta | 8 + Assets/_Game/Data/Enemies/E001/Abilities.meta | 8 + .../E001/Abilities/ABL_E001_Alert.asset | 27 + .../E001/Abilities/ABL_E001_Alert.asset.meta | 8 + .../E001/Abilities/ABL_E001_Chase.asset | 27 + .../E001/Abilities/ABL_E001_Chase.asset.meta | 8 + .../Enemies/E001/ENM_E001_AnimConfig.asset | 25 + .../E001/ENM_E001_AnimConfig.asset.meta | 8 + .../Data/Enemies/E001/ENM_E001_Stats.asset | 41 + .../Enemies/E001/ENM_E001_Stats.asset.meta | 8 + .../Scripts/Editor/BaseGames.Editor.asmdef | 1 + .../Editor/Character/CharacterWizardWindow.cs | 354 +++-- .../Editor/Scene/SceneObjectPlacerTool.cs | 764 ++++++++++ .../Enemies/AI/BD_BossPhaseTransition.cs | 8 +- .../Abilities/AnimatedCeilingDropAbility.cs | 88 ++ .../AnimatedCeilingDropAbility.cs.meta | 11 + .../Enemies/Abilities/AppearAbility.cs | 25 + .../Enemies/Abilities/AppearAbility.cs.meta | 11 + .../Abilities/CeilingHangStrikeAbility.cs | 69 + .../CeilingHangStrikeAbility.cs.meta | 11 + .../Enemies/Abilities/ContactChaseAbility.cs | 65 + .../Abilities/ContactChaseAbility.cs.meta | 11 + .../Enemies/Abilities/FacePlayerAbility.cs | 42 + .../Abilities/FacePlayerAbility.cs.meta | 11 + .../Abilities/MeleeVulnerabilityAbility.cs | 81 + .../MeleeVulnerabilityAbility.cs.meta | 11 + .../Enemies/Abilities/PlayClipAbility.cs | 24 + .../Enemies/Abilities/PlayClipAbility.cs.meta | 11 + .../Enemies/Abilities/RepeatSlamAbility.cs | 75 + .../Abilities/RepeatSlamAbility.cs.meta | 11 + Assets/_Game/Scripts/Enemies/Boss/BossBase.cs | 10 +- .../Scripts/Enemies/Boss/ChaoFengBoss.cs | 171 +++ .../Scripts/Enemies/Boss/ChaoFengBoss.cs.meta | 11 + .../Enemies/Boss/ChaoFengFloatController.cs | 46 + .../Boss/ChaoFengFloatController.cs.meta | 11 + .../Enemies/Boss/ChaoFengKnockdownCounter.cs | 78 + .../Boss/ChaoFengKnockdownCounter.cs.meta | 11 + .../Scripts/Enemies/Boss/ReturnProjectile.cs | 61 + .../Enemies/Boss/ReturnProjectile.cs.meta | 11 + Assets/_Game/Scripts/Enemies/E003_YouZhi.cs | 34 + .../_Game/Scripts/Enemies/E003_YouZhi.cs.meta | 11 + Assets/_Game/Scripts/Enemies/E004_ZhiMu.cs | 45 + .../_Game/Scripts/Enemies/E004_ZhiMu.cs.meta | 11 + Assets/_Game/Scripts/Enemies/E005_FeiZhi.cs | 66 + .../_Game/Scripts/Enemies/E005_FeiZhi.cs.meta | 11 + .../Scripts/Enemies/EnemyAnimationConfigSO.cs | 3 + Assets/_Game/Scripts/Enemies/EnemyBase.cs | 13 +- Assets/_Game/Scripts/Enemies/EnemyMovement.cs | 111 +- Assets/_Game/Scripts/World/Map/MapPanel.cs | 1 + .../_Game/Scripts/World/Map/MapRoomDataSO.cs | 4 +- Docs/Guides/02_Enemy_Boss_Setup_Guide.md | 1097 ++++++++++++++ Docs/Plan/小怪与Boss实现计划-01.md | 1335 +++++++++++++++++ zeling_v2.sln | 12 +- 53 files changed, 4853 insertions(+), 163 deletions(-) create mode 100644 Assets/_Game/Data/Enemies/E001.meta create mode 100644 Assets/_Game/Data/Enemies/E001/Abilities.meta create mode 100644 Assets/_Game/Data/Enemies/E001/Abilities/ABL_E001_Alert.asset create mode 100644 Assets/_Game/Data/Enemies/E001/Abilities/ABL_E001_Alert.asset.meta create mode 100644 Assets/_Game/Data/Enemies/E001/Abilities/ABL_E001_Chase.asset create mode 100644 Assets/_Game/Data/Enemies/E001/Abilities/ABL_E001_Chase.asset.meta create mode 100644 Assets/_Game/Data/Enemies/E001/ENM_E001_AnimConfig.asset create mode 100644 Assets/_Game/Data/Enemies/E001/ENM_E001_AnimConfig.asset.meta create mode 100644 Assets/_Game/Data/Enemies/E001/ENM_E001_Stats.asset create mode 100644 Assets/_Game/Data/Enemies/E001/ENM_E001_Stats.asset.meta create mode 100644 Assets/_Game/Scripts/Enemies/Abilities/AnimatedCeilingDropAbility.cs create mode 100644 Assets/_Game/Scripts/Enemies/Abilities/AnimatedCeilingDropAbility.cs.meta create mode 100644 Assets/_Game/Scripts/Enemies/Abilities/AppearAbility.cs create mode 100644 Assets/_Game/Scripts/Enemies/Abilities/AppearAbility.cs.meta create mode 100644 Assets/_Game/Scripts/Enemies/Abilities/CeilingHangStrikeAbility.cs create mode 100644 Assets/_Game/Scripts/Enemies/Abilities/CeilingHangStrikeAbility.cs.meta create mode 100644 Assets/_Game/Scripts/Enemies/Abilities/ContactChaseAbility.cs create mode 100644 Assets/_Game/Scripts/Enemies/Abilities/ContactChaseAbility.cs.meta create mode 100644 Assets/_Game/Scripts/Enemies/Abilities/FacePlayerAbility.cs create mode 100644 Assets/_Game/Scripts/Enemies/Abilities/FacePlayerAbility.cs.meta create mode 100644 Assets/_Game/Scripts/Enemies/Abilities/MeleeVulnerabilityAbility.cs create mode 100644 Assets/_Game/Scripts/Enemies/Abilities/MeleeVulnerabilityAbility.cs.meta create mode 100644 Assets/_Game/Scripts/Enemies/Abilities/PlayClipAbility.cs create mode 100644 Assets/_Game/Scripts/Enemies/Abilities/PlayClipAbility.cs.meta create mode 100644 Assets/_Game/Scripts/Enemies/Abilities/RepeatSlamAbility.cs create mode 100644 Assets/_Game/Scripts/Enemies/Abilities/RepeatSlamAbility.cs.meta create mode 100644 Assets/_Game/Scripts/Enemies/Boss/ChaoFengBoss.cs create mode 100644 Assets/_Game/Scripts/Enemies/Boss/ChaoFengBoss.cs.meta create mode 100644 Assets/_Game/Scripts/Enemies/Boss/ChaoFengFloatController.cs create mode 100644 Assets/_Game/Scripts/Enemies/Boss/ChaoFengFloatController.cs.meta create mode 100644 Assets/_Game/Scripts/Enemies/Boss/ChaoFengKnockdownCounter.cs create mode 100644 Assets/_Game/Scripts/Enemies/Boss/ChaoFengKnockdownCounter.cs.meta create mode 100644 Assets/_Game/Scripts/Enemies/Boss/ReturnProjectile.cs create mode 100644 Assets/_Game/Scripts/Enemies/Boss/ReturnProjectile.cs.meta create mode 100644 Assets/_Game/Scripts/Enemies/E003_YouZhi.cs create mode 100644 Assets/_Game/Scripts/Enemies/E003_YouZhi.cs.meta create mode 100644 Assets/_Game/Scripts/Enemies/E004_ZhiMu.cs create mode 100644 Assets/_Game/Scripts/Enemies/E004_ZhiMu.cs.meta create mode 100644 Assets/_Game/Scripts/Enemies/E005_FeiZhi.cs create mode 100644 Assets/_Game/Scripts/Enemies/E005_FeiZhi.cs.meta create mode 100644 Docs/Guides/02_Enemy_Boss_Setup_Guide.md create mode 100644 Docs/Plan/小怪与Boss实现计划-01.md diff --git a/Assets/_Game/Data/Enemies/E001.meta b/Assets/_Game/Data/Enemies/E001.meta new file mode 100644 index 0000000..8abad12 --- /dev/null +++ b/Assets/_Game/Data/Enemies/E001.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 16d3503453cb89640b705da44c5fbb53 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Data/Enemies/E001/Abilities.meta b/Assets/_Game/Data/Enemies/E001/Abilities.meta new file mode 100644 index 0000000..44d6d26 --- /dev/null +++ b/Assets/_Game/Data/Enemies/E001/Abilities.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7ae476df26ac83f4bb5466462f840c61 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Data/Enemies/E001/Abilities/ABL_E001_Alert.asset b/Assets/_Game/Data/Enemies/E001/Abilities/ABL_E001_Alert.asset new file mode 100644 index 0000000..993be92 --- /dev/null +++ b/Assets/_Game/Data/Enemies/E001/Abilities/ABL_E001_Alert.asset @@ -0,0 +1,27 @@ +%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: 9050afa76362dff469c64fbb48c9ff8d, type: 3} + m_Name: ABL_E001_Alert + m_EditorClassIdentifier: + abilityId: e001_alert + attackSequence: [] + cooldown: 1.5 + telegraphVfxKey: + telegraphDuration: 0 + interruptOnHurt: 1 + interruptOnStagger: 1 + preferredMinRange: 0 + preferredMaxRange: 5 + requiresLineOfSight: 1 + requiresGrounded: 1 + exclusionGroup: + priority: 0 diff --git a/Assets/_Game/Data/Enemies/E001/Abilities/ABL_E001_Alert.asset.meta b/Assets/_Game/Data/Enemies/E001/Abilities/ABL_E001_Alert.asset.meta new file mode 100644 index 0000000..fa1587f --- /dev/null +++ b/Assets/_Game/Data/Enemies/E001/Abilities/ABL_E001_Alert.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 157dc45e6b444c64ea1a80a5886a8b92 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Data/Enemies/E001/Abilities/ABL_E001_Chase.asset b/Assets/_Game/Data/Enemies/E001/Abilities/ABL_E001_Chase.asset new file mode 100644 index 0000000..a4c9838 --- /dev/null +++ b/Assets/_Game/Data/Enemies/E001/Abilities/ABL_E001_Chase.asset @@ -0,0 +1,27 @@ +%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: 9050afa76362dff469c64fbb48c9ff8d, type: 3} + m_Name: ABL_E001_Chase + m_EditorClassIdentifier: + abilityId: e001_chase + attackSequence: [] + cooldown: 1.5 + telegraphVfxKey: + telegraphDuration: 0 + interruptOnHurt: 1 + interruptOnStagger: 1 + preferredMinRange: 0 + preferredMaxRange: 5 + requiresLineOfSight: 1 + requiresGrounded: 1 + exclusionGroup: + priority: 0 diff --git a/Assets/_Game/Data/Enemies/E001/Abilities/ABL_E001_Chase.asset.meta b/Assets/_Game/Data/Enemies/E001/Abilities/ABL_E001_Chase.asset.meta new file mode 100644 index 0000000..c2f13fb --- /dev/null +++ b/Assets/_Game/Data/Enemies/E001/Abilities/ABL_E001_Chase.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0adeaa8a8508fbd40986dbb71cc85acd +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Data/Enemies/E001/ENM_E001_AnimConfig.asset b/Assets/_Game/Data/Enemies/E001/ENM_E001_AnimConfig.asset new file mode 100644 index 0000000..58f0546 --- /dev/null +++ b/Assets/_Game/Data/Enemies/E001/ENM_E001_AnimConfig.asset @@ -0,0 +1,25 @@ +%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: f7dd720bca19fcc49b22106fb65f7652, type: 3} + m_Name: ENM_E001_AnimConfig + m_EditorClassIdentifier: + Idle: {fileID: 0} + Walk: {fileID: 0} + Run: {fileID: 0} + Turn: {fileID: 0} + Attack: {fileID: 0} + Hurt: {fileID: 0} + Stagger: {fileID: 0} + KnockUp: {fileID: 0} + Dead: {fileID: 0} + Alert: {fileID: 0} + Investigate: {fileID: 0} diff --git a/Assets/_Game/Data/Enemies/E001/ENM_E001_AnimConfig.asset.meta b/Assets/_Game/Data/Enemies/E001/ENM_E001_AnimConfig.asset.meta new file mode 100644 index 0000000..0845fe5 --- /dev/null +++ b/Assets/_Game/Data/Enemies/E001/ENM_E001_AnimConfig.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 06936c5bc3358904cb269abdfa60ed14 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Data/Enemies/E001/ENM_E001_Stats.asset b/Assets/_Game/Data/Enemies/E001/ENM_E001_Stats.asset new file mode 100644 index 0000000..c0dba20 --- /dev/null +++ b/Assets/_Game/Data/Enemies/E001/ENM_E001_Stats.asset @@ -0,0 +1,41 @@ +%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_E001_Stats + m_EditorClassIdentifier: + MaxHP: 50 + Defense: 0 + WalkSpeed: 2 + RunSpeed: 4 + AttackDamage: 10 + AttackRange: 1.5 + AttackCooldown: 1 + DetectRange: 6 + MaxChaseDistance: 15 + LoseLinkTimeout: 2 + AlertDuration: 0.6 + InvestigateDuration: 3 + HomeRadius: 0.5 + KnockbackForce: 5 + HitStunDuration: 0.3 + HitTiers: + heavyHitThreshold: 0 + launchThreshold: 0 + launchUpForce: 0 + launchHorzForce: 0 + knockUpDuration: 0 + EyeOffset: {x: 0, y: 0.8} + LOSBlockingMask: + serializedVersion: 2 + m_Bits: 1 + DetectAngleDeg: 0 + AlertBroadcastRadius: 0 diff --git a/Assets/_Game/Data/Enemies/E001/ENM_E001_Stats.asset.meta b/Assets/_Game/Data/Enemies/E001/ENM_E001_Stats.asset.meta new file mode 100644 index 0000000..4799fea --- /dev/null +++ b/Assets/_Game/Data/Enemies/E001/ENM_E001_Stats.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 508afd17a0cf2fe47935c78097c3b093 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Scripts/Editor/BaseGames.Editor.asmdef b/Assets/_Game/Scripts/Editor/BaseGames.Editor.asmdef index 6fb2b19..876c852 100644 --- a/Assets/_Game/Scripts/Editor/BaseGames.Editor.asmdef +++ b/Assets/_Game/Scripts/Editor/BaseGames.Editor.asmdef @@ -15,6 +15,7 @@ "BaseGames.Player", "BaseGames.Player.States", "BaseGames.Enemies", + "BaseGames.Enemies.Navigation", "BaseGames.Camera", "BaseGames.World", "BaseGames.UI", diff --git a/Assets/_Game/Scripts/Editor/Character/CharacterWizardWindow.cs b/Assets/_Game/Scripts/Editor/Character/CharacterWizardWindow.cs index da964dc..7addf33 100644 --- a/Assets/_Game/Scripts/Editor/Character/CharacterWizardWindow.cs +++ b/Assets/_Game/Scripts/Editor/Character/CharacterWizardWindow.cs @@ -8,6 +8,7 @@ using UnityEngine.UIElements; using BaseGames.Boss; using BaseGames.Combat; using BaseGames.Enemies; +using BaseGames.Enemies.Abilities; using BaseGames.Equipment; using BaseGames.Input; using BaseGames.Parry; @@ -56,13 +57,24 @@ namespace BaseGames.Editor private List<(string label, bool exists)> _bossSOStatus = new(); private double _lastRefreshTime; - // 小怪类型选择 - private int _enemyTypeIndex = 0; - private static readonly string[] EnemyTypeLabels = { "普通(近战)", "远程", "飞行" }; + // 小怪类型选择 — 具体敌人类型 + private int _enemyTypeIndex = 0; + private static readonly (string id, string name)[] EnemyTypes = + { + ("E001", "草蛭"), + ("E002", "簧蛭"), + ("E003", "幼蛭"), + ("E004", "蛭母"), + ("E005", "肥蛭"), + ("E006", "讙"), + }; + + // 动态内容区(类型切换时重建) + private VisualElement _enemyContentArea; // Boss 命名字段 - private string _bossId = "NewBoss"; - private string _enemyId = "NewEnemy"; + private string _bossId = "NewBoss"; // kept for legacy SkillSequenceSO queries if any + private string _enemyId = "E001"; // kept for legacy status calls if any private string _playerId = "Player"; // SO 状态面板(按标签页缓存) @@ -202,59 +214,88 @@ namespace BaseGames.Editor _enemyStatusPanel = new VisualElement(); root.Add(_enemyStatusPanel); - root.Add(MakeSectionHeader("▶ 敌人类型选择")); + root.Add(MakeSectionHeader("▶ 敌人类型")); + root.Add(MakeHelpBox("选择要创建的具体敌人类型,对应 SO 工厂与场景放置按钮会自动切换。")); - var typeRow = new VisualElement { style = { flexDirection = FlexDirection.Row, marginBottom = 4 } }; - for (int i = 0; i < EnemyTypeLabels.Length; i++) + var typeRow = new VisualElement(); + typeRow.style.flexDirection = FlexDirection.Row; + typeRow.style.flexWrap = Wrap.Wrap; + typeRow.style.marginBottom = 4; + + for (int i = 0; i < EnemyTypes.Length; i++) { int captured = i; + var (id, name) = EnemyTypes[i]; var btn = new Button(() => { _enemyTypeIndex = captured; - // 高亮激活按钮(简单刷新所有同类按钮样式) - RefreshEnemyTypeButtons(root); + RefreshEnemyTypeButtons(typeRow); + RefreshEnemyTabContent(_enemyContentArea); + RefreshSOStatus(); }) - { text = EnemyTypeLabels[i] }; + { text = $"{id} {name}" }; btn.name = $"enemy-type-{i}"; btn.EnableInClassList("type-btn--active", i == _enemyTypeIndex); typeRow.Add(btn); } root.Add(typeRow); - root.Add(MakeSectionHeader("▶ SO 资产工厂")); - root.Add(MakeHelpBox("每个敌人建议独立命名,便于 Loot / BD 资产管理。")); - - var idRow = MakeLabeledTextField("敌人 ID", _enemyId, v => _enemyId = v); - root.Add(idRow); - - var factory = MakeActionGroup(); - factory.Add(MakeFactoryButton("EnemyStatsSO", () => CreateEnemyStat())); - factory.Add(MakeFactoryButton("LootTableSO", () => CreateLootTable())); - factory.Add(MakeFactoryButton("AttackPatternSO × 2", () => CreateEnemyAttackPatterns())); - factory.Add(MakeFactoryButton("DamageSourceSO", () => CreateEnemyDamageSource())); - root.Add(factory); - - root.Add(MakeSeparator()); - root.Add(MakeSectionHeader("▶ 场景搭建")); - root.Add(MakeHelpBox("根据选中的类型在场景中生成对应的敌人 GameObject。")); - - var sceneGroup = MakeActionGroup(); - sceneGroup.Add(MakeSceneButton("放置敌人到场景", PlaceSelectedEnemyType)); - root.Add(sceneGroup); + _enemyContentArea = new VisualElement(); + root.Add(_enemyContentArea); + RefreshEnemyTabContent(_enemyContentArea); root.Add(MakeSeparator()); root.Add(MakeSectionHeader("▶ 专项编辑器")); var jumpGroup = MakeActionGroup(); jumpGroup.Add(MakeJumpButton("Data Hub(敌人数据)", DataHubWindow.Open)); - jumpGroup.Add(MakeJumpButton("SO 全局校验", SOValidationRunner.ValidateMenu)); + jumpGroup.Add(MakeJumpButton("SO 全局校验", SOValidationRunner.ValidateMenu)); root.Add(jumpGroup); return root; } + private void RefreshEnemyTabContent(VisualElement container) + { + if (container == null) return; + container.Clear(); + + var (id, name) = EnemyTypes[_enemyTypeIndex]; + string dir = $"{DataRoot}/Enemies/{id}"; + string ablDir = $"{dir}/Abilities"; + + container.Add(MakeSectionHeader($"▶ SO 资产工厂({id} {name})")); + container.Add(MakeHelpBox($"统计 SO 路径:{dir}\n能力配置:{ablDir}")); + + var factory = MakeActionGroup(); + factory.Add(MakeFactoryButton($"ENM_{id}_Stats.asset", () => { CreateEnemyStatsSO(id); RefreshSOStatus(); })); + factory.Add(MakeFactoryButton($"ENM_{id}_AnimConfig.asset", () => { CreateEnemyAnimConfigSO(id); RefreshSOStatus(); })); + foreach (var (ablName, ablId) in GetEnemyAbilityDefs(id)) + { + string capturedName = ablName; + string capturedId = ablId; + factory.Add(MakeFactoryButton($"ABL_{id}_{capturedName}.asset", + () => { CreateEnemyAbilitySO(id, capturedName, capturedId); RefreshSOStatus(); })); + } + container.Add(factory); + + var createAllBtn = new Button(() => { CreateAllEnemySOs(id); RefreshSOStatus(); }) + { text = $"★ 一键创建全部 {id} SO" }; + createAllBtn.AddToClassList("wizard-create-all-btn"); + container.Add(createAllBtn); + + container.Add(MakeSeparator()); + container.Add(MakeSectionHeader("▶ 场景搭建")); + container.Add(MakeHelpBox("在当前活动场景中放置完整组件树并自动绑定已有 SO。")); + + var sceneGroup = MakeActionGroup(); + string sceneLabel = $"放置 {id} {name} 到场景"; + sceneGroup.Add(MakeSceneButton(sceneLabel, () => PlaceSpecificEnemy(id))); + container.Add(sceneGroup); + } + // ════════════════════════════════════════════════════════════════════════ - // Boss 标签页 + // Boss 标签页(嘲风专属) // ════════════════════════════════════════════════════════════════════════ private VisualElement BuildBossTab() @@ -265,27 +306,30 @@ namespace BaseGames.Editor _bossStatusPanel = new VisualElement(); root.Add(_bossStatusPanel); - root.Add(MakeSectionHeader("▶ SO 资产工厂")); - root.Add(MakeHelpBox("每个 Boss 独立目录:Assets/_Game/Data/Enemies//")); - - var idRow = MakeLabeledTextField("Boss ID", _bossId, v => _bossId = v); - root.Add(idRow); + root.Add(MakeSectionHeader("▶ SO 资产工厂(嘲风 ChaoFeng)")); + root.Add(MakeHelpBox("路径:Assets/_Game/Data/Enemies/ChaoFeng/\n能力配置:Assets/_Game/Data/Enemies/ChaoFeng/Abilities/")); var factory = MakeActionGroup(); - factory.Add(MakeFactoryButton("EnemyStatsSO(Boss)", () => CreateBossStat())); - factory.Add(MakeFactoryButton("LootTableSO(Boss)", () => CreateBossLoot())); - factory.Add(MakeFactoryButton("AttackPatternSO × 3(阶段)", () => CreateBossAttackPatterns())); - factory.Add(MakeFactoryButton("BossSkillSO × 3", () => CreateBossSkills())); - factory.Add(MakeFactoryButton("SkillSequenceSO(Phase 1)", () => CreateBossSkillSequence(1))); - factory.Add(MakeFactoryButton("SkillSequenceSO(Phase 2)", () => CreateBossSkillSequence(2))); - factory.Add(MakeFactoryButton("DamageSourceSO × 3", () => CreateBossDamageSources())); + factory.Add(MakeFactoryButton("ENM_ChaoFeng_Stats.asset", () => { CreateChaoFengStatsSO(); RefreshSOStatus(); })); + factory.Add(MakeFactoryButton("ENM_ChaoFeng_AnimConfig.asset",() => { CreateChaoFengAnimConfigSO(); RefreshSOStatus(); })); + factory.Add(MakeFactoryButton("ABL_ChaoFeng_Idle.asset", () => { CreateChaoFengSkillSO("Idle", "chaofeng_idle"); RefreshSOStatus(); })); + factory.Add(MakeFactoryButton("ABL_ChaoFeng_Slam.asset", () => { CreateChaoFengSkillSO("Slam", "chaofeng_slam"); RefreshSOStatus(); })); + factory.Add(MakeFactoryButton("ABL_ChaoFeng_Sweep.asset", () => { CreateChaoFengSkillSO("Sweep", "chaofeng_sweep"); RefreshSOStatus(); })); + factory.Add(MakeFactoryButton("ABL_ChaoFeng_WindBlade.asset", () => { CreateChaoFengSkillSO("WindBlade", "chaofeng_windblade"); RefreshSOStatus(); })); + factory.Add(MakeFactoryButton("ABL_ChaoFeng_Summon.asset", () => { CreateChaoFengSkillSO("Summon", "chaofeng_summon"); RefreshSOStatus(); })); root.Add(factory); + var createAllBtn = new Button(() => { CreateAllChaoFengSOs(); RefreshSOStatus(); }) + { text = "★ 一键创建全部 ChaoFeng SO" }; + createAllBtn.AddToClassList("wizard-create-all-btn"); + root.Add(createAllBtn); + root.Add(MakeSeparator()); root.Add(MakeSectionHeader("▶ 场景搭建")); + root.Add(MakeHelpBox("放置嘲风完整组件树(ChaoFengBoss + 浮空控制器 + 击倒计数 + Phase1 HitBox × 4 + 炮口 × 3)。")); var sceneGroup = MakeActionGroup(); - sceneGroup.Add(MakeSceneButton("放置 Boss 到场景", SceneObjectPlacerTool.PlaceBossEnemy)); + sceneGroup.Add(MakeSceneButton("放置嘲风到场景并绑定 SO", SceneObjectPlacerTool.PlaceChaoFeng)); root.Add(sceneGroup); root.Add(MakeSeparator()); @@ -294,7 +338,7 @@ namespace BaseGames.Editor var jumpGroup = MakeActionGroup(); jumpGroup.Add(MakeJumpButton("Boss 技能序列查看器", BossSkillSequenceWindow.OpenWindow)); jumpGroup.Add(MakeJumpButton("Data Hub(Boss技能)", DataHubWindow.Open)); - jumpGroup.Add(MakeJumpButton("SO 全局校验", SOValidationRunner.ValidateMenu)); + jumpGroup.Add(MakeJumpButton("SO 全局校验", SOValidationRunner.ValidateMenu)); root.Add(jumpGroup); return root; @@ -507,96 +551,113 @@ namespace BaseGames.Editor EditorUtility.DisplayDialog("指定完成", msg, "确定"); } - // ── SO 资产工厂:小怪 ──────────────────────────────────────────────── + // ── SO 资产工厂:小怪(按类型) ─────────────────────────────────────────── - private void CreateEnemyStat() + /// 返回指定敌人类型的 (abilityFileName, abilityId) 定义列表。 + private static (string ablName, string ablId)[] GetEnemyAbilityDefs(string enemyId) => enemyId switch { - string dir = $"{DataRoot}/Enemies/{_enemyId}"; - var asset = EditorScaffoldUtils.CreateSOAsset(dir, $"ENM_{_enemyId}_Stats"); - if (asset != null) RefreshSOStatus(); + "E001" => new[] { ("Alert", "e001_alert"), ("Chase", "e001_chase") }, + "E002" => new[] { ("Strike", "e002_strike") }, + "E003" => new[] { ("Fall", "e003_fall") }, + "E004" => new[] { ("Bite", "e004_bite"), ("Slam", "e004_slam"), ("Acid", "e004_acid"), + ("Charge", "e004_charge"), ("Chase", "e004_chase") }, + "E005" => new[] { ("Bite", "e005_bite"), ("Acid", "e005_acid") }, + "E006" => new[] { ("Leap", "e006_leap"), ("Chase", "e006_chase") }, + _ => System.Array.Empty<(string, string)>(), + }; + + private static void CreateEnemyStatsSO(string id) + { + string dir = $"Assets/_Game/Data/Enemies/{id}"; + EditorScaffoldUtils.CreateSOAsset(dir, $"ENM_{id}_Stats"); } - private void CreateLootTable() + private static void CreateEnemyAnimConfigSO(string id) { - string dir = $"{DataRoot}/Enemies/{_enemyId}"; - var asset = EditorScaffoldUtils.CreateSOAsset(dir, $"ENM_{_enemyId}_Loot"); - if (asset != null) RefreshSOStatus(); + string dir = $"Assets/_Game/Data/Enemies/{id}"; + EditorScaffoldUtils.CreateSOAsset(dir, $"ENM_{id}_AnimConfig"); } - private void CreateEnemyAttackPatterns() + private static void CreateEnemyAbilitySO(string enemyId, string ablName, string ablId) { - string dir = $"{DataRoot}/Enemies/{_enemyId}"; - foreach (var label in new[] { "Melee", "Ranged" }) - EditorScaffoldUtils.CreateSOAsset(dir, $"ENM_{_enemyId}_Pattern_{label}"); - RefreshSOStatus(); - } - - private void CreateEnemyDamageSource() - { - string dir = $"{DataRoot}/Enemies/{_enemyId}"; - EditorScaffoldUtils.CreateSOAsset(dir, $"ENM_{_enemyId}_DS"); - RefreshSOStatus(); - } - - // ── SO 资产工厂:Boss ───────────────────────────────────────────────── - - private void CreateBossStat() - { - string dir = $"{DataRoot}/Enemies/{_bossId}"; - EditorScaffoldUtils.CreateSOAsset(dir, $"ENM_{_bossId}_Stats"); - RefreshSOStatus(); - } - - private void CreateBossLoot() - { - string dir = $"{DataRoot}/Enemies/{_bossId}"; - EditorScaffoldUtils.CreateSOAsset(dir, $"ENM_{_bossId}_Loot"); - RefreshSOStatus(); - } - - private void CreateBossAttackPatterns() - { - string dir = $"{DataRoot}/Enemies/{_bossId}/Patterns"; - foreach (var label in new[] { "Phase1", "Phase2_A", "Phase2_B" }) - EditorScaffoldUtils.CreateSOAsset(dir, $"ENM_{_bossId}_Pattern_{label}"); - RefreshSOStatus(); - } - - private void CreateBossSkills() - { - string dir = $"{DataRoot}/Enemies/{_bossId}/Skills"; - foreach (var label in new[] { "Skill_Slam", "Skill_Sweep", "Skill_Summon" }) - EditorScaffoldUtils.CreateSOAsset(dir, $"SKL_{_bossId}_{label}"); - RefreshSOStatus(); - } - - private void CreateBossSkillSequence(int phase) - { - string dir = $"{DataRoot}/Enemies/{_bossId}/Skills"; - EditorScaffoldUtils.CreateSOAsset(dir, $"SKL_{_bossId}_Phase{phase}_Sequence"); - RefreshSOStatus(); - } - - private void CreateBossDamageSources() - { - string dir = $"{DataRoot}/Enemies/{_bossId}/DamageSources"; - foreach (var label in new[] { "Slam", "Sweep", "Projectile" }) - EditorScaffoldUtils.CreateSOAsset(dir, $"ENM_{_bossId}_DS_{label}"); - RefreshSOStatus(); - } - - // ── 场景搭建 ────────────────────────────────────────────────────────── - - private void PlaceSelectedEnemyType() - { - switch (_enemyTypeIndex) + string dir = $"Assets/_Game/Data/Enemies/{enemyId}/Abilities"; + string name = $"ABL_{enemyId}_{ablName}"; + var so = EditorScaffoldUtils.CreateSOAsset(dir, name); + // Set abilityId on newly-created SO (skip if already existed = null returned) + if (so != null) { - case 0: SceneObjectPlacerTool.PlaceEnemy(); break; - case 1: SceneObjectPlacerTool.PlaceEnemy(); break; // 复用,类型通过 SO 区分 - case 2: SceneObjectPlacerTool.PlaceEnemy(); break; + so.abilityId = ablId; + EditorUtility.SetDirty(so); + AssetDatabase.SaveAssets(); } } + private static void CreateAllEnemySOs(string id) + { + CreateEnemyStatsSO(id); + CreateEnemyAnimConfigSO(id); + foreach (var (ablName, ablId) in GetEnemyAbilityDefs(id)) + CreateEnemyAbilitySO(id, ablName, ablId); + AssetDatabase.SaveAssets(); + EditorUtility.DisplayDialog("创建完成", + $"全部 {id} SO 已创建(已存在的跳过)。\n请放置到场景后检查组件绑定。", "确定"); + } + + private static void PlaceSpecificEnemy(string id) + { + switch (id) + { + case "E001": SceneObjectPlacerTool.PlaceE001_CaoZhi(); break; + case "E002": SceneObjectPlacerTool.PlaceE002_HuangZhi(); break; + case "E003": SceneObjectPlacerTool.PlaceE003_YouZhi_Enemy(); break; + case "E004": SceneObjectPlacerTool.PlaceE004_ZhiMu_Enemy(); break; + case "E005": SceneObjectPlacerTool.PlaceE005_FeiZhi_Enemy(); break; + case "E006": SceneObjectPlacerTool.PlaceE006_Huan(); break; + default: SceneObjectPlacerTool.PlaceEnemy(); break; + } + } + + // ── SO 资产工厂:嘲风 Boss ───────────────────────────────────────────── + + private static void CreateChaoFengStatsSO() + { + string dir = "Assets/_Game/Data/Enemies/ChaoFeng"; + EditorScaffoldUtils.CreateSOAsset(dir, "ENM_ChaoFeng_Stats"); + } + + private static void CreateChaoFengAnimConfigSO() + { + string dir = "Assets/_Game/Data/Enemies/ChaoFeng"; + EditorScaffoldUtils.CreateSOAsset(dir, "ENM_ChaoFeng_AnimConfig"); + } + + private static void CreateChaoFengSkillSO(string skillName, string skillId) + { + string dir = "Assets/_Game/Data/Enemies/ChaoFeng/Abilities"; + string name = $"ABL_ChaoFeng_{skillName}"; + var so = EditorScaffoldUtils.CreateSOAsset(dir, name); + if (so != null) + { + EditorUtility.SetDirty(so); + AssetDatabase.SaveAssets(); + } + } + + private static void CreateAllChaoFengSOs() + { + CreateChaoFengStatsSO(); + CreateChaoFengAnimConfigSO(); + foreach (var (n, id) in new[] { ("Idle","chaofeng_idle"), ("Slam","chaofeng_slam"), + ("Sweep","chaofeng_sweep"), ("WindBlade","chaofeng_windblade"), + ("Summon","chaofeng_summon") }) + CreateChaoFengSkillSO(n, id); + AssetDatabase.SaveAssets(); + EditorUtility.DisplayDialog("创建完成", + "全部嘲风 SO 已创建(已存在的跳过)。\n放置到场景后检查 BossSkillExecutor._skills 绑定。", "确定"); + } + + // ── 场景搭建(已移至 RefreshEnemyTabContent 内的内联按钮) ───────────── + // ── SO 状态面板刷新 ─────────────────────────────────────────────────── private void RefreshSOStatus() @@ -636,16 +697,19 @@ namespace BaseGames.Editor if (_enemyStatusPanel == null) return; _enemyStatusPanel.Clear(); - string dir = $"{DataRoot}/Enemies/{_enemyId}"; - var checks = new (string label, UnityEngine.Object asset)[] - { - ("EnemyStatsSO", FindAtPath($"{dir}/ENM_{_enemyId}_Stats.asset")), - ("LootTableSO", FindAtPath($"{dir}/ENM_{_enemyId}_Loot.asset")), - ("AttackPatternSO×2", FindAtPath($"{dir}/ENM_{_enemyId}_Pattern_Melee.asset")), - ("DamageSourceSO", FindAtPath($"{dir}/ENM_{_enemyId}_DS.asset")), - }; + var (id, name) = EnemyTypes[_enemyTypeIndex]; + string dir = $"{DataRoot}/Enemies/{id}"; + string ablDir = $"{dir}/Abilities"; - _enemyStatusPanel.Add(MakeStatusGrid(checks)); + var items = new List<(string label, UnityEngine.Object asset)> + { + ($"ENM_{id}_Stats", FindAtPath($"{dir}/ENM_{id}_Stats.asset")), + ($"ENM_{id}_AnimConfig",FindAtPath($"{dir}/ENM_{id}_AnimConfig.asset")), + }; + foreach (var (ablName, _) in GetEnemyAbilityDefs(id)) + items.Add(($"ABL_{id}_{ablName}", FindAtPath($"{ablDir}/ABL_{id}_{ablName}.asset"))); + + _enemyStatusPanel.Add(MakeStatusGrid(items.ToArray())); } private void BuildBossStatus() @@ -653,15 +717,17 @@ namespace BaseGames.Editor if (_bossStatusPanel == null) return; _bossStatusPanel.Clear(); - string dir = $"{DataRoot}/Enemies/{_bossId}"; + const string dir = "Assets/_Game/Data/Enemies/ChaoFeng"; + const string ablDir = "Assets/_Game/Data/Enemies/ChaoFeng/Abilities"; var checks = new (string label, UnityEngine.Object asset)[] { - ("EnemyStatsSO(Boss)", FindAtPath($"{dir}/ENM_{_bossId}_Stats.asset")), - ("LootTableSO", FindAtPath($"{dir}/ENM_{_bossId}_Loot.asset")), - ("AttackPatternSO(Phase1)", FindAtPath($"{dir}/Patterns/ENM_{_bossId}_Pattern_Phase1.asset")), - ("BossSkillSO(≥1)", EditorScaffoldUtils.FindAllAssetsOfType() - .FirstOrDefault(s => s.name.StartsWith("SKL_" + _bossId, StringComparison.OrdinalIgnoreCase))), - ("SkillSequenceSO(Phase1)", FindAtPath($"{dir}/Skills/SKL_{_bossId}_Phase1_Sequence.asset")), + ("ENM_ChaoFeng_Stats", FindAtPath($"{dir}/ENM_ChaoFeng_Stats.asset")), + ("ENM_ChaoFeng_AnimConfig",FindAtPath($"{dir}/ENM_ChaoFeng_AnimConfig.asset")), + ("ABL_ChaoFeng_Idle", FindAtPath($"{ablDir}/ABL_ChaoFeng_Idle.asset")), + ("ABL_ChaoFeng_Slam", FindAtPath($"{ablDir}/ABL_ChaoFeng_Slam.asset")), + ("ABL_ChaoFeng_Sweep", FindAtPath($"{ablDir}/ABL_ChaoFeng_Sweep.asset")), + ("ABL_ChaoFeng_WindBlade", FindAtPath($"{ablDir}/ABL_ChaoFeng_WindBlade.asset")), + ("ABL_ChaoFeng_Summon", FindAtPath($"{ablDir}/ABL_ChaoFeng_Summon.asset")), }; _bossStatusPanel.Add(MakeStatusGrid(checks)); @@ -782,11 +848,11 @@ namespace BaseGames.Editor return row; } - private void RefreshEnemyTypeButtons(VisualElement tabRoot) + private void RefreshEnemyTypeButtons(VisualElement typeRow) { - for (int i = 0; i < EnemyTypeLabels.Length; i++) + for (int i = 0; i < EnemyTypes.Length; i++) { - var btn = tabRoot.Q