From a64449d21358cd5ffe972bcc5b23c136a337b01f Mon Sep 17 00:00:00 2001 From: Joywayer Date: Thu, 30 Jul 2026 15:04:26 +0800 Subject: [PATCH] =?UTF-8?q?refactor(editor):=20=E8=A7=92=E8=89=B2=E5=90=91?= =?UTF-8?q?=E5=AF=BC=20Boss=20=E9=A1=B5=E6=94=B9=E4=BA=A7=E5=87=BA=20Enemy?= =?UTF-8?q?AbilitySO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 该向导是 5 个空 BossSkillSO 资产的创建链路——不改则会持续产出旧轨资产。 招式表扩为带 soType / rangeRadius / cooldown / weight 的形式(与 GetEnemyAbilityDefs 对齐),阶段可用性移交 BossPhaseAbilityGate。 产出时强制 rangeRadius > 0,不把责任推给下游作者。 状态面板改查 EnemyAbilitySO,并新增 AI 图注册状态一行。 Co-Authored-By: Claude Opus 5 (1M context) --- .../Editor/Character/CharacterWizardWindow.cs | 101 ++++++++++++------ 1 file changed, 69 insertions(+), 32 deletions(-) diff --git a/Assets/_Game/Scripts/Editor/Character/CharacterWizardWindow.cs b/Assets/_Game/Scripts/Editor/Character/CharacterWizardWindow.cs index 4f269798..c31b63bb 100644 --- a/Assets/_Game/Scripts/Editor/Character/CharacterWizardWindow.cs +++ b/Assets/_Game/Scripts/Editor/Character/CharacterWizardWindow.cs @@ -5,7 +5,6 @@ using UnityEditor; using UnityEngine; using UnityEngine.UIElements; -using BaseGames.Boss; using BaseGames.Combat; using BaseGames.Enemies; using BaseGames.Enemies.Abilities; @@ -76,8 +75,7 @@ namespace BaseGames.Editor // 动态内容区(类型切换时重建) private VisualElement _enemyContentArea; - // Boss 命名字段 - 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"; @@ -330,11 +328,12 @@ namespace BaseGames.Editor var factory = MakeActionGroup(); factory.Add(MakeFactoryButton("ENM_ChaoFeng_Stats.asset", () => { CreateChaoFengStatsSO(); RefreshSOStatus(); })); factory.Add(MakeFactoryButton("ENM_ChaoFeng_AnimConfig.asset",() => { CreateChaoFengAnimConfigSO(); RefreshSOStatus(); })); - foreach (var (skName, skId, skPhases, skWeight) in ChaoFengSkillDefs) + foreach (var (abName, abId, soType, r, cd, w, ph) in ChaoFengAbilityDefs) { - string cName = skName; string cId = skId; int[] cPhases = skPhases; float cWeight = skWeight; + string cName = abName; string cId = abId; System.Type cType = soType; + float cR = r, cCd = cd, cW = w; int cPh = ph; factory.Add(MakeFactoryButton($"ABL_ChaoFeng_{cName}.asset", - () => { CreateChaoFengSkillSO(cName, cId, cPhases, cWeight); RefreshSOStatus(); })); + () => { CreateChaoFengAbilitySO(cName, cId, cType, cR, cCd, cW, cPh); RefreshSOStatus(); })); } root.Add(factory); @@ -345,7 +344,9 @@ namespace BaseGames.Editor root.Add(MakeSeparator()); root.Add(MakeSectionHeader("▶ 场景搭建")); - root.Add(MakeHelpBox("放置嘲风完整组件树(ChaoFengBoss + 浮空控制器 + 击倒计数 + Phase1 HitBox × 4 + 炮口 × 3)。")); + root.Add(MakeHelpBox("放置嘲风完整组件树(ChaoFengBoss + 浮空控制器 + 击倒计数 + " + + "EnemyAiBrain + BossPhaseAbilityGate + 6 个能力组件 + " + + "Phase1 HitBox × 4 + 炮口 × 3)。")); var bossSpriteField = new UnityEditor.UIElements.ObjectField("默认外观 Sprite(碰撞体尺寸依据,可留空)") { objectType = typeof(Sprite), allowSceneObjects = false, value = _defaultSprite }; @@ -362,9 +363,8 @@ namespace BaseGames.Editor root.Add(MakeSectionHeader("▶ 专项编辑器")); var jumpGroup = MakeActionGroup(); - jumpGroup.Add(MakeJumpButton("Boss 技能序列查看器", BossSkillSequenceWindow.OpenWindow)); jumpGroup.Add(MakeJumpButton("武器 HitBox 向导", WeaponHitBoxWizard.Open)); - jumpGroup.Add(MakeJumpButton("Data Hub(Boss技能)", DataHubWindow.Open)); + jumpGroup.Add(MakeJumpButton("Data Hub(敌人 / 能力)", DataHubWindow.Open)); jumpGroup.Add(MakeJumpButton("SO 全局校验", SOValidationRunner.ValidateMenu)); root.Add(jumpGroup); @@ -689,41 +689,72 @@ namespace BaseGames.Editor EditorScaffoldUtils.CreateSOAsset(dir, "ENM_ChaoFeng_AnimConfig"); } - private static void CreateChaoFengSkillSO(string skillName, string skillId, int[] phaseIndices, float weight) + private static void CreateChaoFengAbilitySO( + string abilityName, string abilityId, System.Type soType, + float rangeRadius, float cooldown, float weight, int phase) { - string dir = "Assets/_Game/Data/Enemies/ChaoFeng/Abilities"; - string name = $"ABL_ChaoFeng_{skillName}"; - var so = EditorScaffoldUtils.CreateSOAsset(dir, name); - if (so != null) + const string dir = "Assets/_Game/Data/Enemies/ChaoFeng/Abilities"; + string name = $"ABL_ChaoFeng_{abilityName}"; + + var so = EditorScaffoldUtils.CreateSOAsset(soType, dir, name) as EnemyAbilitySO; + if (so == null) return; // 已存在则不覆盖 + + so.abilityId = abilityId; + so.designNote = phase < 0 + ? "入场演出,不进选招池" + : $"阶段 {phase} 招式;阶段可用性由 BossPhaseAbilityGate 配置"; + so.cooldown = cooldown; + + if (phase < 0) { - so.skillId = skillId; - so.displayName = skillName; - so.availablePhaseIndices = phaseIndices; - so.weight = weight; - EditorUtility.SetDirty(so); - AssetDatabase.SaveAssets(); + so.category = AbilityCategory.None; + so.rangeRadius = 0f; + so.weight = 0f; } + else + { + so.category = AbilityCategory.Attack; + so.rangeRadius = rangeRadius; // 必须 > 0,否则选招器永远选不中 + so.weight = weight; + } + + EditorUtility.SetDirty(so); + AssetDatabase.SaveAssets(); } - /// 嘲风技能集(计划):Phase0 四技能加权随机 + Phase1 风石。 - private static readonly (string name, string id, int[] phases, float weight)[] ChaoFengSkillDefs = + /// + /// 嘲风能力集。阶段可用性不在 SO 上——由 BossPhaseAbilityGate 承载 + /// (见放置工具 PlaceChaoFeng),这里只记录供说明文本使用。 + /// rangeRadius 必须 > 0:category==Attack 且射程为 0 的招永远够不着玩家, + /// EnemyAbilitySO.Validate 与 EnemyBase.BuildAttackSelector 都会显式报错。 + /// + private static readonly (string name, string id, System.Type soType, + float rangeRadius, float cooldown, float weight, int phase)[] + ChaoFengAbilityDefs = { - ("Boomerang", "boomerang", new[] { 0 }, 1.0f), - ("FanCombo", "fan_combo", new[] { 0 }, 1.5f), - ("TornadoSmall", "tornado_small", new[] { 0 }, 1.2f), - ("TornadoLarge", "tornado_large", new[] { 0 }, 0.8f), - ("WindStone", "wind_stone", new[] { 1 }, 1.0f), + ("Intro", "chaofeng_intro", typeof(PlayClipAbilitySO), 0f, 0f, 0f, -1), + ("Boomerang", "boomerang", typeof(PlayClipAbilitySO), 9f, 4.0f, 1.0f, 0), + ("FanCombo", "fan_combo", typeof(EnemyAbilitySO), 2.5f, 2.5f, 1.5f, 0), + ("TornadoSmall", "tornado_small", typeof(PlayClipAbilitySO), 8f, 3.5f, 1.2f, 0), + ("TornadoLarge", "tornado_large", typeof(PlayClipAbilitySO), 12f, 6.0f, 0.8f, 0), + ("WindStone", "wind_stone", typeof(PlayClipAbilitySO), 12f, 3.0f, 1.0f, 1), }; private static void CreateAllChaoFengSOs() { CreateChaoFengStatsSO(); CreateChaoFengAnimConfigSO(); - foreach (var (n, id, phases, weight) in ChaoFengSkillDefs) - CreateChaoFengSkillSO(n, id, phases, weight); + foreach (var (n, id, t, r, cd, w, ph) in ChaoFengAbilityDefs) + CreateChaoFengAbilitySO(n, id, t, r, cd, w, ph); AssetDatabase.SaveAssets(); EditorUtility.DisplayDialog("创建完成", - "全部嘲风 SO 已创建(已存在的跳过)。\n放置到场景后检查 BossSkillExecutor._skills 绑定。", "确定"); + "全部嘲风 SO 已创建(已存在的跳过)。\n" + + "接下来:\n" + + "1. 用「放置嘲风到场景并绑定 SO」生成组件树;\n" + + "2. 检查 EnemyAiBrain._definitionId = ChaoFeng;\n" + + "3. 检查各能力组件的 _config 已绑定对应 ABL_ 资产;\n" + + "4. 在 BossPhaseAbilityGate 上确认阶段表(地面四招=阶段0,风石=阶段1)。", + "确定"); } // ── 场景搭建(已移至 RefreshEnemyTabContent 内的内联按钮) ───────────── @@ -794,10 +825,16 @@ namespace BaseGames.Editor ("ENM_ChaoFeng_Stats", FindAtPath($"{dir}/ENM_ChaoFeng_Stats.asset")), ("ENM_ChaoFeng_AnimConfig",FindAtPath($"{dir}/ENM_ChaoFeng_AnimConfig.asset")), }; - foreach (var (skName, _, _, _) in ChaoFengSkillDefs) - checks.Add(($"ABL_ChaoFeng_{skName}", FindAtPath($"{ablDir}/ABL_ChaoFeng_{skName}.asset"))); + foreach (var (abName, _, _, _, _, _, _) in ChaoFengAbilityDefs) + checks.Add(($"ABL_ChaoFeng_{abName}", + FindAtPath($"{ablDir}/ABL_ChaoFeng_{abName}.asset"))); + // AI 图是代码定义的(定制路径),没有资产可查——用注册表确认它被反射收集到了 + bool hasAiGraph = BaseGames.AI.AiDefinitionRegistry.Has("ChaoFeng"); _bossStatusPanel.Add(MakeStatusGrid(checks.ToArray())); + _bossStatusPanel.Add(new Label( + hasAiGraph ? "✔ AI 图 ChaoFengAi 已注册(定制路径 _definitionId = ChaoFeng)" + : "✘ AI 图 ChaoFengAi 未注册——检查 [AiDefinition(\"ChaoFeng\")] 特性")); } // ── 辅助:状态格 ─────────────────────────────────────────────────────