Files
zeling_v2/Docs_Dev/superpowers/specs/2026-07-24-enemy-nav-approach-attack-design.md
T

13 KiB
Raw Blame History

敌人寻路逼近 + 攻击选择器 设计

日期:2026-07-24  状态:设计(待实现) 关联:2026-07-23-enemy-pathfind-obstruction-handling-design.md(受阻处理)、 2026-07-21-enemy-ability-config-to-so-design.md(能力参数入 SO)、 2026-07-03-enemy-ai-framework-design.mdBrainGraph HFSM)。

1. 背景与目标

1.1 现状

  • 导航层EnemyNavAgent / PathBerserker2d)已完整:RequestMoveTo 自带重寻路防抖、受阻检测、 改道到最近可达点、NavLink(跳/落/爬)委托能力执行。
  • 执行层EnemyLocomotion)有 LocomotionMode.ApproachMoveTo(Vector2),但目前没有任何 AI 状态真正用寻路做持续逼近。
  • 决策层PerceptionStateMachine)的 Chase 现在只能挂单个 ChaseAbilityId。E001 用的 ContactChaseAbility 是"朝玩家一侧速度直冲、撞墙即停"的 committed 冲锋,完全不走寻路 被中间隔着的平台/沟拦住就停。
  • 感知层PhysicsPerceptionSystem)槽位驱动,EnemyBase 把槽位翻译成 ISensor 语义供 AI 读。

1.2 目标(本设计范围)

新增一种与"冲锋(Chase)"并列的交战范式 —— 寻路逼近(Approach)+ 到攻击范围内选招攻击

  1. 敌人沿寻路路径(含跳/落/爬 NavLink)绕过地形持续逼近玩家,而非直线冲。
  2. 进入某招的攻击范围后,按攻击策略从"够得着且未冷却"的候选里选一个攻击
  3. 攻击时暂停逼近;攻击结束回到逼近,重判距离/重选招,形成 逼近 → 攻击 → 重定位 → 攻击 的闭环。

冲锋范式(ContactChaseAbility)保持不变,两种范式共存、按敌人选用。

1.3 非目标

  • 不改冲锋(Chase)范式的行为。
  • 不做矩形/扇形攻击范围(本期仅圆形距离判定,PerceptionProbe 化留作后续扩展)。
  • 不做多目标/仇恨列表(沿用现有单一玩家目标)。
  • 不新建敌人预制体(预制体已延后,见 memory enemy_prefabs_deferred)。

2. 职责边界(核心原则)

