Files
zeling_v2/Docs/superpowers/specs/2026-07-10-enemy-locomotion-refactor-design.md
T
joywayer ab8f61d2d9 @
docs(ai): 敌人执行层统一 + AI/动画/属性瘦身 设计文档

通篇审查敌人架构(movement/stats/state/AI+abilities)后的重构方案:
引入统一 EnemyLocomotion(Idle/Patrol/Face/Approach 模式 + Wander/Pace/
Waypoints 策略)取代 7 种巡逻/4 套移动/IMover/三个协程能力;删 AiPhase
影子层(动画改由 locomotion 驱动);清 EnemyStatsSO ~11 个死字段。范围
E001 样板+框架,E002-E06/Boss 定落地范式后续实现。P1(死代码清除)已完成。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-10 11:39:26 +08:00

12 KiB
Raw Blame History

敌人执行层统一 + AI/动画/属性瘦身 —— 设计文档

1. 背景与问题

敌人 AI 已从三方 Behavior Designer 迁到自研 BrainGraph。四个子系统(movement / stats / state / AI+abilities)通篇审查后暴露三类问题:

  1. 两套 AI 并存~50 个 BD_* Behavior Designer 任务 + BaseGames.Enemies.AI asmdef + 三个 Opsive 包,运行期全死,只靠 GRAPH_DESIGNER 宏还在编译。→ 已在 P1 删除(本文件写作前完成)
  2. "同一概念多实现"
    • 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 / FlyingDirectNavigator MovePosition。nav 与 AI 会在同一 FixedUpdate 写同一批 PendingInput 字段互相覆盖。
    • "朝向"4 入口、"停"4 入口、"设速度"跨 EnemyStatsSO/TBM.movementSpeed/MoveInput 三处。
    • IMover(含 UseChaseSpeed/UsePatrolSpeed/LookAround/WalkRandom)零生产调用,被能力直调 EnemyBase 架空。
  3. 冗余状态层AiPhase(Idle/Patrol/Alert/Chase/Combat/Investigate/ReturnHome) 与 BrainGraph 状态严格 1:1,只用来选动画、AI 从不回读;Combat/Investigate/ReturnHome 是死枚举值。
  4. Model A 过度设计IdleAbility/PatrolAbility/AlertAbilitywhile(true) yield 常驻协程,唯一作用是占住 IsRunningEnsureAbility 空转——为琐碎行为各付出「协程+注册项+SO 资产」,且借用了本为攻击设计的 EnemyAbilitySOattackSequence/telegraph/range/LOS/priority 全是死字段)。
  5. 属性冗余EnemyStatsSO 约 11/26 字段死或与 sensor 槽 / DamageSourceSO 重复;速度配在 3 处;玩家/敌人难度缩放逻辑重复。

现状要点:只有 E001 有可用 AI[AiDefinition("E001")] + EnemyAiBrain)。E002/E003/E006/Boss 完全无 AIE004/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 FSMControlled/Hurt/Stagger/KnockUp/Dead 受击/硬直/死亡生命周期;当 AI 的门(IsControllable) 伤害/招架/死亡
决策 BrainGraphAiRuntime/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 }

    // 对外只暴露"声明意图"的 APIAI/能力调用),执行在 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; }
    }
}
  • 内部后端不变:地面怪用 EnemyNavAgent(PathBerserker2d)、飞行怪用 FlyingDirectNavigator,作为可替换的 IPathAgentEnemyLocomotion 是它们之上的唯一门面。物理原生架构(寻路只给方向、velocity 执行、碰撞体底部对齐 y=0)保持,见 enemy_nav_movement_architecture
  • 巡逻 = Patrol 模式 + 每敌人序列化 PatrolStrategy + 参数:一个组件三种玩法(Wander 随机 / Pace 撞墙翻向踱步 / Waypoints 路点序列),消灭 7 份实现并找回踱步/路点。
  • 速度单一来源:由模式派生(Patrol→WalkSpeed、Approach→RunSpeed,读 EnemyStatsSO)。删除 EnemyMovementEnemyStatsSO 的二次直读接线(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 来源)+ 可选 abilityIdAddAbilityState 换成 AddLocomotionState

