# 手动测试 06 · 玩家 FSM 与移动系统 > **测试类型**:Unity Editor 手动测试(Play Mode) > **覆盖模块**:`BaseGames.Player`、`BaseGames.Player.States` > **依赖组件**:`PlayerController`、`PlayerMovement`、`AnimancerComponent`、`PlayerStats` > **场景要求**:包含玩家 Prefab 的测试场景(TestRoom.unity),地面 Layer = `Ground` --- ## 快速工具 | 工具 | 用途 | 菜单路径 | |------|------|----------| | **Place Player** | 在场景中放置带完整组件的玩家 GameObject(PlayerController、PlayerStats、HurtBox 等) | `BaseGames → Scene → Place → Player` | | **Place Ground Platform** | 放置地面平台(BoxCollider2D,Layer=Ground) | `BaseGames → Scene → Place → Ground Platform` | | **Place Obstacle (Static)** | 放置静止障碍物(用于蹬墙跳测试墙),手动调整尺寸和位置 | `BaseGames → Scene → Place → Obstacle (Static)` | | **Place Room Camera** | 放置带 Cinemachine + RoomCamera + CinemachineConfiner2D 的房间相机 | `BaseGames → Scene → Place → Room Camera` | > **注意**:PlayModeDebugOverlay 已移除。Play Mode 运行时调试请使用 `Window → Analysis → EventBus Monitor`(若已集成)或 Console 过滤器查看状态机日志。 **典型工作流**: 1. `BaseGames → Scene → Place → Player` 放置玩家;`Place → Ground Platform` 放置地面;`Place → Room Camera` 放置相机。 2. `MT-PLAYER-04` 蹬墙跳测试:`Place → Obstacle (Static)` 在玩家右侧放置垂直墙体,调整位置和尺寸。 3. Play Mode 运行时通过 Inspector 直接修改 HP / Soul 字段,或者利用 Console + EventBus Monitor 观察状态机转换。 --- ## 目录 1. [场景搭建要求](#1-场景搭建要求) 2. [MT-PLAYER-01:基础移动状态转换](#mt-player-01基础移动状态转换) 3. [MT-PLAYER-02:跳跃与下落](#mt-player-02跳跃与下落) 4. [MT-PLAYER-03:冲刺(地面/空中)](#mt-player-03冲刺地面空中) 5. [MT-PLAYER-04:蹬墙滑行与蹬墙跳](#mt-player-04蹬墙滑行与蹬墙跳) 6. [MT-PLAYER-05:受击与死亡状态](#mt-player-05受击与死亡状态) 7. [MT-PLAYER-06:灵泉治疗](#mt-player-06灵泉治疗) 8. [MT-PLAYER-07:形态切换](#mt-player-07形态切换) 9. [MT-PLAYER-08:连击链完整性](#mt-player-08连击链完整性) 10. [MT-PLAYER-09:存档点复活流程](#mt-player-09存档点复活流程) --- ## 1. 场景搭建要求 在开始任何玩家测试前,确认测试场景包含以下元素: | 元素 | 说明 | ✓ | |------|------|---| | 玩家 Prefab | 挂有 `PlayerController`、`PlayerMovement`、`AnimancerComponent`、`PlayerStats`、`HurtBox` | ☐ | | 地面平台 | Layer = `Ground`,带 `Collider2D` | ☐ | | 墙壁 | 至少一面垂直墙壁,Layer = `Ground`,用于蹬墙测试 | ☐ | | InputReader SO | `PlayerController` 的 `_inputReader` 字段已绑定 `InputReaderSO` 资产 | ☐ | | PlayerStatsSO | `PlayerController` 的 `_statsConfig` 字段已绑定 | ☐ | | PlayerMovementConfigSO | `PlayerMovement` 的 `_config` 字段已绑定 | ☐ | | AnimationConfigSO | `PlayerController` 的 `_animConfig` 字段已绑定 | ☐ | | Physics2D Layer 矩阵 | Player、Ground Layer 碰撞开启 | ☐ | > **🔧 场景快速搭建** > > 1. `BaseGames → Scene → Place → Player` 放置玩家 + `Place → Ground Platform` 生成地面 + `Place → Room Camera` 生成相机 > 2. `MT-PLAYER-04` 蹬墙跳:`Place → Obstacle (Static)` 在玩家右侧放置垂直墙体,调整 Transform 尺寸。 > > **玩家 Prefab 手动组装(若无预制体)**: > > 1. 场景 `[Player]` 根节点下创建 `PLY_Player` GameObject > 2. 添加以下组件: > - `Rigidbody2D`(Dynamic,FreezeRotation,Interpolate) > - `CapsuleCollider2D`(尺寸参考美术图层,isTrigger=false) > - `AnimancerComponent`(从 Kybernetik.Animancer 包) > - `PlayerController`(`BaseGames.Player`) > - `PlayerMovement`(`BaseGames.Player`) > - `StatusEffectManager`(`BaseGames.Combat.StatusEffects`) > 3. 创建子 GameObject `HurtBox`(isTrigger=true,Layer=PlayerHurtBox)并添加 `HurtBox` 组件 > 4. 创建子 GameObject `HitBox_Sword`(isTrigger=true,Layer=PlayerHitBox),**初始 SetActive(false)**,添加 `HitBox` 组件 > 5. **Inspector 中绑定 SO 字段**: > - `PlayerController._inputReader` → 拖入 `InputReaderSO.asset`(`Assets/Data/Input/`) > - `PlayerController._statsConfig` → 拖入 `PlayerStats.asset`(`Assets/Data/Settings/`) > - `PlayerController._animConfig` → 拖入 `AnimationConfig.asset` > - `PlayerMovement._config` → 拖入 `PlayerMovementConfig.asset` > 6. 菜单 `BaseGames → Tools → Physics2D Layer Matrix → Check` / **Auto Fix** 确保层碰撞矩阵正确 > > **一键生成 SO 资产(若尚未创建)**: > 菜单 `BaseGames → Tools → Create Test Assets` 自动生成上述所有 SO 占位资产。 --- ## MT-PLAYER-01:基础移动状态转换 **目的**:验证 `IdleState → RunState → IdleState` 转换,Animancer FSM 动画同步。 ### 步骤 1. 进入 Play Mode 2. 玩家站立不动,观察动画 **预期**:播放 `Idle` 动画,无抖动。 3. 按住 **A/D**(或方向键左右)移动 **预期**: - 玩家水平位移,`PlayerMovement.IsGrounded == true` - 动画切换到 `Run`(Animancer 状态可在 Animator 窗口观察) 4. 松开移动键 **预期**: - 玩家减速到静止(`Deceleration` 生效) - 动画切回 `Idle` 5. 按住移动并朝反方向转向 **预期**: - `SpriteRenderer.flipX` 改变(或 Transform.localScale.x 取反) - 朝向立即响应,无延迟 | 检查点 | 期望 | ✓ | |--------|------|---| | Idle 动画播放 | 无卡帧,无 A-pose | ☐ | | Run 动画播放 | 移动速度与动画速率匹配 | ☐ | | 朝向正确 | 面向移动方向 | ☐ | | Console 无 Error | 0 个红色 Error | ☐ | --- ## MT-PLAYER-02:跳跃与下落 **目的**:验证跳跃物理弧线、土狼时间(Coyote Time)、可变跳跃高度(提前松键降低高度)。 ### 步骤 **步骤 A:普通跳跃** 1. 地面上按 **Space**(跳跃键) 2. 观察轨迹 **预期**:抛物线跳跃弧线,高度约为 `PlayerMovementConfigSO.JumpForce / Gravity`。 **步骤 B:可变跳跃** 1. 跳跃后立即松开 **Space** **预期**:玩家跳跃高度降低(`PlayerMovement` 在松键时减少向上速度),动画保持 Jump → Fall 正确过渡。 **步骤 C:土狼时间(Coyote Time)** 1. 让玩家走到平台边缘,**走出平台悬空** 2. 在 `CoyoteTime`(约 0.15s)内按 **Space** **预期**:跳跃生效(玩家可以从空中起跳),而非立即下落。 **步骤 D:下落加速** 1. 跳跃到最高点后松键,等待自然下落 **预期**:下落速度比上升时快(`FallMultiplier` 生效),手感有重量感。 | 检查点 | 期望 | ✓ | |--------|------|---| | Jump 动画 | 起跳时播放 Jump 动画 | ☐ | | Fall 动画 | 下落时切换 Fall 动画 | ☐ | | 土狼时间有效 | 走出平台后短时内仍可跳跃 | ☐ | | 落地动画 | 落地瞬间切回 Idle/Run(无卡帧) | ☐ | --- ## MT-PLAYER-03:冲刺(地面/空中) **目的**:验证 `DashState`(地面)和 `AerialDashState`(空中)的无敌帧、冷却、次数限制。 ### 步骤 **步骤 A:地面冲刺** 1. 地面上按 **Left Shift**(冲刺键) **预期**: - 玩家水平快速位移(距离约 `dashDistance`) - 冲刺期间播放 Dash 动画 - 冲刺期间受到敌人攻击**不触发** `HurtState`(无敌帧) - 冲刺结束后自然过渡 `IdleState` 或 `RunState` **步骤 B:空中冲刺** 1. 跳跃后按 **Left Shift** **预期**: - 空中水平冲刺 - 消耗 `_aerialDashCount`(通常 1 次) - 落地后次数重置 **步骤 C:空中冲刺次数用尽** 1. 空中冲刺 1 次(次数用尽) 2. 再次按冲刺键 **预期**:冲刺不触发(次数为 0 时无反应或有 UI 提示)。 **步骤 D:冲刺冷却** 1. 地面冲刺后立即再次按冲刺键 **预期**:冷却期间(`dashCooldown`)无法再次冲刺。 | 检查点 | 期望 | ✓ | |--------|------|---| | 地面冲刺位移 | 快速平移 `dashDistance` 距离 | ☐ | | 无敌帧 | 冲刺期间不受伤 | ☐ | | 空中冲刺次数 | 次数耗尽后无法再冲 | ☐ | | 落地重置次数 | 落地后次数恢复 | ☐ | --- ## MT-PLAYER-04:蹬墙滑行与蹬墙跳 **目的**:验证 `WallSlideState` 减速下滑、`WallJumpState` 弹离逻辑。 ### 步骤 **步骤 A:蹬墙滑行** 1. 跳跃接近垂直墙壁,按住朝向墙壁的方向键 2. 玩家贴墙后观察 **预期**: - 下落速度从自由落体减缓到 `wallSlideSpeed` - 播放 `WallSlide` 动画 **步骤 B:蹬墙跳** 1. WallSlide 状态下按 **Space** **预期**: - 玩家弹离墙壁(方向取反) - 施加 `wallJumpForce`(包含 X/Y 两个分量) - 播放 `WallJump` 动画 **步骤 C:离墙后方向控制** 1. 蹬墙跳后立即按反向方向键(远离墙壁方向) **预期**:玩家可控制方向(不被强制锁定朝向太久)。 | 检查点 | 期望 | ✓ | |--------|------|---| | WallSlide 减速 | 贴墙后下落速度明显降低 | ☐ | | WallJump 弹离 | 弹离墙壁,方向翻转 | ☐ | | 跳跃后可控 | `wallJumpLockTime` 后方向键恢复控制 | ☐ | --- ## MT-PLAYER-05:受击与死亡状态 **目的**:验证 `HurtState` 硬直和 `DeadState` 死亡冻结。 ### 步骤(需要有能攻击玩家的敌人或调试伤害源) **步骤 A:受击硬直** 1. 让敌人攻击玩家(HP 不为 0) 2. 观察玩家反应 **预期**: - 受击动画播放 - 硬直期间(`hurtDuration`)玩家无法输入 - 硬直结束后自动恢复 `IdleState` - HP 减少,HUD HP 条更新 **步骤 B:死亡** 1. 让玩家 HP 降至 0 **预期**: - 死亡动画播放 - `Rigidbody2D.constraints` 冻结(角色不再受物理影响下移) - `_onPlayerDied` 事件触发(EventBusMonitor 可见) - 死亡屏幕 UI 出现 | 检查点 | 期望 | ✓ | |--------|------|---| | HurtState 动画 | 受击动画播放 `hurtDuration` 秒 | ☐ | | 硬直期间无法输入 | 按攻击键无反应 | ☐ | | HP 减少 | HUD HP 条正确减少 | ☐ | | DeadState 冻结 | 死亡后角色不再移动 | ☐ | | 死亡 UI | DeathScreen 显示 | ☐ | --- ## MT-PLAYER-06:灵泉治疗 **目的**:验证 `SpringState` 治疗动画、灵泉消耗、打断限制。 ### 步骤 1. 确保玩家有灵泉(`SpringCount > 0`)且 HP 未满 2. 按 **治疗键**(默认参考 `InputReaderSO` 配置) **预期**: - 播放治疗动画(`Spring` 动画片段) - 治疗动画期间不可被打断(敌人攻击触发 `HurtState` 优先级低于 `SpringState` 的硬直保护期) - 动画结束后 HP 增加(`springHealAmount`) - `SpringCount - 1`,HUD 灵泉图标减少 3. 灵泉耗尽后再按治疗键 **预期**:无反应(`SpringCount == 0` 时不进入 `SpringState`)。 | 检查点 | 期望 | ✓ | |--------|------|---| | 治疗动画 | Spring 动画播放完整 | ☐ | | HP 回复 | HP 增加 `springHealAmount` | ☐ | | 灵泉数量 -1 | HUD 灵泉图标减少 | ☐ | | 灵泉耗尽 | `SpringCount == 0` 时无法治疗 | ☐ | --- ## MT-PLAYER-07:形态切换 **目的**:验证 `FormController` 三形态(Sky/Earth/Death)切换正确更新外观和事件。 ### 步骤 1. 进入 Play Mode 2. 打开 `Window → BaseGames → EventBus Monitor` 3. 按形态切换键(循环切换 Sky → Earth → Death → Sky) **预期(每次切换):** | 检查点 | 期望 | ✓ | |--------|------|---| | 调色板更新 | `SpriteRenderer` 颜色/材质变化(Palette Swap) | ☐ | | `EVT_FormChanged` 触发 | EventBusMonitor 显示频道触发 | ☐ | | `EVT_SkillSetChanged` 触发 | 技能组随形态更新 | ☐ | | HUD 形态图标 | 形态图标切换到对应形态 | ☐ | | 武器伤害来源刷新 | 不同形态的武器 SO 绑定正确 | ☐ | 4. 切换到 Earth 形态后存档(与存档点交互) 5. 退出并重新进入 Play Mode,加载存档 **预期**:重载后当前形态仍为 Earth(`PlayerSaveData.ActiveFormId` 持久化)。 --- ## MT-PLAYER-08:连击链完整性 **目的**:验证 3 段连击链(Attack1 → Attack2 → Attack3)、超时重置、空中攻击、下劈/上劈。 ### 步骤 **步骤 A:3 段地面连击** 1. 地面站立状态,连续快速按 3 次攻击键(间隔在 Combo 窗口内) **预期**: - 播放 Attack1 → Attack2 → Attack3 三段动画 - 第 3 段结束后返回 Idle,不可延续第 4 段 **步骤 B:连击超时重置** 1. 按 1 次攻击后等待超时(约 1-2 秒) 2. 再次按攻击键 **预期**:从 Attack1 重新开始(Combo 计数重置)。 **步骤 C:空中攻击** 1. 跳跃后按攻击键 **预期**:播放 `AirAttack` 动画,HitBox 激活(正前方)。 **步骤 D:下劈** 1. 跳跃后按 **向下 + 攻击** **预期**:播放 `DownAttack` 动画;若正下方有敌人,命中后玩家向上反弹(`trampolineForce`)。 **步骤 E:上劈** 1. 地面上按 **向上 + 攻击** **预期**:播放 `UpAttack` 动画,HitBox 激活(正上方)。 | 检查点 | 期望 | ✓ | |--------|------|---| | 3 段连击 | Attack1→Attack2→Attack3 顺序正确 | ☐ | | 超时重置 | 超时后从 Attack1 重新开始 | ☐ | | 空中攻击 | AirAttack 动画,前方 HitBox | ☐ | | 下劈反弹 | 下方命中后向上弹起 | ☐ | | 上劈 | UpAttack 动画,上方 HitBox | ☐ | | 命中灵力增加 | 攻击命中敌人后灵力条增加 | ☐ | --- ## MT-PLAYER-09:存档点复活流程 **目的**:端到端验证存档/死亡/复活完整链路。 ### 步骤 1. 进入 Play Mode,找到场景中的 `SavePoint`(存档点) 2. 走到存档点附近,按交互键(默认 E) 3. 确认 Console 出现 `[SaveManager] 存档成功` 日志 4. 让玩家死亡(HP 降至 0) 5. 在死亡屏幕点击 "重试"(或按确认键) **预期**: - 玩家复活在存档点位置 - HP 和灵力恢复满值 - 死亡时丢失的 Geo 以 `DeathShade` 形式出现在死亡地点 6. 走到 `DeathShade` 并与其交互 **预期**: - 回收丢失的 Geo - `DeathShade` 消失 - Geo 数量正确增加 | 检查点 | 期望 | ✓ | |--------|------|---| | 存档触发 | SavePoint 交互后存档成功 | ☐ | | 死亡屏幕 | HP 归零后显示 DeathScreen | ☐ | | 复活位置 | 复活在存档点,不是死亡地点 | ☐ | | HP 满值 | 复活后 HP = MaxHP | ☐ | | DeathShade 出现 | 死亡地点有 DeathShade | ☐ | | Geo 回收 | 与 DeathShade 交互回收 Geo | ☐ |