docs(enemy): Boss AI 双轨统一设计——决策层与执行层一并归并到小怪轨

竞品调研 + 代码现状核查后推翻 2026-07-27 §5 的「执行层按复杂度分层」:
Boss 侧那层富编排从未真正存在(0 个 AttackPatternSO/SkillSequenceSO 资产、
5 个 BossSkillSO 除 skillId/weight 外全空、执行器跑一遍纯空转、
UseBossSkillWeighted 零调用者、预制体上无 EnemyAiBrain)。

决策:
- 决策层单轨 —— 每 Boss 一个 AiScript 定制类,不建 Boss 骨架/配方
- 执行层单轨 —— Boss 招走 EnemyAbilitySO + EnemyAttackSO,编排权威=动画时间轴
- 阶段=换招池 —— BossPhaseAbilityGate 按阶段启停能力组件
- 选招策略是可换小构件 —— AttackSelectionMode 加防重复值,不抽策略接口
- 旧 Boss 轨退役 9 脚本 + 5 资产,判据「有更优替代才删」

同时记录 WeakPointSystem.GetDamageMultiplier 零调用者、
BossBase.HandleParrySuccess 订阅全局弹反频道导致的误伤 bug、
敌人侧动画事件只接了 7/20 三处历史遗漏,以及两条脚手架创建链路
(CharacterWizardWindow / SceneObjectPlacerTool.PlaceChaoFeng)必须同改。
This commit is contained in:
2026-07-30 10:29:49 +08:00
parent aaca138774
commit c176423103
@@ -0,0 +1,537 @@
# Boss AI 双轨统一:决策层与执行层一并归并到小怪轨
> 日期:2026-07-30  状态:设计已确认,待实施
> 背景:小怪走 `EnemyAiBrain`BrainGraph+ 组合式模块 + `EnemyAbilityBase`/`EnemyAbilitySO`
> Boss 走 `BossBase` + `BossSkillExecutor` + `BossSkillSO`。2026-07-27 的抽取决策记录把终态定为
> 「决策层统一 BrainGraph + 执行层按复杂度分层(Boss 保留 `BossSkillExecutor` 富编排)」。
> 本次经竞品调研与代码现状核查,**推翻其中"执行层分层"那一半**:Boss 侧那层富编排从未真正存在。
---
## 1. 结论摘要
| 决策 | 内容 |
|---|---|
| **决策层单轨** | Boss 走 BrainGraph。每个 Boss 一个 `AiScript` 定制类(已有的约 5% 定制路径),不建 Boss 骨架、不建 Boss 配方 |
| **执行层单轨** | Boss 招走 `EnemyAbilitySO` + `EnemyAttackSO` + `EnemyAbilityBase`。招式编排权威 = 动画时间轴(clip + 归一化时机 + 动画事件) |
| **Boss 专属不是"另一条轨"** | 而是旁挂 MonoBehaviour(浮空/击落/资源)+ 一个 `IBossControl` facet(阶段)|
| **阶段 = 换招池** | `BossPhaseAbilityGate` 按阶段启用/禁用能力组件。不在 SO 上加阶段字段 |
| **选招策略是可换的小构件** | `AttackSelectionMode` 加第三个值(加权随机 + 防重复)。不抽策略接口,enum 就是接缝 |
| **旧 Boss 轨整包退役** | 9 个脚本 + 5 个资产删除,判据是「有更优替代才删」 |
| **脚手架一并改** | `CharacterWizardWindow``SceneObjectPlacerTool.PlaceChaoFeng` 是旧轨资产的**创建链路**,不改则会持续产出旧轨对象(CLAUDE.md 第 2、6 条)|
核心原则:**决策归图,编排归动画,能不能用归招式自己,选哪个归选招器,演出归旁挂组件。**
---
## 2. 竞品证据
调研对象为同类型(横版平台动作 / Metroidvania)代表作及其续作的 Boss 设计,2026-07-30 完成。
以下六条是本设计的实证依据。
**2.1 技术轨本来就是单轨。** 代表作中所有敌人与 Boss 一律使用同一套可视化状态机(每个 Boss 一张
名为 `Control` 的图),与小怪用的是同一技术栈。差别只在状态数量,不在架构。
→ 双轨没有技术必要性。
**2.2 Boss 决策层极简,复杂度全在单招内部。** 招数量:某作最终 Boss 9 招,另几个知名 Boss 分别
6 招、4 招、3 招。决策形状统一是「选招 hub 态 → 加权随机挑一招(带防重复与位置门)→ 打完回 hub」。
→ Boss 决策层不需要比现有 `ApproachAttackEngagement` 复杂。
**2.3 阶段 = 换招池 + 一段过渡演出。** 某作最终 Boss 四阶段:P1 三招;血量阈值 1 **加**两招并提速;
阈值 2 **减掉**两招换成新招;P4 只剩两招。过渡是固定的「站定 → 仰头咆哮 → 硬直玩家」。
→ 阶段的本质是招池过滤器 + 一段无敌演出,现有 `BeginPhaseTransition` 已是正确形态。
**2.4 续作相对前作唯一的 Boss AI 改动,是选招谓词而非架构。** 开发者访谈明确说明:前作 Boss 是
反应式的——按玩家站位选招;续作去掉了这一点,改为主动式——按自身计划推进,不看玩家在哪。
**这条最有价值**:它说明「选招策略」应当是一个可替换的小构件,而不是一整套 AI 技术栈。
现有 `AttackSelectionMode` 只有 Priority / WeightedRandom 两维,缺的正是「读不读玩家位置」这一维。
**2.5 Boss 没有未发现层。** 竞品 Boss 不巡逻、不伪装、不警觉,靠竞技场进入 / 锁门 / 触发器开战。
→ 小怪骨架的第一层对 Boss 完全无用;Boss 要的是入场演出层。这是不复用 `PerceptionSkeleton` 的理由。
**2.6 committed 起手是共识。** 预警 → 起手 → 打完,不中途收招(某作的空中俯冲锁定的是**起手瞬间**的
玩家位置,而非实时追踪)。
→ 与 2026-07-29 组合式模块设计 §3.6 已定的「攻击起手后打完」一致,两轨在这点本来就同构。
---
## 3. 代码现状核查
### 3.1 Boss 现在没有决策层
`UseBossSkillWeighted` **零调用者**(原行为树任务已移除)。`ENM_ChaoFeng.prefab` 上**没有**
`EnemyAiBrain`(无 `_recipe` / `_definitionId` 序列化字段)。**嘲风目前打不起来。**
这条推翻了 2026-07-27 决策记录 §5 的执行建议「不要拿已能跑的 ChaoFeng 开刀验证」——
它不能跑,因此没有回归风险,正是首个验证对象。
### 3.2 Boss 的"富执行层"是空架构
- 全项目 **0 个** `AttackPatternSO` 资产、**0 个** `SkillSequenceSO` 资产。
- 5 个 `ABL_ChaoFeng_*.asset`(实为 `BossSkillSO`)除 `skillId``weight` 外全空:
`attackPatterns: []``sequenceOnMiss: {fileID: 0}``skillAnimation._Clip: {fileID: 0}`
`vulnerabilityWindows: []``counterResponses: []``cooldown: 0`
- 因此 `BossSkillExecutor.ExecuteSkillCoroutine` 跑一遍是纯空转:无动画、无序列、无判定。
### 3.3 零运行时消费者清单
| 成员 | 状态 |
|---|---|
| `BossSkillSO.attackPatterns` | 仅被两个编辑器窗口读取,运行时零读取 |
| `interactionTags` / `buildsRage` / `poiseWindow`BossSkillSO 上那份)| 零读取 |
| `PlayerCounterResponse.soulPowerGrant` / `spiritPowerGrant` | 零读取(`ExecuteCounterEffect` 未读)|
| `ArenaEventType/Params/Trigger/Data``IArenaInteractable` | 零消费者 |
| `TelegraphSystem` | 零消费者(调用方 `BD_TelegraphAttack` 已不存在)|
| `WeakPointSystem.GetDamageMultiplier()` | **零调用者**——注释称"由 BossStats 调用",项目无 `BossStats` 类型。弱点伤害倍率从未生效 |
| `BossSkillEventChannelSO` | `BossSkillExecutor` 在发,**全项目零订阅方** |
| `EnemyBase.ConsumeParryEvent` | 零调用者(`BD_OnParried` 已不存在)|
| `EnemyBase.ReceiveParry` | 零调用者(形态正确但未接线,见 §6.2)|
### 3.4 嘲风预制体实况
挂载的 Boss 组件只有三个:`BossSkillExecutor`5 个空技能)、`ChaoFengFloatController`
`ChaoFengKnockdownCounter`。**未挂** `WeakPointSystem``BossResource``TelegraphSystem``EnemyAiBrain`
而嘲风实际的招式编排走的是**动画事件**(clip 上的 `SpawnProjectile` 事件 → `ChaoFengBoss.SpawnProjectile(payload)`),
即小怪轨 `EnemyAttackSO` 的做法。**唯一的 Boss 用脚投票选了另一条路。**
### 3.5 现存弹反反制路径有 bug
`BossBase.HandleParrySuccess` 订阅的是 `ParryInfoEventChannelSO` 这个**全局**弹反成功频道,
无来源过滤。玩家弹反一只小怪也会让场上的 Boss 进入硬直。这是应当删除而非保留的另一个理由。
---
## 4. 架构:关注点归属
| 关注点 | 归属 | 依据 |
|---|---|---|
| 选状态 / 何时出手 / 何时换阶段 | AI 图(每 Boss 一个 `AiScript` | 这些是决策 |
| 一招怎么打(clip + 归一化时机 + HitBox + 弹体) | `EnemyAbilityBase` 子类 + `EnemyAbilitySO` | 与小怪同构 |
| 这招现在能不能用(阶段门) | 能力组件的启用状态(`BossPhaseAbilityGate`)| 阶段=换招池在数据上显式(§2.3)|
| 选哪一招(权重 / 防重复) | `EnemyAttackSelector` | 已是唯一选招器 |
| 阶段过渡无敌演出 | `BossBase.BeginPhaseTransition` | 已实现且正确,保留 |
| 死亡 | `Die` / `PerformDeath`(物理层)| 与小怪同一结论;Boss 已在走这条 |
| 浮空 / 击落计数 / 资源 | 旁挂 MonoBehaviourAI 图不知道其内部 | 单 Boss 或专项机制,不上升到骨架 |
### 4.1 为什么不建 Boss 骨架
竞品每个 Boss 都是一张手写图(§2.1)。Boss 数量在十几个量级,配方化省下的编译与复制成本极小,
而完全自由的价值很高。更关键的是:Boss 之间的差异远大于小怪(浮空 / 击落计数 / 多阶段竞技场 /
守卫箱),骨架会迅速退化成开关面板——正是 2026-07-29 刚从 `PerceptionStateMachine.Config` 上修掉的病。
复用 `PerceptionSkeleton` 也被否决:它专为感知升降级而存在,会给 Boss 挂上用不到的 `Alert` 态、
`InVision+HasAlert` 死边和 `leftVision → Rest` 脱战边(§2.5:Boss 不脱战)。
**统一的是运行时(`BrainGraph` + `AiRuntime` + `IsControllable` 让位门)与建态原语(`AiStateFragments`),
不是图的形状。**
### 4.2 共享图片段(不是可插拔模块)
Boss 图手写,但重复形状抽成静态片段——`AiStateFragments` 那个级别,不需要 `[SerializeReference]`
下拉、不需要 SO
```csharp
public static class BossFragments
{
/// 阶段过渡态:OnEnter 调 BeginPhaseTransition,过渡结束后转出。
public static BrainBuilder.StateBuilder PhaseTransition(
BrainBuilder b, string state, int targetPhase, float invincibleDuration);
/// 逼近 ↔ 选招攻击。转发到 ApproachAttackEngagement 提取出的静态建态方法,
/// 使交战模块与 Boss 图共用同一份实现(态名可指定,Boss 图可有多组)。
public static BrainBuilder.StateBuilder ApproachAttack(
BrainBuilder b, string approach, string attack, string rest);
/// 移动到第 index 个竞技场锚点(BossArenaAnchors),到位后转出。
public static BrainBuilder.StateBuilder MoveToAnchor(
BrainBuilder b, string state, int index);
}
```
`ApproachAttackEngagement.Build` 的建态逻辑提升为 `internal static`,模块与 `BossFragments` 各自转发。
入场直接用已有的 `AiStateFragments.Ability(b, Intro, introAbilityId)`
### 4.3 嘲风 AI 图形状(范例)
```csharp
[AiDefinition("ChaoFeng")]
public sealed class ChaoFengAi : AiScript
{
protected override void Build(BrainBuilder b)
{
b.Entry(Wait);
// 竞技场触发前静置(态名常量声明略)
AiStateFragments.Locomotion(b, Wait, LocomotionMode.Idle)
.To(Intro).OnEvent(AiSignal.Engaged);
// 入场演出
AiStateFragments.Ability(b, Intro, "chaofeng_intro")
.To(Ground).When(x => !x.Combat.IsAbilityRunning("chaofeng_intro"), "introDone");
// 阶段 0:地面,逼近 ↔ 选招(四招由阶段门放行)
BossFragments.ApproachAttack(b, Ground, GroundAttack, rest: Ground)
.To(PhaseTx).When(x => x.Vitals.HpBelow(0.5f), "hp<50%");
// 阶段过渡:无敌 + 浮空演出(ChaoFengBoss.OnBeginPhaseTransition 已实现)
BossFragments.PhaseTransition(b, PhaseTx, targetPhase: 1, invincibleDuration: 2f)
.To(Air).When(x => !x.Boss.IsPhaseTransitioning, "txDone");
// 阶段 1:空中,悬停 ↔ 风石(地面四招已被阶段门禁用)
AiStateFragments.Locomotion(b, Air, LocomotionMode.Face)
.To(AirAttack).When(x => x.Combat.HasEligibleAttack(), "attackInRange");
b.DeclareState(AirAttack)
.OnEnter(x => x.Combat.UseBestAttack())
.To(Air).When(x => !x.Combat.IsAbilityRunning(), "attackDone");
}
}
```
死亡不在图上:`ChaoFengBoss.Die()` 的击败演出协程照旧,`NotifyDecisionStop()`(原 `StopBehaviorTree`
`AiSignal.Died` 让全局死亡边把决策停住。与 2026-07-29 §3 修订的结论一致——**死亡不是一个决策**。
---
## 5. 新增的六件
| # | 内容 | 规模 |
|---|---|---|
| 1 | `IBossControl` facet + `IAiContext.Boss` | 小接口 |
| 2 | `BossPhaseAbilityGate` 组件 | 小组件 |
| 3 | `AttackSelectionMode.WeightedRandomAntiRepeat` + 选招器防重复 | 改两个文件 |
| 4 | `EnemyAnimationEvents` 补 4 个 case | 各 23 行 |
| 5 | `EnemyBase.SetInvincible(float)` | 约 6 行 |
| 6 | `BossArenaAnchors` 组件 + 「移动到第 N 锚点」图片段 | 小组件 |
### 5.1 `IBossControl`
```csharp
namespace BaseGames.AI
{
/// Boss 专属决策面。小怪图访问 IAiContext.Boss 会抛异常(显式报错,不静默兜底)。
public interface IBossControl
{
int CurrentPhase { get; }
bool IsPhaseTransitioning { get; }
bool ResourceFull { get; } // BossResource 满值;无资源组件时恒 false
void BeginPhaseTransition(int targetPhase, float invincibleDuration);
}
}
```
`EnemyBrainContext.Boss => _boss ?? throw new InvalidOperationException(...)`
按 CLAUDE.md 第 6 条:小怪图误用它必须崩,不能返回一个空对象把问题盖住。
`BossBase` 实现 `IBossControl``EnemyBrainContext` 在构造时 `_boss = enemy as BossBase`
### 5.2 `BossPhaseAbilityGate`
```csharp
[Serializable] struct PhaseEntry { public EnemyAbilityBase ability; public int[] phases; }
```
`BossBase.EnterPhase(n)` 调用本组件按表启停能力组件;`OnSpawn` 重置到阶段 0 的启用集
(对象池复用)。同时 `EnemyAbilityBase.CanUse``&& enabled`——现状下禁用的组件
`CanUse` 仍为真,选招器会选中一个 `StartCoroutine` 必然失败的招。
**为什么阶段门不放 SO**:阶段可用性是跨所有 Boss 招的共性,而具体招式已经在用
`EnemyAbilitySO` 的类型化子类(`RushAbilitySO` / `RepeatSlamAbilitySO` …)。若做成
`BossAbilitySO : EnemyAbilitySO`,Boss 的近战招就要同时继承 Boss 与 Melee 两个子类——
C# 单继承直接堵死。
### 5.3 `WeightedRandomAntiRepeat`
选招器持有 `_lastPicked`,对上一招施加权重系数(默认 0.3,放 `EnemyStatsSO`)。
`BossBase.UseBossSkillWeighted` 的同名逻辑就此并入选招器后删除。
**为什么不抽 `IAttackSelectionStrategy`**:按 2026-07-27 §Layer B/C 的判据——三个 enum 值互不耦合、
`Select` 是一个三目、每模式一个 private 方法,不存在"每加一种就长一根 `if` 分支"的结构。
等真要做 §2.4 那种主动计划式选招时再抽,那时才有第 4、5 个消费者。**enum 就是留给它的接缝。**
### 5.4 动画事件补接(四个 case)
`AnimationEventType` 有 20 个值,`EnemyAnimationEvents.HandleEvent` 只接了 7 个
`EnableHitBox` / `DisableHitBox` / `SpawnProjectile` / `RoarStart` / `RoarEnd` /
`PhaseTwoStart` / `AnimationComplete`)。玩家侧 `PlayerAnimationEvents` 接得更全。
敌人侧漏掉的这四个恰好都是 Boss 复刻要用的:
| 补接 | 复刻的竞品能力 |
|---|---|
| `PlaySFX` / `TriggerFeedback` | **音效预警**——竞品有 Boss 用两句语音区分物理招与远程招,预警的是招式**类别**而非具体招式,让玩家缩小可能性但不能确定。这是最经济的 telegraph 手法,现在写不出来 |
| `EnableIFrame` / `DisableIFrame` | 招式中途无敌:突进穿过玩家时不吃伤害、传送消失期间无敌 |
配套 §5 第 5 项:`EnemyBase.IsInvincible` 是只读计算属性(`Dead || _deathSequenceActive`),
没有带时长的无敌源,需新增 `SetInvincible(float)` 并纳入 `IsInvincible`
### 5.5 `BossArenaAnchors`
竞技场锚点走位(站在悬空平台上出招、传送到房间固定角落)是竞品定点型 Boss 的核心。
`IEnemyLocomotion.MoveTo(Vector2)` 能到点,但锚点集合无处安放。
一个持有 `Transform[]` 的组件 + 一个「移动到第 N 个锚点」的图片段即可。
---
## 6. 退役清单
判据:**有更优替代才删。** 按替代的成熟度分四类。
### 6.1 A 类:有现成更优替代 → 直接删
| 删除 | 更优替代(已存在) |
|---|---|
| `BossSkillSO` | `EnemyAbilitySO` + `EnemyAttackSO`(归一化时机、槽位 HitBox、`ProjectileConfigSO`|
| `AttackPatternSO` | `EnemyAttackSO``AoERadius/AoEOffset` 的更优形态是 HitBox 碰撞体几何;`KnockbackAngle``DamageSourceSO.KnockbackForce` + 命中方向 |
| `SkillSequenceSO` | `EnemyAbilitySO.attackSequence`(顺序连段)+ `RepeatSlamAbility`(范围内重复)|
| `BossSkillExecutor` | `EnemyAbilityBase` 子类 + `EnemyAttackSelector` |
| `InteractionTag` | `DamageFlags`——`CanBeParried``HurtBox.cs:77` 已生效,"不可弹反招"今天就能配 |
| `BossSkillSO.poiseWindow` | `EnemyAttackSO.hasPoiseWindow/poiseLevel/poiseStartT/poiseEndT` |
| `ArenaEventType/Params/Trigger/Data``IArenaInteractable` | `EnemySpawnerOnEvent`(动画事件生成 hazard)。`BossSkillEventChannelSO` 零订阅方,删执行器不丢现有功能;若将来场景机关要按招响应,接缝是在能力层发同一频道 |
| `BossSkillCategory` / `BossSkillType` | `AbilityCategory` |
| `TelegraphSystem` + `EnemyAbilitySO.telegraphVfxKey/telegraphDuration` + `EnemyAbilityBase.TelegraphRoutine` | clip 姿态 + 动画事件(§5.4 补接后)。三者是同一件事的三条平行路,且"生成 VFX 然后干等 N 秒"会在动画前插一段无动画静止 |
| `BossBase.UseBossSkill` / `UseBossSkillWeighted` / `LastUsedSkillId` / `IsSkillAvailableInPhase` / `CheckResourceCost` / 三个缓冲区 | `EnemyAttackSelector` + `WeightedRandomAntiRepeat` + `BossPhaseAbilityGate` |
| `BossResourceCost` / `autoTriggerOnFull` / `fullTriggerSkill` / `resetValueAfterTrigger` | AI 图条件边。"资源满自动放招"由 `BossResource` 直接调 `UseBossSkill` 是**绕过决策层**,违反"AI 只决策、能力负责实现";正确形态是图上一条 `When(x => x.Boss.ResourceFull)` 边进专属招态 |
| `EnemyBase.ConsumeParryEvent` + `_wasParried` / `_parryTimestamp` / `ParryEventTTL` | 零调用者;`ForceState(Stagger)` + `IsControllable` 让位门是更优表达 |
| `Editor/Modules/BossSkillModule.cs``Editor/Enemies/BossSkillSequenceWindow.cs` | 依附 `BossSkillSO`,类型删除后必然失效。`BossSkillModule` 实现 `IDataModule` 且无显式注册点(反射发现),删文件即可 |
| 5 个 `ABL_ChaoFeng_*.asset` | 空 `BossSkillSO`。注:`ABL_` 前缀本身**合规**`AssetFolderSpec.md` 第 298 行),删除原因是内容为空 |
### 6.2 B 类:替代需要顺手造,造完更优 → 删 + 造
**这三项现成的更优替代并不存在,是本设计建议顺手造一个。**
**`WeakPointSystem` → 倍率下沉到 `HurtBox`。**
`HurtBox` 没有伤害倍率字段,所以"打特定部位加伤"目前无载体。但 `WeakPointSystem` 的实现是坏的:
倍率写进自己的私有字段,`GetDamageMultiplier()` 零调用者,注释里的 `BossStats` 类型不存在。
更优形态是倍率归 `HurtBox` 自己(一个字段 + 伤害计算处一次乘法,约 3 行),弱点的定时开关由能力控制——
`MeleeVulnerabilityAbility` 已经证明「攻击后开放脆弱窗口」这个形态可行。
**`PlayerCounterResponse` / `CounterType` / `BossBase.ApplyCounterResponse` / `ExecuteCounterEffect` /
`CounterStaggerCoroutine` / `_onParrySuccess``EnemyBase.ReceiveParry`。**
`ReceiveParry(staggerDuration)` 已存在且形态正确(强制 Stagger + 打断全部能力 + 定时恢复,小怪 Boss
通用,不需要按 skillId 查表),但零调用者。删除现路径同时修掉 §3.5 的全局误伤 bug。
**代价须明示**`DamageInfo` 里**没有攻击者引用**(只有 `SourceProjectile`),所以"谁被弹反谁硬直"
这根线要在 Combat 层补——要么 `DamageInfo` 加 attacker,要么弹反成功时回调发起攻击的 `HitBox`
**列为独立后续任务,本次不做。** 期间 Boss 被弹反不会硬直,但优于现在的误伤行为。
**`BossSkillSO.designNote``EnemyAbilitySO.designNote`。**
无替代(`EnemyAttackSO.attackName` 只是调试名)。目标体量下 150+ 能力需要设计备注,加一个
`[TextArea]` 字段。
### 6.3 C 类:无替代 → 保留并改造
| 保留 | 改造 |
|---|---|
| `BossResource` + `BossResourceConfigSO` | 项目无第二套敌人资源条,"蓄能/怒气"是真机制。删掉自动触发三字段(见 A 类),保留数值维护 + `NormalizedValue`,新增 `IsFull`,触发权交回 AI 图(经 `IBossControl.ResourceFull`|
| `EnemyBase.ReceiveParry` | 形态正确,等 Combat 层接线(§6.2|
| `EnemyBase.StopBehaviorTree` | 改名 `NotifyDecisionStop()`(行为树债清理),同步改 `EnemyDeathSequence``ChaoFengBoss` 两处调用点 |
| `ChaoFengFloatController` / `ChaoFengKnockdownCounter` | 单 Boss 专属机制,形态正确,不动 |
| `BossBase.BeginPhaseTransition` / `EnterPhase` / `IsPhaseTransitioning` / `IsInvincible` 覆写 | 已实现且正确,仅新增 `IBossControl` 实现 |
### 6.4 D 类:会丢一个编辑器视图
删除 `BossSkillModule` / `BossSkillSequenceWindow` 后,**能力资产没有任何编辑器总览视图**
`EnemyModule` 不涉及能力资产,已核)。列为后续待补:`EnemyAbilityModule`(能力总览 +
射程 / 权重 / 冷却 / 阶段一览)。不阻塞本次——`AiDefinitionValidator` 那条校验线才是防
450+ 引用出错的关键,优先级更高。
### 6.5 明确不做的四件
| 不做 | 理由 |
|---|---|
| Boss 骨架 / Boss 配方 SO | §4.1 |
| `IAttackSelectionStrategy` 策略接口 | §5.3YAGNI,留 enum 接缝 |
| 主动计划式选招 | §2.4 是方向不是需求。等真要"不看玩家站位、按自身轮转"时加第 4 个 enum 值 |
| 多体 Boss 出招令牌 | 见 §7.2。等真设计双体 Boss 时再加 |
---
## 7. 表达力核对:能否复刻竞品 Boss
### 7.1 能直接表达(现有构件已覆盖)
| 竞品行为 | 项目对应构件 |
|---|---|
| 阶段加招 / **减招** | `BossPhaseAbilityGate` |
| 阶段过渡定格咆哮 + 硬直玩家 | `BeginPhaseTransition`(无敌)+ `RoarStart/RoarEnd` 动画事件(已接线)|
| 加权随机选招 + 防重复 | `EnemyAttackSelector` + `WeightedRandomAntiRepeat` |
| 按玩家距离 / 站位选招(反应式)| 招式自管圆形射程 `InAttackRange()` + `preferredMinRange/MaxRange` |
| 起手锁定玩家位置的落点攻击 | 嘲风 `tornado_large` 已实现(生成瞬间取玩家位置)|
| 不追踪的固定弧线弹幕(空间封锁)| `projectileCount` + `spreadAngleDeg` + `projectileFireT` |
| 突进穿越场地 | `RushAbility` / `ChargeAbility` / `MultiDashAbility` |
| 跳跃砸地 | `LeapAttackAbility` |
| 传送到玩家侧后突袭 | `BlinkStrikeAbility` |
| 天花板悬挂 / 掉落突袭 | `CeilingDropAbility` / `CeilingHangStrikeAbility` |
| 玩家还在范围内就反复砸 | `RepeatSlamAbility` |
| 召唤小怪 | `EnemySpawnerOnEvent`(动画事件驱动,零代码)|
| 招式霸体不被打断 | `interruptOnHurt=false` + `EnemyPoiseComponent` |
| 起手后打完不收招 | committed 攻击(`Attack` 态不挂脱战边)|
| **不可弹反招(必须闪避)** | 该招 `DamageSourceSO.Flags` 去掉 `CanBeParried``HurtBox.cs:77` 已在判定 |
| 入场演出 | `AppearAbility` + `AiSignal.Engaged`(新增该枚举值)|
| 击落 / 破防计数 | `ChaoFengKnockdownCounter` |
| 浮空 / 飞行阶段 | `FlyingNavigator` + `ChaoFengFloatController` |
最后那条顺带证实:`InteractionTag.Parryable` / `Unblockable` 与 Combat 层的 `DamageFlags`
**同一件事两处表达**,删掉不丢功能,反而消除打架隐患。
### 7.2 需要新组件的一处:多体 Boss 出招令牌
竞品有 Boss 在某阶段以两个实体同时行动且**交替出招**(一只出招时另一只等待,不会同时放同一招)。
项目 AI 图是每敌人独立,`Blackboard` 是 per-context`EnemyBrainContext``new Blackboard()`),
无跨敌人通道。
**这不是架构缺口**:形态是一个 `BossEncounterCoordinator` 持有出招令牌,参战 Boss 的选招多一个
「我持有令牌吗」的门。加一个组件 + `CanUse` 上一个门,不推翻本设计任何决策。
**现在不做**,等真设计双体 Boss 时再加。
### 7.3 两处看着像缺口、其实不是
**命中才追加连段。** `BossSkillSO.sequenceOnHit` 是整个 Boss SO 里唯一有真实语义却要被删的字段。
但竞品罕见(连段是固定的,不看是否命中;更常见的是"玩家还在范围内就重复",`RepeatSlamAbility` 已覆盖)。
真要的话形态是一个能力子类——`HitBox.OnHitConfirmed` 已存在,能力自己判断再决定跑不跑第二段。
不需要一整套序列 SO。
**竞技场机关联动。** `arenaEvents` / `IArenaInteractable` 只是一张零消费者的表。替代路径两条且都已存在:
招式动画上挂 `SpawnProjectile` 生成 hazard,或场景侧订阅招式事件频道。
---
## 8. 脚手架修改(CLAUDE.md 第 2、6 条)
**旧轨资产有两条创建链路,不改则会持续产出旧轨对象——这是根因,必须与代码一并修。**
### 8.1 `Editor/Character/CharacterWizardWindow.cs`
| 位置 | 现状 | 改为 |
|---|---|---|
| 第 8 行 | `using BaseGames.Boss;` | 删(仅为 `BossSkillSO`|
| 第 80 行 | `_bossId` 字段,注释 `kept for legacy SkillSequenceSO queries if any` | 删(死字段 + 死注释)|
| 第 692706 行 `CreateChaoFengSkillSO` | 建 `BossSkillSO`,设 `skillId` / `displayName` / `availablePhaseIndices` / `weight` | 建 `EnemyAbilitySO` 的**类型化子类**(按招式类型),设 `abilityId` / `weight` / `cooldown` / `category = AbilityCategory.Attack` / **`rangeRadius > 0`**。阶段索引不再进 SO,改由 `BossPhaseAbilityGate` 承载 |
| 第 709716 行 `ChaoFengSkillDefs` | `(name, id, phases, weight)` | `(name, id, soType, rangeRadius, cooldown, weight)`,与 `GetEnemyAbilityDefs` 的三元组形式对齐;阶段信息移到放置工具的阶段门配置 |
| 第 718727 行 `CreateAllChaoFengSOs` | 提示"检查 `BossSkillExecutor._skills` 绑定" | 改为"检查 `EnemyAiBrain._definitionId = ChaoFeng` 与各能力组件的 `_config` 绑定" |
| 第 785801 行 `BuildBossStatus` | `FindAtPath<BossSkillSO>` | `FindAtPath<EnemyAbilitySO>`;并新增一行 AI 图状态检查(`AiDefinitionRegistry` 是否含 `ChaoFeng`|
| 第 365 行 | `MakeJumpButton("Boss 技能序列查看器", BossSkillSequenceWindow.OpenWindow)` | 删(窗口已删)|
| 第 367 行 | `MakeJumpButton("Data HubBoss技能)", …)` | 改标签为 `Data Hub(敌人/能力)``BossSkillModule` 已删)|
| 第 348 行 | 放置说明提到 `BossSkillExecutor` 组件树 | 改为「`ChaoFengBoss` + 浮空控制器 + 击倒计数 + `EnemyAiBrain` + `BossPhaseAbilityGate` + 5 个能力组件 + HitBox × 4 + 炮口 × 3」|
**`rangeRadius > 0` 是硬要求**`EnemyAbilitySO.Validate()``EnemyBase.BuildAttackSelector()` 都会
`category == Attack && rangeRadius <= 0` 显式报错(招式永远够不着)。向导必须产出合规值,
不能留 0 让作者事后补——那是把创建链路的责任推给下游(第 6 条)。
### 8.2 `Editor/Scene/SceneObjectPlacerTool.cs``PlaceChaoFeng`,约 10891191 行)
| 位置 | 现状 | 改为 |
|---|---|---|
| 1089 | `GetOrAddComponent<BossSkillExecutor>(go)` | 删;改为挂 `EnemyAiBrain``_definitionId = "ChaoFeng"`+ `BossPhaseAbilityGate` + 5 个能力组件 |
| 1098 | 注释「技能执行时由 `BossSkillExecutor` 开关」 | 改为「由各能力组件按 `EnemyAttackSO.hitBoxSlot` 开关」|
| 11611172 | 收集 `BossSkillSO` 赋给 `skillExec._skills` | 改为把 `ABL_ChaoFeng_*``EnemyAbilitySO`)逐个赋给对应能力组件的 `_config`,并把 HitBox 绑进 `MeleeAttackAbility._hitBoxSlots` |
| 1169 | `未找到 BossSkillSO{n}` | `未找到 EnemyAbilitySO{n}` |
| 11861187 | 提示挂入 `BossSkillExecutor._hitBoxes` / 拖炮口进 `BossSkillSO` | 改为槽位绑定说明 + 炮口留在 `ChaoFengBoss` 上(现状已如此,`SpawnProjectile` 读的是 `ChaoFengBoss` 的 muzzle 字段)|
| 1190–1191 | 提示「技能执行仍由 `BossSkillExecutor` 负责」 | 删该括号说明;改为「Boss 走定制路径:`_definitionId = ChaoFeng`,不用配方」|
| 405407 | E004 的提示「★ 挂载 `BossSkillExecutor` 并指定 `BossSkillSO` 列表」 | 删(E004 归属已定为精英怪,走 `ApproachAttackEngagement`|
| 新增 | — | 一并放置 `BossFightTrigger`(发 `EVT_BossFightStarted` + `brain.Send(AiSignal.Engaged)`),否则 Boss 永远不开战(§3.1 缺口)|
### 8.3 顺带一致性修正
`EnemyAbilitySO``[CreateAssetMenu(fileName = "EAB_")]``AssetFolderSpec.md` 第 298 行的
`ABL_`(能力)及两个向导的实际命名都不一致 → 改为 `ABL_`
`EnemyAttackSO``EATK_` 规范未收录、无冲突,本次不动。
---
## 9. 文件清单
```
删除 Assets/_Game/Scripts/Enemies/Boss/BossSkillSO.cs
删除 Assets/_Game/Scripts/Enemies/Boss/AttackPatternSO.cs
删除 Assets/_Game/Scripts/Enemies/Boss/SkillSequenceSO.cs
删除 Assets/_Game/Scripts/Enemies/Boss/BossSkillExecutor.cs
删除 Assets/_Game/Scripts/Enemies/Boss/BossSkillTypes.cs (整个文件)
删除 Assets/_Game/Scripts/Enemies/Boss/WeakPointSystem.cs
删除 Assets/_Game/Scripts/Enemies/Boss/Patterns/TelegraphSystem.cs(连带空目录 Patterns/
删除 Assets/_Game/Scripts/Editor/Modules/BossSkillModule.cs
删除 Assets/_Game/Scripts/Editor/Enemies/BossSkillSequenceWindow.cs
删除 Assets/_Game/Data/Enemies/ChaoFeng/Abilities/ABL_ChaoFeng_{Boomerang,FanCombo,TornadoSmall,TornadoLarge,WindStone}.asset
新增 Assets/_Game/Scripts/AI/IBossControl.cs
新增 Assets/_Game/Scripts/Enemies/Boss/BossPhaseAbilityGate.cs
新增 Assets/_Game/Scripts/Enemies/Boss/BossArenaAnchors.cs
新增 Assets/_Game/Scripts/Enemies/Boss/BossFightTrigger.cs
新增 Assets/_Game/Scripts/Enemies/AIBrain/Modules/BossFragments.cs
新增 Assets/_Game/Scripts/Enemies/AIBrain/Ai/ChaoFengAi.cs
新增 Assets/_Game/Data/Enemies/ChaoFeng/Abilities/ABL_ChaoFeng_*.asset (经向导重建为 EnemyAbilitySO
改 Assets/_Game/Scripts/AI/IAiContext.cs + IBossControl Boss
改 Assets/_Game/Scripts/AI/AiSignal.cs + Engaged
改 Assets/_Game/Scripts/Enemies/AIBrain/EnemyBrainContext.cs 实现 Boss facet(非 Boss 抛异常)
改 Assets/_Game/Scripts/Enemies/Abilities/AttackSelectionMode.cs + WeightedRandomAntiRepeat
改 Assets/_Game/Scripts/Enemies/Abilities/EnemyAttackSelector.cs 防重复
改 Assets/_Game/Scripts/Enemies/Abilities/EnemyAbilityBase.cs CanUse += enabled;删 TelegraphRoutine
改 Assets/_Game/Scripts/Enemies/Abilities/EnemyAbilitySO.cs + designNote;− telegraph 两字段;fileName → ABL_
改 Assets/_Game/Scripts/Enemies/AIBrain/Modules/Engagement/ApproachAttackEngagement.cs 建态逻辑提静态
改 Assets/_Game/Scripts/Animation/EnemyAnimationEvents.cs + 4 个 case
改 Assets/_Game/Scripts/Enemies/EnemyBase.cs + SetInvincible;改名 NotifyDecisionStop;− ConsumeParryEvent
改 Assets/_Game/Scripts/Enemies/Behaviors/EnemyDeathSequence.cs 调用点改名
改 Assets/_Game/Scripts/Combat/HurtBox.cs + 伤害倍率
改 Assets/_Game/Scripts/Enemies/Boss/BossBase.cs 瘦身约 150 行;实现 IBossControl
改 Assets/_Game/Scripts/Enemies/Boss/BossResource.cs 自动触发;+ IsFull
改 Assets/_Game/Scripts/Enemies/Boss/BossResourceConfigSO.cs 自动触发三字段
改 Assets/_Game/Scripts/Enemies/Boss/ChaoFengBoss.cs StopBehaviorTree() 调用点改为 NotifyDecisionStop()
改 Assets/_Game/Scripts/Editor/Character/CharacterWizardWindow.cs §8.1
改 Assets/_Game/Scripts/Editor/Scene/SceneObjectPlacerTool.cs §8.2
测试 Assets/Tests/EditMode/ 见 §11
文档 Docs/Guides/02_EnemyBoss 新增一节「Boss AI 怎么写」(定制路径 + 阶段门 + 图片段)
```
---
## 10. 嘲风落地路线
五招的归属:
| 招 | 能力类型 | 说明 |
|---|---|---|
| `fan_combo` | `MeleeAttackAbility` | 多段 `EATK_`HitBox 走槽位 |
| `boomerang` | `PlayClipAbility` | 生成逻辑已在 `ChaoFengBoss.SpawnProjectile`clip 上挂 `SpawnProjectile("boomerang")` 动画事件 |
| `tornado_small` | `PlayClipAbility` | 同上 |
| `tornado_large` | `PlayClipAbility` | 同上,落点锁定已实现 |
| `wind_stone` | `PlayClipAbility` | 同上,阶段门只在阶段 1 放行 |
阶段门配置:地面四招 `phases = [0]`,风石 `phases = [1]`
---
## 11. 测试策略
| 测试 | 覆盖 |
|---|---|
| `EnemyAttackSelectorTests`(扩)| `WeightedRandomAntiRepeat` 的防重复权重分布;上一招被禁用后不参与选招;无正权重时退化路径不变 |
| `BossPhaseAbilityGateTests`(新)| 阶段切换后启用集正确;`OnSpawn` 回阶段 0;未登记的能力不受影响 |
| `ChaoFengAiTests`(新)| fake `IAiContext` + fake `IBossControl` 驱动 `AiRuntime`,断言 `Wait → Intro → Ground ⇄ GroundAttack → PhaseTx → Air ⇄ AirAttack` 的状态序列与 trace label`Died` 全局边 |
| `EnemyBrainContextTests`(扩)| 非 Boss 敌人访问 `IAiContext.Boss``InvalidOperationException` |
| `HurtBoxTests`(扩)| 伤害倍率生效;默认 1 时行为不变 |
---
## 12. 风险与缓解
| 风险 | 缓解 |
|---|---|
| 删除面大,中途不可用 | **分两次提交**:先加(六件新增 + 嘲风能力资产 + AI 图跑通 + 两个脚手架改造),确认能打之后再删旧轨整包。任何时刻都有一条能跑的路 |
| 删脚本导致资产变 missing script | 删除顺序**必须**先资产后脚本 |
| 关闭 Domain Reload 下的陈旧态 | `BossPhaseAbilityGate` 的启用集是纯运行时态,`OnSpawn` 已重置,无静态态;`AiDefinitionRegistry` 的静态字典已有 `[RuntimeInitializeOnLoadMethod]` |
| 弹反反制暂时缺失 | §6.2 已列为独立后续任务。现状是"弹反任何敌人 Boss 都硬直"的错误行为,删掉是净改善 |
| 能力资产失去编辑器视图 | §6.4,后续补 `EnemyAbilityModule`;本次优先 `AiDefinitionValidator` |
| 脚手架改造遗漏导致旧轨复活 | 改完跑三件自检:`Validate All ScriptableObjects``Validate Address Keys``Physics2D Layer Matrix/Check`;并用向导重建嘲风 5 个资产验证产出合规 |
---
## 13. 注记
- 本设计推翻 2026-07-27 `shared-primitives-decision.md` §5 的「执行层按复杂度分层」,
保留其 §2`WeightedPick` 抽取)与 §3、§4(冷却 / HitBox 时序不抽)三条结论。
- 2026-07-29 `enemy-ai-composable-modules-design.md` §8 把「Boss 双轨统一」列为单独立项,
并注明「E004 归属未定前不动,`ApproachAttackEngagement` 按精英怪尺度做」。
本次确认 E004 归精英怪,`ApproachAttackEngagement` 的投入不作废,反而被 Boss 图复用(§4.2)。
- 竞品调研于 2026-07-30 完成,调研对象与来源链接见对应会话记录;本文档按 CLAUDE.md 第 4 条
只保留功能性描述,不写入具体作品与角色名。