# 手动测试 08 · 敌人系统 > **测试类型**:Unity Editor 手动测试(Play Mode) > **覆盖模块**:`BaseGames.Enemies`、`BaseGames.Enemies.AI`、`BaseGames.Enemies.Navigation` > **依赖组件**:`EnemyBase`、`EnemyCombat`、`EnemyMovement`、`BehaviorDesigner`、`PathBerserker2d` > **场景要求**:已烘焙 NavSurface,至少包含近战/远程/飞行三种敌人各一只 --- ## 快速工具 | 工具 | 用途 | 菜单路径 | |------|------|----------| | **Place Enemy (Basic)** | 放置带 EnemyBase、EnemyStats、HurtBox、HitBox_Body 的基础敌人;多次调用可摆放多种变体,然后手动调整组件 | `BaseGames → Scene → Place → Enemy (Basic)` | | **Place Nav Surface** | 在场景中放置 PathBerserker2d NavSurface 对象 | `BaseGames → Scene → Place → Nav Surface` | | **Place Ground Platform** | 放置地面平台(Layer=Ground) | `BaseGames → Scene → Place → Ground Platform` | > **NavSurface 烘焙**:在 Inspector 中找到 `NavSurface` 组件,点击 **Bake** 按钮(无对应菜单命令)。 > **注意**:PlayModeDebugOverlay 已移除。运行时状态效果测试请通过配置带效果的敌人攻击,或代码调用 `StatusEffectManager.Apply()`。 **典型工作流**: 1. 测试前:`Place → Ground Platform` 生成地面 + `Place → Enemy (Basic)` 放置近战 / 远程 / 飞行三种敌人(多次调用,手动调整组件和配置)。 2. **Add Enemy Variants**:`Place → Enemy (Basic)` 多次,分别调整 `EnemyStats` 和行为树为远程 / 飞行变体。 3. Inspector NavSurface → **Bake** 烘焙寻路数据(相比手动查找 Inspector 更直接)。 4. 状态效果测试(`MT-ENEMY-04`):配置带毒/燃烧效果的敌人攻击,或通过代码调用;观察 VFX 变化和 Console 事件。 --- ## 目录 1. [NavSurface 烘焙检查](#1-navsurface-烘焙检查) 2. [MT-ENEMY-01:近战敌人 AI 基础行为](#mt-enemy-01近战敌人-ai-基础行为) 3. [MT-ENEMY-02:远程敌人(RangedEnemy)](#mt-enemy-02远程敌人rangedenemy) 4. [MT-ENEMY-03:飞行敌人(FlyingEnemy)](#mt-enemy-03飞行敌人flyingenemy) 5. [MT-ENEMY-04:敌人霸体与击退](#mt-enemy-04敌人霸体与击退) 6. [MT-ENEMY-05:敌人死亡与掉落](#mt-enemy-05敌人死亡与掉落) 7. [MT-ENEMY-06:敌人配额管理](#mt-enemy-06敌人配额管理) 8. [MT-ENEMY-07:Boss 战切换流程](#mt-enemy-07boss-战切换流程) --- ## 1. NavSurface 烘焙检查 PathBerserker2d 的寻路**完全依赖烘焙数据**,未烘焙时敌人原地站立无响应。 > **🔧 敌人场景快速搭建** > > 1. `BaseGames → Scene → Place → Ground Platform` 生成地面 + `Place → Player` 放置玩家(若尚未搭建) > 2. `BaseGames → Scene → Place → Enemy (Basic)` 多次调用,放置近战 / 远程 / 飞行三种敌人: > - **MeleeEnemy**:保持默认配置(EnemyBase + EnemyStats + EnemyMovement + HurtBox) > - **RangedEnemy**:手动添加 `ShootPoint` 子 GameObject,调整 EnemyStats 为远程变体 SO > - **FlyingEnemy**:手动修改 Rigidbody2D → Kinematic,`gravityScale = 0` > > 三只敌人均需在 Inspector 中绑定 Behavior Designer 行为树资产(`BehaviorTree._externalBehavior` 字段) > 3. Inspector NavSurface 组件 → 点击 **Bake** 烘焙近战 + 远程敌人的寻路数据 > > **注意**:`FlyingEnemy` 无需 NavSurface 寻路;`RangedEnemy` 需要 NavSurface 才能进行保距移动。 **检查步骤**: 1. 在测试场景中选中挂有 `NavSurface` 组件的 GameObject 2. Inspector 中找到 `NavSurface` 组件,点击 **Bake** 3. Scene 视图中地面显示**蓝绿色半透明网格** → 烘焙成功 **提示**:若 Gizmo 不可见,点击 Scene 视图右上角 `Gizmos` → 确认 PathBerserker2d 相关项已勾选。 --- ## MT-ENEMY-01:近战敌人 AI 基础行为 **目的**:验证近战敌人的巡逻 → 追击 → 攻击 → 返回 Behavior Designer 行为树。 > **🔧 前置检查** > - `Place → Enemy (Basic)` 已放置 `MeleeEnemy` 并挂载所有必要组件 > - Inspector 中 `BehaviorTree._externalBehavior` 字段已绑定行为树资产(手动拖入) > - NavSurface 已烘焙(Inspector → Bake 按钮) ### 步骤 **步骤 A:巡逻行为** 1. 进入 Play Mode 2. 玩家保持在敌人**视野范围外**(超过 `detectionRange`) 3. 观察敌人 **预期**: - 敌人在巡逻点之间来回移动 - 播放 `Walk`/`Patrol` 动画 - Console 无 PathBerserker2d 寻路错误 **步骤 B:追击行为** 1. 玩家进入敌人视野(`detectionRange` 内且无遮挡物) 2. 观察敌人反应 **预期**: - 敌人停止巡逻,转向玩家方向 - 播放 `Run`/`Chase` 动画 - 以最优路径追击玩家(PathBerserker2d 动态路径) **步骤 C:攻击行为** 1. 玩家进入敌人攻击范围(`attackRange` 内) 2. 观察敌人攻击 **预期**: - 播放攻击动画(Behavior Designer 行为树触发攻击节点) - `EnemyCombat.HitBox` 激活,若玩家在判定范围内触发伤害 - 攻击后进入冷却(`attackCooldown`) **步骤 D:掉失目标后返回** 1. 玩家跑出敌人追击范围(`chaseRange` 外) 2. 观察敌人 **预期**: - 敌人停止追击,返回初始位置或巡逻路径 - 返回后恢复巡逻动画 | 检查点 | 期望 | ✓ | |--------|------|---| | 巡逻动画 | 视野外敌人来回巡逻 | ☐ | | 追击切换 | 玩家进入视野后立即追击 | ☐ | | 攻击命中 | 攻击范围内玩家 HP 减少 | ☐ | | 返回巡逻 | 丢失目标后返回初始位置 | ☐ | | 无寻路错误 | Console 无 PathBerserker2d 相关 Error | ☐ | --- ## MT-ENEMY-02:远程敌人(RangedEnemy) **目的**:验证 `RangedEnemy` 的投射物发射、移动闪避、最优射击位置。 > **🔧 前置检查** > - `BaseGames → Scene → Place → Enemy (Basic)` 已放置 `RangedEnemy`(调整 EnemyStats 为远程变体,位置 x=8) > - Inspector 中为 `RangedEnemy` 配置行为树资产(保距 + LOS 检测 + 发射节点) > - `RangedEnemy._shootPoint` 子 Transform 已手动添加 > - 场景有静止障碍物(`Place → Obstacle (Static)`)以便观察子弹碰撞 ### 步骤 1. 确认测试场景中有 `RangedEnemy` 实例(挂有 `EnemyCombat` 组件) 2. 进入 Play Mode,玩家接近远程敌人 **步骤 A:保持距离** **预期**:远程敌人尝试保持在 `preferredDistance` 附近,玩家靠近时后退。 **步骤 B:发射投射物** **预期**: - 在 `shootRange` 内发射投射物(`LinearProjectile` 或 `ArcProjectile`) - 投射物从 `_shootPoint` Transform 位置发出 - 命中玩家触发伤害 **步骤 C:LOS(视线)检测** 1. 让玩家躲在墙壁后面(无视线) 2. 观察远程敌人是否仍然发射 **预期**:无视线时,敌人停止发射(LOS 检测生效)。 | 检查点 | 期望 | ✓ | |--------|------|---| | 保持距离 | 玩家靠近时后退 | ☐ | | 发射投射物 | 视线内发射子弹命中玩家 | ☐ | | LOS 遮挡 | 墙后无视线时停止发射 | ☐ | --- ## MT-ENEMY-03:飞行敌人(FlyingEnemy) **目的**:验证 `FlyingEnemy` 的空中移动、俯冲攻击、不受地面 NavSurface 约束。 > **🔧 前置检查** > - `BaseGames → Scene → Place → Enemy (Basic)` 已放置 `FlyingEnemy`(位置 (3,5,0)) > - `Rigidbody2D.gravityScale = 0`,`bodyType = Kinematic`(手动在 Inspector 设置) > - 配置行为树资产(直线追击 + 俯冲攻击,无需 NavSurface) ### 步骤 1. 确认场景有 `FlyingEnemy` 实例(Kinematic RB,gravityScale=0) 2. 进入 Play Mode **步骤 A:空中悬停/巡逻** **预期**: - 飞行敌人在空中自由移动,不受地形限制 - 可以穿越平台上下(不像地面敌人需要寻路绕路) **步骤 B:俯冲攻击** **预期**: - 锁定玩家后俯冲攻击 - 攻击后飞回起始高度,继续攻击循环 **步骤 C:不被地面碰撞阻挡** 1. 让飞行敌人在追击路径中有地形障碍 **预期**:飞行敌人从障碍物上方飞过(不被地面碰撞体阻挡)。 | 检查点 | 期望 | ✓ | |--------|------|---| | 空中自由移动 | 不受地面寻路限制 | ☐ | | 俯冲攻击 | 正确识别玩家位置并俯冲 | ☐ | | 绕过地形 | 从障碍上方飞过 | ☐ | --- ## MT-ENEMY-04:敌人霸体与击退 **目的**:验证 `EnemyPoiseComponent` 在普通攻击下保持动画,重攻击才打断。 ### 步骤 **步骤 A:普通连击不打断** 1. 对有较高霸体的敌人进行连击 **预期**:若连击伤害低于霸体 `breakLevel`,敌人动画**不被打断**,继续执行 AI 行为。 **步骤 B:重攻击打断** 1. 对同一敌人使用 `BreakLevel` 高的攻击(如下劈 `DownAttack` 或特殊技能) **预期**:敌人进入受击硬直,AI 行为树暂停。 **步骤 C:击退效果** 1. 攻击有击退(`knockbackForce > 0`)的攻击 **预期**:敌人被击退一定距离(`knockbackForce` 方向与大小),不会穿墙。 | 检查点 | 期望 | ✓ | |--------|------|---| | 普通攻击不打断 | 霸体保护时 AI 行为继续 | ☐ | | 重攻击打断 | BreakLevel > Poise 时触发硬直 | ☐ | | 击退物理 | 被击退后不穿越地形 | ☐ | --- ## MT-ENEMY-05:敌人死亡与掉落 **目的**:验证敌人 HP 归零后的死亡流程、Geo 掉落、`LootResolver`。 ### 步骤 1. 将敌人 HP 降至 0 **预期**: - 死亡动画播放 - 死亡动画结束后 GameObject 从场景移除(或 `SetActive(false)` 归还对象池) - `LootTableSO` 根据权重随机掉落 Geo 或其他物品 2. 在掉落的 Geo 上移动玩家 **预期**:Geo 被拾取,玩家 `CurrentGeo` 增加,HUD Geo 数量更新。 3. 重新进入场景(房间切换后返回) **预期**:根据 `WorldStateRegistry` 配置,死亡敌人是否重生(可配置的一次性/可重生区别)。 | 检查点 | 期望 | ✓ | |--------|------|---| | 死亡动画 | HP 归零后播放死亡动画 | ☐ | | 清理 | 动画结束后 GameObject 消失或归池 | ☐ | | Geo 掉落 | 掉落 Geo 可拾取,HUD 更新 | ☐ | | LootTable | 掉落物符合权重概率 | ☐ | --- ## MT-ENEMY-06:敌人配额管理 **目的**:验证 `EnemyQuotaManager` 限制同屏激活敌人数量,防止性能劣化。 ### 步骤 1. 打开 Inspector,找到场景中的 `EnemyQuotaManager` 2. 记录当前 `maxActiveEnemies` 配置值(如 8) 3. 进入 Play Mode,触发大量敌人(通过多个 EnemySpawner) **预期**: - 同屏激活的敌人不超过 `maxActiveEnemies` - 超出配额的敌人保持待机(Deactivate 或等待) - 当已激活敌人死亡后,待机敌人激活补充 | 检查点 | 期望 | ✓ | |--------|------|---| | 不超过配额 | 激活敌人数量 ≤ maxActiveEnemies | ☐ | | 死亡后补充 | 敌人死亡后待机敌人激活 | ☐ | --- ## MT-ENEMY-07:Boss 战切换流程 **目的**:验证 Boss 战触发的 GameState 切换(Gameplay → BossFight)、Boss 血条 UI 显示。 ### 步骤 1. 进入有 Boss 触发器的场景或房间 2. 玩家进入 Boss 房间触发器 **预期**: - `EVT_BossFightStarted` 事件触发(EventBusMonitor 可见) - `GameManager` 状态切换到 `BossFight` - Boss HP 血条 UI 出现(大型 HP 条 + Boss 名称文字) - 背景音乐切换为 Boss 战曲目 3. 将 Boss HP 降至 0 **预期**: - `EVT_BossFightEnded` 事件触发(`victory = true`) - GameManager 切回 `Gameplay` 状态 - Boss 血条 UI 隐藏 - 胜利 VFX/音效播放 | 检查点 | 期望 | ✓ | |--------|------|---| | Boss 战触发 | 进入触发器后 GameState = BossFight | ☐ | | Boss HP 条 | Boss 血条 UI 正确显示 | ☐ | | BGM 切换 | 战斗音乐正确播放 | ☐ | | Boss 死亡 | 胜利后状态恢复 Gameplay | ☐ | | BossProgressTracker | Console 中 Boss 击败状态记录 | ☐ |