spec:ResourceFull 由「无组件时恒 false」改为显式抛,与 Boss facet、AnchorAt 统一为「依赖组件缺失一律抛,不返默认值」。 计划:Task 16 补上 BossBase 残余三处行为树残留的清理项(含运行时告警字符串); Task 19 记录 Patterns/ 是零外部引用的独立程序集,可整目录删。
37 KiB
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 已在走这条 |
| 浮空 / 击落计数 / 资源 | 旁挂 MonoBehaviour,AI 图不知道其内部 | 单 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:
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 图形状(范例)
[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 + 两个子系统引用字段 |
各 2–3 行 |
| 5 | BossArenaAnchors 组件 + 「移动到第 N 锚点」图片段 |
小组件 |
修订(2026-07-30,实施计划阶段):初稿列的第 5 件「
EnemyBase.SetInvincible(float)」已取消。 核查PlayerAnimationEvents后确认玩家侧的EnableIFrame/DisableIFrame走的是HurtBox.SetInvincible(bool)——该 API 已存在,无敌时长由动画时间轴上两个事件的间距表达。 敌人侧照同一形态接线即可,不需要在EnemyBase上新造带时长的无敌源。 这也更正确:时长归动画,不归代码常量。
5.1 IBossControl
namespace BaseGames.AI
{
/// Boss 专属决策面。小怪图访问 IAiContext.Boss 会抛异常(显式报错,不静默兜底)。
public interface IBossControl
{
int CurrentPhase { get; }
bool IsPhaseTransitioning { get; }
bool ResourceFull { get; } // BossResource 满值;未挂该组件即抛(见下)
void BeginPhaseTransition(int targetPhase, float invincibleDuration);
// 竞技场锚点(§5.5)。归本 facet 而非黑板:见下方修订说明。
Vector2 AnchorAt(int index);
float DistanceToAnchor(int index);
}
}
修订(2026-07-30,实施计划阶段):锚点坐标初稿设计为经
IAiContext.Blackboard传递 (BossBase在Awake写入黑板键)。该方案有两处硬阻塞,已废弃:
- 拿不到黑板 ——
EnemyAiBrain._context是私有字段且无公开访问器,BossBase无从写入。- 时序坑 ——
EnemyBrainContext.ResetScratch()会_blackboard.Clear()(对象池复用时调用), 写入点必须排在它之后,是个易错的隐式顺序依赖。改为挂在
IBossControl上:锚点本就是 Boss 专属知识,facet 已存在,BossBase在Awake解析组件即可。净收益是少一个失败模式(黑板键缺失)、少一条隐式时序约束,且DistanceToAnchor把"我离锚点多远"封在 Boss 内部,不需要给 AI 层引入新的位置概念。
EnemyBrainContext.Boss => _boss ?? throw new InvalidOperationException(...)。
按 CLAUDE.md 第 6 条:小怪图误用它必须崩,不能返回一个空对象把问题盖住。
修订(2026-07-30,Task 1+2 代码审查):
ResourceFull初稿定为「无资源组件时恒 false」, 已改为未挂BossResource时显式抛。原设计是同一条第 6 条禁止的下游兜底—— 图上挂了When(x => x.Boss.ResourceFull)边、预制体却漏挂组件时,那条边会永远不成立且零报错。 与紧邻的Bossfacet、以及 §5.5 的AnchorAt保持同一失败语义:Boss 专属 facet 的每个成员, 在其依赖组件缺失时一律显式抛,不返回默认值。
BossBase 实现 IBossControl;EnemyBrainContext 在构造时 _boss = enemy as BossBase。
5.2 BossPhaseAbilityGate
[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 |
招式中途无敌:突进穿过玩家时不吃伤害、传送消失期间无敌 |
四个 case 的实现与 PlayerAnimationEvents 逐字对称:反馈两件走 IFeedbackPlayer.TriggerPreset /
PlaySFXById(EnemyFeedback 已实现该接口),无敌两件走 HurtBox.SetInvincible(bool)(API 已存在)。
EnemyAnimationEvents 因此需补两个子系统引用:HurtBox _hurtBox 与 IFeedbackPlayer _feedback
(后者在 Awake 用 GetComponentInParent<IFeedbackPlayer>() ?? NullFeedbackPlayer.Instance 解析,
与玩家侧同写法)。
5.5 BossArenaAnchors
竞技场锚点走位(站在悬空平台上出招、传送到房间固定角落)是竞品定点型 Boss 的核心。
IEnemyLocomotion.MoveTo(Vector2) 能到点,但锚点集合无处安放。
一个持有 Transform[] 的组件 + 一个「移动到第 N 个锚点」的图片段即可。
坐标经 IBossControl.AnchorAt(int) / DistanceToAnchor(int) 暴露给图(不走黑板,见 §5.1 修订)。
锚点物体放在场景里而非 SO 里——它随房间布局走。下标越界或引用为空显式抛,
不回退到 Boss 自身位置(那会让"锚点漏配"表现为"Boss 站着不动",是典型的下游兜底掩盖问题)。
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.3,YAGNI,留 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 |
删(死字段 + 死注释) |
第 692–706 行 CreateChaoFengSkillSO |
建 BossSkillSO,设 skillId / displayName / availablePhaseIndices / weight |
建 EnemyAbilitySO 的类型化子类(按招式类型),设 abilityId / weight / cooldown / category = AbilityCategory.Attack / rangeRadius > 0。阶段索引不再进 SO,改由 BossPhaseAbilityGate 承载 |
第 709–716 行 ChaoFengSkillDefs |
(name, id, phases, weight) |
(name, id, soType, rangeRadius, cooldown, weight),与 GetEnemyAbilityDefs 的三元组形式对齐;阶段信息移到放置工具的阶段门配置 |
第 718–727 行 CreateAllChaoFengSOs |
提示"检查 BossSkillExecutor._skills 绑定" |
改为"检查 EnemyAiBrain._definitionId = ChaoFeng 与各能力组件的 _config 绑定" |
第 785–801 行 BuildBossStatus |
FindAtPath<BossSkillSO> |
FindAtPath<EnemyAbilitySO>;并新增一行 AI 图状态检查(AiDefinitionRegistry 是否含 ChaoFeng) |
| 第 365 行 | MakeJumpButton("Boss 技能序列查看器", BossSkillSequenceWindow.OpenWindow) |
删(窗口已删) |
| 第 367 行 | MakeJumpButton("Data Hub(Boss技能)", …) |
改标签为 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,约 1089–1191 行)
| 位置 | 现状 | 改为 |
|---|---|---|
| 1089 | GetOrAddComponent<BossSkillExecutor>(go) |
删;改为挂 EnemyAiBrain(_definitionId = "ChaoFeng")+ BossPhaseAbilityGate + 5 个能力组件 |
| 1098 | 注释「技能执行时由 BossSkillExecutor 开关」 |
改为「由各能力组件按 EnemyAttackSO.hitBoxSlot 开关」 |
| 1161–1172 | 收集 BossSkillSO 赋给 skillExec._skills |
改为把 ABL_ChaoFeng_*(EnemyAbilitySO)逐个赋给对应能力组件的 _config,并把 HitBox 绑进 MeleeAttackAbility._hitBoxSlots |
| 1169 | 未找到 BossSkillSO:{n} |
未找到 EnemyAbilitySO:{n} |
| 1186–1187 | 提示挂入 BossSkillExecutor._hitBoxes / 拖炮口进 BossSkillSO |
改为槽位绑定说明 + 炮口留在 ChaoFengBoss 上(现状已如此,SpawnProjectile 读的是 ChaoFengBoss 的 muzzle 字段) |
| 1190–1191 | 提示「技能执行仍由 BossSkillExecutor 负责」 |
删该括号说明;改为「Boss 走定制路径:_definitionId = ChaoFeng,不用配方」 |
| 405–407 | 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;+ _hurtBox / _feedback 引用
改 Assets/_Game/Scripts/Enemies/EnemyBase.cs 改名 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 条 只保留功能性描述,不写入具体作品与角色名。