层级 归属组件 负责
敌人级交战状态感知 PhysicsPerceptionSystem 玩家是否脱战 / 进警戒 / 进追逐等敌人级状态(aggro/alert/los/sight 槽)
招式级攻击触发范围 各攻击能力自身 本招"够不够得着"——射程内聚在招自己,与动画/HitBox/冷却/权重同处
交战决策(逼近↔攻击、选招) AI 状态机(PerceptionStateMachine + AiScript 只决定进哪个态/转换条件/何时选招;不做位移、不做选招计算
逼近位移 EnemyLocomotion 只执行寻路逼近(新增 Pursue
选招计算 EnemyAttackSelector 过滤候选(射程+冷却+LOS+着地)→ 按 mode 选一个;纯逻辑、可单测
攻击执行 各攻击能力 自己的动画/HitBox/朝向/停移

关键:交战入口仍由感知系统决定aggro 槽 → InChaseZone() → 进 Approach); "某招够不够得着"下沉到招自身(编辑内聚:改一个招只看一个组件,不再跨到中心感知配槽、不再对名字)。

3. 数据流

感知系统(aggro/los/sight 槽) ──> ISensor ──> PerceptionStateMachine
        │(是否脱战/警戒/追逐)                     │决策
        ▼                                         ▼
   InChaseZone()=true ──> Approach态            Attack态
                            │Tick                  │OnEnter
             Locomotion.Pursue(LastKnown)   Combat.UseBestAttack()
                            │(寻路,RunSpeed)        │
                            ▼                       ▼
        EnemyBase.MoveTo ─> Nav(防抖/受阻/NavLink)  EnemyAttackSelector
                                                    (各候选自答 InAttackRange)
                                                        │
                                                    选中招.Execute()(自驱动画/HitBox/停移)
  • Approach → AttackCombat.HasEligibleAttack()(有招够得着且未冷却)。
  • Attack → Approach!Combat.IsAbilityRunning()(打完回逼近重判)。
  • 任意态 → Rest:脱离全部感知区(!InChaseZone && !InVisionZone)。

4. 组件设计

4.1 EnemyAbilitySO 新增字段(选招/射程元数据,基类通用)

[Header("调度分类与选招")]
public AbilityCategory category = AbilityCategory.None; // None/Attack/Movement/Utility
[Min(0f)] public float weight = 1f;                     // 权重随机用

[Header("攻击触发范围(招式自管,圆形)")]
[Min(0f)] public float   rangeRadius = 0f;   // 0 = 未配置(category==Attack 时 Awake 报错)
public Vector2 rangeOffset = Vector2.zero;   // 相对敌人,X 随朝向翻转
// 复用现有:requiresLineOfSight / requiresGrounded / priority / cooldown
  • AbilityCategory 新增枚举(BaseGames.Enemies.Abilities)。选择器只收 category == Attack
  • 旧的 preferredMinRange/preferredMaxRange 降级为纯编辑器提示(不再参与选招),后续可删。

4.2 攻击能力:自答"够不够得着"

EnemyAbilityBase 增加:

/// <summary>本招攻击触发范围内是否有玩家(圆形距离判定,招式自管其射程)。</summary>
public virtual bool InAttackRange()
{
    if (_config == null || _config.rangeRadius <= 0f || _enemy?.PlayerTransform == null) return false;
    float sign   = _transform.localScale.x < 0f ? -1f : 1f;
    Vector2 origin = (Vector2)_transform.position
                   + new Vector2(_config.rangeOffset.x * sign, _config.rangeOffset.y);
    float r = _config.rangeRadius;
    return ((Vector2)_enemy.PlayerTransform.position - origin).sqrMagnitude <= r * r;
}
  • LOS 复用敌人级 EnemyBase.IsPlayerVisible()(不在招上单独打射线)。
  • 无物理 Overlap(有玩家 Transform,距离判定即可),零 GC、零额外物理查询。

4.3 EnemyAttackSelectorEnemyBase 持有,类比 EnemyAbilityRegistry

  • 收集EnemyBase.AwakeAbilities.CollectFrom 后,从 Abilities.AllConfig.category == Attack 的能力为候选。
  • 可单测抽象:选择器作用于候选接口,不直连 MonoBehaviour
    public interface IAttackCandidate {
        bool  CanUse { get; }        // 未冷却+未运行+存活
        bool  InAttackRange();       // 招式自管射程
        bool  RequiresGrounded { get; }
        float Weight { get; }
        int   Priority { get; }
        bool  Execute();
    }
    
    EnemyAbilityBase(攻击类)实现之。
  • API
    bool           HasEligible(bool hasLOS, bool grounded);
    IAttackCandidate Select(bool hasLOS, bool grounded, AttackSelectionMode mode); // 无合格→null
    
  • 单招合格判定c.CanUse && c.InAttackRange() && (!requiresLOS(此招) || hasLOS) && (!c.RequiresGrounded || grounded)
    • requiresLineOfSight 读各招 ConfighasLOS = enemy.IsPlayerVisible()groundedEnemyBase 提供。
  • 选招(AttackSelectionMode
    • WeightedRandom:合格集内按 Weight 加权随机(UnityEngine.Random)。
    • Priority:取 Priority 最高;并列取候选列表首个(确定性)。
  • 根因校验:收集后若某 category==Attack 候选 rangeRadius<=0LogError(永远够不着的静默 bug 暴露); 若敌人配了 ApproachAttack 但候选集为空 → LogError

4.4 EnemyStatsSO 新增

public AttackSelectionMode attackSelectionMode = AttackSelectionMode.WeightedRandom;

AttackSelectionMode { WeightedRandom, Priority }BaseGames.Enemies.Abilities)。

4.5 ICombatant 扩展(EnemyBrainContext 实现)

bool HasEligibleAttack(); // = selector.HasEligible(enemy.IsPlayerVisible(), enemy.Movement.IsGrounded)
bool UseBestAttack();     // sel=selector.Select(...); return sel != null && sel.Execute();

着地读 EnemyBase.Movement.IsGroundedEnemyMovement 已暴露);如需可在 EnemyBase 加一层便捷只读属性。

  • 动态量(LOS/着地/选招模式)在 EnemyBrainContext 侧采集后委托选择器,选择器保持无敌人耦合。

4.6 IEnemyLocomotion 扩展

void Pursue(Vector2 target); // 寻路逼近原语:设 Approach 模式 + RunSpeed + 每帧 Nav.RequestMoveTo(target)
  • Vector2 驱动(不耦合 Transform),底层已防抖/受阻/NavLink。
  • 现有 Approach(Transform) 可委托 Pursue(target.position)