3.3 动画(删 AiPhase

  • EnemyLocomotion 按当前模式/实际速度驱动步态 clip:静止→Idle、Patrol 移动→Walk、Approach→Run、Face→Alert。映射表 = 原 AiPhase→AnimConfig switch,改 key 在 LocomotionMode 上(放 EnemyLocomotion 或一个轻量 EnemyAnimationDriver)。
  • 反应态(Hurt/Stagger/KnockUp/Dead)与攻击/出现能力照旧各自播 clip,覆盖在步态之上。
  • 删除 AiPhase 枚举、EnemyBase.SetAiPhase/CurrentAiPhase/OnAiPhaseChanged,及所有能力里的 SetAiPhase(...)ReceiveAlert 里用 AiPhase 做的"已交战不降级"判断改读 AI 当前状态名或一个 bool IsEngaged

3.4 属性清理(EnemyStatsSO

删除死/重复字段:AttackDamageAttackRangeDetectRangeDetectAngleDegEyeOffsetLOSBlockingMask(感知全归 sensor 槽 PhysicsPerceptionSystem)、AlertDurationInvestigateDurationKnockbackForceHitStunDuration(击退归 DamageSourceSO)、HitTierConfig.heavyHitThreshold(硬直由 Poise Break vs PoiseLevel 决定)。

  • 速度单一来源(删 EnemyMovement._config 直读,改经 EnemyLocomotionStats)。
  • (低优先,可选)玩家/敌人难度缩放抽共享 ScalableVitals helper(当前两边近乎逐行重复)。

4. E002-E006 / Boss 的 AI 落地范式(本文件定义,后续实现)

每个敌人 = 一个 [AiDefinition("Exxx")] AiScript 子类,内容仅为:

  1. 复用 PerceptionStateMachine(或 Boss 专用图),填状态名 + 每状态的 locomotion 意图 + 攻击能力 id
  2. 攻击行为写成 EnemyAbilityBase 子类(已有一批:Melee/Projectile/Leap/Charge/CeilingDrop/…),配 EnemyAbilitySO(真攻击才需 cooldown/telegraph/HitBox)。
  3. 巡逻/待机/警觉/追击不写能力,只在状态里声明 locomotion 意图(含选 PatrolStrategy)。
  4. 脚手架 SceneObjectPlacerTool.PlaceExxxEnemyAiBrain(_definitionId) + EnemyLocomotion + 攻击能力子节点。

近重复能力(后续合并,不阻塞本次):CeilingDropAbility vs AnimatedCeilingDropAbility(后者注释已声明取代前者);AppearAbility = PlayClipAbility + 一行 SetAiPhaseFacePlayerAbilityAlertAbility 的"朝向"原语;多个攻击能力里手抄的 HitBox 窗口 + IsGrounded 射线。

5. 分期实施(每期独立可提交、可回滚)

  • P1 死代码清除 已完成commit e894a9d):删 BD_* + BaseGames.Enemies.AI asmdef + GRAPH_DESIGNER 宏 + 三个 com.opsive.* 包;恢复编译(0 错误)。
  • P2 引入 EnemyLocomotion — 建组件 + 接入 Nav/Flying 后端;先与旧 API 并存,E001 切过去 PlayMode 跑通四态 + de-escalation。
  • P3 AI 改绑 locomotion 意图 — 删三协程能力/三 SO/IMoverPerceptionStateMachineAddLocomotionStateE001CaoZhiAi 改声明意图;ContactChaseAbility 内部改 Approach。E001 验证。
  • P4 删 AiPhase — 动画改由 locomotion 驱动;迁移 SetAiPhase 全部调用点;ReceiveAlert 改判据。
  • P5 Stats 清理 — 删死字段 + 速度单一来源(+ 可选难度缩放 helper)。
  • P6 巡逻策略补全 — 实现 PatrolStrategy.Pace/Waypoints(找回丢失玩法)。
  • 文档产物 — 本文件即 E002-E06/Boss AI 落地范式;各敌人后续按范式逐个实现(独立任务)。

6. 测试

  • EditModeBrainGraph 现有测试(Assets/Tests/EditMode/AI/)保留;IMover 相关用例改用 IEnemyLocomotion spy 或 ICombatant spy 验证状态机 Enter/Tick/Exit 机制。
  • PlayModeTestRoomAE001:每期验 待机(伪装)→巡逻→警觉(朝向)→追击(接触伤害)→死亡 全链 + 脱离感知 de-escalation;断言 AI 脚本零 velocity/朝向 实现、动画随 locomotion 模式切换、console 0 报错。
  • 自检Validate All ScriptableObjects / Validate Address Keys / Physics2D Layer Matrix CheckCLAUDE.md §3)。

7. 风险与缓解

  • 飞行怪双模型FlyingEnemy/FlyingDirectNavigatorMovePosition,需在 EnemyLocomotion 内以 IPathAgent 后端形式收编,避免又留一套并行执行。P2 明确覆盖飞行分支。
  • 物理原生易回归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 落地范式文档。