425 lines
14 KiB
Markdown
425 lines
14 KiB
Markdown
# 手动测试 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 | ☐ |
|