4.7 PerceptionStateMachine:新增 ApproachAttack 交战风格(opt-in

Config 新增:

public EngagementStyle Engagement = EngagementStyle.ChaseAbility; // ChaseAbility(现状) | ApproachAttack(新)
public string ApproachState = "Approach";
public string AttackState   = "Attack";

EngagementStyle == ApproachAttack 时,把原单一 Chase 能力节点换成两态子机(ChaseAbility 风格拓扑不变):

  • Idle/Patrol/AlertApproach When canChase(沿用现有 InChaseZone / 冷却门条件)
  • ApproachLocomotion 驱动)
    • OnEnter/TickLocomotion.Pursue(Sensor.LastKnown)
    • OnExitLocomotion.Stop()
    • Attack When Combat.HasEligibleAttack()
    • Rest When !InChaseZone && !InVisionZone
  • Attack(能力驱动,无固定 id
    • OnEnterLocomotion.Stop() + Combat.UseBestAttack()(攻击时暂停逼近)
    • Approach When !Combat.IsAbilityRunning()(打完回逼近重判/重选招)
    • Rest When !InChaseZone && !InVisionZone(打完且脱离感知区)
  • RestPatrol(不变)
  • 全局 DiedDeath(不变)

5. 边界与错误处理

情形 行为
无 NavLink 隔沟够不到玩家 走现有受阻逻辑 → 停在最近可达点等待(仍在 chase 区,Approach 态保持)
配了 ApproachAttack 但无攻击候选 / 招 rangeRadius<=0 Awake 显式 LogError(根因暴露,不静默兜底,CLAUDE.md §6)
玩家走出所有招射程 无合格攻击 → 持续逼近
攻击被受击打断 能力中断 → Attack.Tick 见 !IsAbilityRunning → 回 Approach
玩家脱离感知区 !InChaseZone && !InVisionZone → Rest → Patrol
关闭 Domain Reload 选择器无静态态;UnityEngine.Random 运行时安全(见 memory domain_reload_disabled

6. 测试

6.1 EditMode 单测(EnemyAttackSelector

用假候选(实现 IAttackCandidate+ 复用 Assets/Tests/EditMode/AI/Fakes 范式:

  • 合格过滤:冷却中 / 射程外 / 无 LOS(招 requiresLOS 且 hasLOS=false/ 需着地但腾空 → 各自被排除。
  • Priority 模式:多招合格取最高 Priority,并列取首个(确定性断言)。
  • WeightedRandom 模式:仅从合格集选(多次采样断言从不选到不合格招);单一合格招 → 必选它(确定性)。
  • 空候选 / 全不合格 → Select 返回 null、HasEligible 为 false。

6.2 手动 / PlayMode

  • 一个 EngagementStyle=ApproachAttack 的测试敌人(TestRoomA 内临时搭,或改 ChaoFeng): 跨平台缺口(跳 NavLink)逼近玩家 → 到某招射程停 → 选招攻击 → 打完重定位 → 玩家逃出感知区回巡逻。
  • 验证攻击射程 Gizmo 与实际触发一致。

7. 脚手架 / 规范合规(CLAUDE.md §2/§6

  • 无新资产类型(仅 EnemyAbilitySO/EnemyStatsSO 加字段)。
  • 能力/角色向导需按能力类型设 category(攻击类→Attack)与默认 weight/rangeRadius——列入实现计划。
  • 攻击能力在编辑器画自身射程 Gizmo(rangeRadius+rangeOffset,随朝向翻转),设计师摆招即见。
  • 自检工具(SOValidationRunner)加一条:category==Attack 必须 rangeRadius>0

8. 涉及文件(预估)

  • 改:EnemyAbilitySO.cs+category/weight/rangeRadius/rangeOffset)、EnemyStatsSO.cs+attackSelectionMode)、 EnemyAbilityBase.cs+InAttackRange/IAttackCandidate 实现)、EnemyBase.cs+选择器持有/IsGrounded 暴露)、 EnemyBrainContext.cs+HasEligibleAttack/UseBestAttack)、ICombatant.csIEnemyLocomotion.cs+Pursue)、 EnemyLocomotion.cs+Pursue 实现)、PerceptionStateMachine.cs+ApproachAttack 拓扑)。
  • 新:AbilityCategory.csAttackSelectionMode.csIAttackCandidate.csEnemyAttackSelector.csEnemyAttackSelectorTests.csEditMode)。
  • 编辑器:攻击能力射程 Gizmo;能力向导 category/range 设置;SOValidationRunner 校验项。