13 KiB
敌人执行层统一 + AI/动画/属性瘦身 —— 设计文档
- 日期:2026-07-10
- 分支:
feat/braingraph-enemy-integration - 范围:E001 样板 + 可复用框架;E002-E006/Boss 的 AI 落地范式在本文件定义、后续逐个实现。
- 关联:braingraph_ai_framework、ai_decision_only_delegates_abilities、enemy_nav_movement_architecture;spec
2026-07-03-enemy-ai-framework-design.md。
1. 背景与问题
敌人 AI 已从三方 Behavior Designer 迁到自研 BrainGraph。四个子系统(movement / stats / state / AI+abilities)通篇审查后暴露三类问题:
- 两套 AI 并存:~50 个
BD_*Behavior Designer 任务 +BaseGames.Enemies.AIasmdef + 三个 Opsive 包,运行期全死,只靠GRAPH_DESIGNER宏还在编译。→ 已在 P1 删除(本文件写作前完成)。 - "同一概念多实现":
- 7 种巡逻:
PatrolAbility(nav 随机) /IMover.WalkRandom(nav 随机, 仅测试) /BD_WalkRandom/BD_InvestigateLastKnown子步 /BD_Patrol(速度踱步, 撞墙翻向) /BD_PatrolWaypoints(路点) /FlyingDirectNavigator.WalkToRandom(MovePosition)。既有 4 份"nav 随机游走"重复,又把有玩法差异的"踱步/路点"在迁移中弄丢。 - 4 套移动执行:
EnemyMovement直设 velocity /PendingInput信号 /FlyingEnemy直设 velocity /FlyingDirectNavigatorMovePosition。nav 与 AI 会在同一 FixedUpdate 写同一批PendingInput字段互相覆盖。 - "朝向"4 入口、"停"4 入口、"设速度"跨
EnemyStatsSO/TBM.movementSpeed/MoveInput三处。 IMover(含UseChaseSpeed/UsePatrolSpeed/LookAround/WalkRandom)零生产调用,被能力直调EnemyBase架空。
- 7 种巡逻:
- 冗余状态层:
AiPhase(Idle/Patrol/Alert/Chase/Combat/Investigate/ReturnHome) 与 BrainGraph 状态严格 1:1,只用来选动画、AI 从不回读;Combat/Investigate/ReturnHome是死枚举值。 - Model A 过度设计:
IdleAbility/PatrolAbility/AlertAbility是while(true) yield常驻协程,唯一作用是占住IsRunning让EnsureAbility空转——为琐碎行为各付出「协程+注册项+SO 资产」,且借用了本为攻击设计的EnemyAbilitySO(attackSequence/telegraph/range/LOS/priority全是死字段)。 - 属性冗余:
EnemyStatsSO约 11/26 字段死或与 sensor 槽 /DamageSourceSO重复;速度配在 3 处;玩家/敌人难度缩放逻辑重复。
现状要点:只有 E001 有可用 AI([AiDefinition("E001")] + EnemyAiBrain)。E002/E003/E006/Boss 完全无 AI;E004/E005 prefab 里的 "BehaviorTree" 只是 _stopBehaviorTree bool 字段,非 Opsive 组件。→ 整套 AI/能力设计只在 1 个敌人上验证过,是重构的最佳时机。
2. 设计原则(不变)
- AI 只决策,不 actuate:AI 决定"进哪个状态 / 转换条件 / 触发哪个能力 / 声明哪个移动意图";移动/朝向/停/速度/动画的实现不在 AI。见 ai_decision_only_delegates_abilities。
- 根因修复,不下游兜底:删除多余抽象本身(而非留着不调用),让"AI 无法 actuate"在类型层成立。见 CLAUDE.md §6。
- "能力"一词收回给战斗:带冷却/预警/HitBox/接触伤害才叫 ability;纯移动/朝向归 locomotion。
3. 目标架构
三条正交轴,各自单一职责:
| 轴 | 组件 | 职责 | 谁改它 |
|---|---|---|---|
| 反应态 | EnemyStateType FSM(Controlled/Hurt/Stagger/KnockUp/Dead) |
受击/硬直/死亡生命周期;当 AI 的门(IsControllable) |
伤害/招架/死亡 |
| 决策 | BrainGraph(AiRuntime/PerceptionStateMachine/AiScript) |
感知→状态;声明移动意图 + 触发攻击能力 | 感知查询 |
| 执行 | EnemyLocomotion(新) + EnemyAbilityBase(攻击) |
移动/朝向/步态动画;攻击 | AI 声明意图;能力协程 |
3.1 核心组件 EnemyLocomotion(统一移动执行器)
单一移动入口,取代 4 套移动模型 + IMover + 三个协程能力 + EnemyBase 上散落的移动方法。
namespace BaseGames.Enemies
{
public enum LocomotionMode { Idle, Patrol, Face, Approach }
public enum PatrolStrategy { Wander, Pace, Waypoints }
// 对外只暴露"声明意图"的 API(AI/能力调用),执行在 FixedUpdate 内部完成
public interface IEnemyLocomotion
{
void SetMode(LocomotionMode mode); // Idle(停)/ Patrol(按配置策略游走)
void Approach(Transform target); // 持续跟随(Chase 能力 / 调查用),派生 RunSpeed
void MoveTo(Vector2 point); // 一次性目标点
void Face(Vector2 lookAt); // 停 + 朝向(Alert 用)
void Stop();
LocomotionMode CurrentMode { get; }
bool IsMoving { get; }
}
}
- 内部双后端,作为可替换的
IPathAgent:- 地面怪 =
EnemyNavAgent(PathBerserker2d)。PB2d 是表面图寻路(NavSurface/NavSegment+ 表面间NavLink),agent 始终映射到可行走表面——不支持飞行/自由空间寻路(已核实包源码:NavSegmentPositionPointer/IsOnLink/KeepGrounded,零 fly/aerial 支持)。 - 飞行怪 =
FlyingDirectNavigator。不走 PB2d,直接Rigidbody2D.MovePosition向目标点直线直飞(含正弦悬停)。 EnemyLocomotion是二者之上的唯一门面,按敌人类型选后端。物理原生架构(寻路只给方向、velocity 执行、碰撞体底部对齐 y=0)保持,见 enemy_nav_movement_architecture。
- 地面怪 =
- 巡逻 =
Patrol模式 + 每敌人序列化PatrolStrategy+ 参数:一个组件三种玩法(Wander 随机 / Pace 撞墙翻向踱步 / Waypoints 路点序列),消灭 7 份实现并找回踱步/路点。 - 速度单一来源:由模式派生(Patrol→
WalkSpeed、Approach→RunSpeed,读EnemyStatsSO)。删除EnemyMovement对EnemyStatsSO的二次直读接线(EnemyMovement._config),删除经TBM.movementSpeed的速度中转重复。 Approach不改追击语义:ContactChaseAbility保留为能力;仅把内部_enemy.MoveTo(player)换成Locomotion.Approach(player);接触伤害(战斗语义)仍在能力里。
3.2 AI 决策层用法(仍"只决策")
PerceptionStateMachine 状态不再绑"能力 id",改绑 locomotion 意图 + 可选攻击能力 id:
Idle_Disguise : OnEnter → Locomotion.SetMode(Idle)
Move_Patrol : OnEnter → Locomotion.SetMode(Patrol)
Alert : OnEnter/Tick → Locomotion.Face(player)
Chase : OnEnter → Combat.UseAbility("e001_chase") // 能力内部 Approach(player)
Death : 终态(死亡演出走反应态 FSM)
SetMode/Face/Approach是声明(与原SetAiPhase同性质),执行在EnemyLocomotion——AI 仍不碰 velocity/朝向实现。- 删除
IdleAbility/PatrolAbility/AlertAbility三协程能力、ABL_E001_Idle/ABL_E001_Patrol/ABL_E001_Alert三 SO、IMover接口及其在EnemyBrainContext的实现。 IAiContext增加IEnemyLocomotion Locomotion { get; }(替换IMover Mover)。ICombatant.UseAbility/IsAbilityRunning/InterruptAbilities保留(攻击/Chase 用)。PerceptionStateMachine.Config改为每状态填LocomotionIntent(模式 + 可选 target 来源)+ 可选abilityId;AddAbilityState换成AddLocomotionState。
3.3 动画(删 AiPhase)
EnemyLocomotion按当前模式/实际速度驱动步态 clip:静止→Idle、Patrol 移动→Walk、Approach→Run、Face→Alert。映射表 = 原AiPhase→AnimConfigswitch,改 key 在LocomotionMode上(放EnemyLocomotion或一个轻量EnemyAnimationDriver)。- 反应态(Hurt/Stagger/KnockUp/Dead)与攻击/出现能力照旧各自播 clip,覆盖在步态之上。
- 删除
AiPhase枚举、EnemyBase.SetAiPhase/CurrentAiPhase/OnAiPhaseChanged,及所有能力里的SetAiPhase(...)。ReceiveAlert里用AiPhase做的"已交战不降级"判断改读 AI 当前状态名或一个bool IsEngaged。
3.4 属性清理(EnemyStatsSO)
删除死/重复字段:AttackDamage、AttackRange、DetectRange、DetectAngleDeg、EyeOffset、LOSBlockingMask(感知全归 sensor 槽 PhysicsPerceptionSystem)、AlertDuration、InvestigateDuration、KnockbackForce、HitStunDuration(击退归 DamageSourceSO)、HitTierConfig.heavyHitThreshold(硬直由 Poise Break vs PoiseLevel 决定)。
- 速度单一来源(删
EnemyMovement._config直读,改经EnemyLocomotion读Stats)。 - (低优先,可选)玩家/敌人难度缩放抽共享
ScalableVitalshelper(当前两边近乎逐行重复)。
4. E002-E006 / Boss 的 AI 落地范式(本文件定义,后续实现)
每个敌人 = 一个 [AiDefinition("Exxx")] AiScript 子类,内容仅为:
- 复用
PerceptionStateMachine(或 Boss 专用图),填状态名 + 每状态的 locomotion 意图 + 攻击能力 id。 - 攻击行为写成
EnemyAbilityBase子类(已有一批:Melee/Projectile/Leap/Charge/CeilingDrop/…),配EnemyAbilitySO(真攻击才需 cooldown/telegraph/HitBox)。 - 巡逻/待机/警觉/追击不写能力,只在状态里声明 locomotion 意图(含选
PatrolStrategy)。 - 脚手架
SceneObjectPlacerTool.PlaceExxx挂EnemyAiBrain(_definitionId)+EnemyLocomotion+ 攻击能力子节点。
近重复能力(后续合并,不阻塞本次):CeilingDropAbility vs AnimatedCeilingDropAbility(后者注释已声明取代前者);AppearAbility = PlayClipAbility + 一行 SetAiPhase;FacePlayerAbility 与 AlertAbility 的"朝向"原语;多个攻击能力里手抄的 HitBox 窗口 + IsGrounded 射线。
5. 分期实施(每期独立可提交、可回滚)
- P1 死代码清除 — ✅ 已完成(commit
e894a9d):删BD_*+BaseGames.Enemies.AIasmdef +GRAPH_DESIGNER宏 + 三个com.opsive.*包;恢复编译(0 错误)。 - P2 引入
EnemyLocomotion— 建组件 + 接入 Nav/Flying 后端;先与旧 API 并存,E001 切过去 PlayMode 跑通四态 + de-escalation。 - P3 AI 改绑 locomotion 意图 — 删三协程能力/三 SO/
IMover;PerceptionStateMachine改AddLocomotionState;E001CaoZhiAi改声明意图;ContactChaseAbility内部改Approach。E001 验证。 - P4 删
AiPhase— 动画改由 locomotion 驱动;迁移SetAiPhase全部调用点;ReceiveAlert改判据。 - P5 Stats 清理 — 删死字段 + 速度单一来源(+ 可选难度缩放 helper)。
- P6 巡逻策略补全 — 实现
PatrolStrategy.Pace/Waypoints(找回丢失玩法)。 - 文档产物 — 本文件即 E002-E06/Boss AI 落地范式;各敌人后续按范式逐个实现(独立任务)。
6. 测试
- EditMode:BrainGraph 现有测试(
Assets/Tests/EditMode/AI/)保留;IMover相关用例改用IEnemyLocomotionspy 或ICombatantspy 验证状态机 Enter/Tick/Exit 机制。 - PlayMode(TestRoomA,E001):每期验 待机(伪装)→巡逻→警觉(朝向)→追击(接触伤害)→死亡 全链 + 脱离感知 de-escalation;断言 AI 脚本零
velocity/朝向实现、动画随 locomotion 模式切换、console 0 报错。 - 自检:
Validate All ScriptableObjects/Validate Address Keys/Physics2D Layer Matrix Check(CLAUDE.md §3)。
7. 风险与缓解
- 飞行怪双模型:
FlyingEnemy/FlyingDirectNavigator走MovePosition,需在EnemyLocomotion内以IPathAgent后端形式收编,避免又留一套并行执行。P2 明确覆盖飞行分支。EnemyLocomotionAPI 对上层一致,SetMode/Approach/MoveTo在飞行后端映射为直飞目标点。 - 飞行绕障是已知缺口(非本次范围):PB2d 表面寻路给不了飞行绕障,现有
FlyingDirectNavigator是直线直飞、无障碍绕行;当前 E002/E004/E005/E006 prefab 两种导航组件都未挂(飞行导航"有实现未接线")。真要做"能绕墙的飞行怪"时需单独的飞行寻路方案(如 2D 网格 A*/点图),不能靠 PB2d 或现有直飞。本次不实现;作为落地飞行怪 AI 时的前置决策记录在案。 - 物理原生易回归:nav 只给方向、velocity 执行、
TBM.SegmentMovement关、碰撞体底部 y=0——重构中必须保留,见 enemy_nav_movement_architecture。 - 动画覆盖顺序:反应态/攻击 clip 必须能压过步态 clip;P4 需验证受击/攻击时步态不抢播。
AiPhase删除波及面:SetAiPhase有多处调用点(能力、ReceiveAlert、gizmo/overlay);P4 逐点迁移并保留调试可视化(改读状态名)。- 范围克制:本次只在 E001 落地并验证;E002-E006/Boss 仅定范式,避免一次性大改多敌人放大回归面。
8. 成功判据
- 编译 0 错误;E001 PlayMode 行为与重构前一致(四态 + de-escalation + 接触伤害)。
- 代码库中"巡逻"实现 1 个(
EnemyLocomotion+ 策略)、"移动执行"入口 1 个、无AiPhase、无IMover、无 Idle/Patrol/Alert 协程能力与其 SO。 EnemyStatsSO无死字段;速度单一来源。- E002-E006/Boss 有明确、可照抄的 AI 落地范式文档。