938 lines
33 KiB
Markdown
938 lines
33 KiB
Markdown
# 手动测试 13 · 玩家能力与技能系统
|
||
|
||
> **测试类型**:Unity Editor 手动测试(Play Mode)
|
||
> **覆盖模块**:`BaseGames.Player`、`BaseGames.Player.States`、`BaseGames.Skills`
|
||
> **依赖组件**:`PlayerController`、`PlayerMovement`、`PlayerStats`、`PlayerCombat`、`FormController`、`SkillManager`、`SpringSystem`、`ParrySystem`
|
||
> **场景要求**:包含玩家 Prefab 的测试场景,至少一块地面平台、一面垂直墙、若干测试用敌人
|
||
|
||
---
|
||
|
||
## 快速工具
|
||
|
||
| 工具 | 用途 | 菜单路径 |
|
||
|------|------|----------|
|
||
| **Place Player** | 放置完整玩家 GameObject | `BaseGames → Scene → Place → Player` |
|
||
| **Place Ground Platform** | 放置地面平台(Layer=Ground) | `BaseGames → Scene → Place → Ground Platform` |
|
||
| **Place Obstacle (Static)** | 放置垂直墙壁(用于抓墙测试) | `BaseGames → Scene → Place → Obstacle (Static)` |
|
||
| **Place Test Enemy** | 放置静止测试用敌人(带 HurtBox) | `BaseGames → Scene → Place → Test Enemy` |
|
||
| **Place Room Camera** | 放置 Cinemachine 相机 | `BaseGames → Scene → Place → Room Camera` |
|
||
|
||
---
|
||
|
||
## 场景搭建要求
|
||
|
||
在运行所有测试前,请确认以下清单:
|
||
|
||
| 检查项 | 说明 | ✓ |
|
||
|--------|------|---|
|
||
| 玩家 Prefab 已放置 | 带 `PlayerController`、`PlayerMovement`、`PlayerStats`、`PlayerCombat`、`FormController`、`SkillManager`、`SpringSystem`、`ParrySystem`、`HurtBox`、`AnimancerComponent`、`WeaponManager` | ☐ |
|
||
| 地面平台 | Layer = `Ground`,至少一块宽平台 | ☐ |
|
||
| 垂直墙壁 | Layer = `Ground`,高度 ≥ 4 格,用于抓墙/蹬墙跳测试 | ☐ |
|
||
| 测试用敌人 | 带 `HurtBox`、`EnemyBase`,初始静止,用于攻击/Pogo/灵泉充能测试 | ☐ |
|
||
| InputReaderSO | 已绑定 `_inputReader` 字段 | ☐ |
|
||
| PlayerMovementConfigSO | 已绑定,含跳跃/冲刺/墙壁相关参数 | ☐ |
|
||
| PlayerAnimationConfigSO | 已绑定,含所有动画 Clip 资产 | ☐ |
|
||
| FormConfigSO | 已绑定到 `FormController._config`,三形态 SO 已配置 | ☐ |
|
||
| SkillManager._formSkillSets | Inspector 中已配置三个 `FormSkillSet`,每项指定对应形态的三个技能 SO | ☐ |
|
||
| SkillManager._formController | Inspector 中已绑定 `FormController` 引用 | ☐ |
|
||
| Physics2D Layer 矩阵 | PlayerHitBox ↔ EnemyHurtBox、Player ↔ Ground 碰撞均已开启 | ☐ |
|
||
| Console 无红色 Error | 进入 Play Mode 前 Error = 0 | ☐ |
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [MT-ABILITY-01:地面三连击与攻击缓冲](#mt-ability-01地面三连击与攻击缓冲)
|
||
2. [MT-ABILITY-02:上劈(地面 / 空中)](#mt-ability-02上劈地面--空中)
|
||
3. [MT-ABILITY-03:下劈与 Pogo 弹起](#mt-ability-03下劈与-pogo-弹起)
|
||
4. [MT-ABILITY-04:空中水平攻击](#mt-ability-04空中水平攻击)
|
||
5. [MT-ABILITY-05:攻击取消窗口](#mt-ability-05攻击取消窗口)
|
||
6. [MT-ABILITY-06:抓墙与高度记忆机制](#mt-ability-06抓墙与高度记忆机制)
|
||
7. [MT-ABILITY-07:蹬墙跳(背墙跳 / 对墙跳)](#mt-ability-07蹬墙跳背墙跳--对墙跳)
|
||
8. [MT-ABILITY-08:二段跳](#mt-ability-08二段跳)
|
||
9. [MT-ABILITY-09:无敌冲刺](#mt-ability-09无敌冲刺)
|
||
10. [MT-ABILITY-10:三形态切换](#mt-ability-10三形态切换)
|
||
11. [MT-ABILITY-11:三套资源系统](#mt-ability-11三套资源系统)
|
||
12. [MT-ABILITY-12:灵泉使用](#mt-ability-12灵泉使用)
|
||
13. [MT-ABILITY-13:灵泉充能(击杀积累)](#mt-ability-13灵泉充能击杀积累)
|
||
14. [MT-ABILITY-14:魂技能施放](#mt-ability-14魂技能施放)
|
||
15. [MT-ABILITY-15:魄技能施放(技能 1 / 2)](#mt-ability-15魄技能施放技能-1--2)
|
||
16. [MT-ABILITY-16:弹反系统](#mt-ability-16弹反系统)
|
||
|
||
---
|
||
|
||
## MT-ABILITY-01:地面三连击与攻击缓冲
|
||
|
||
**目的**:验证地面三段连击的前摇/有效帧/后摇三阶段、HitBox 激活时序、攻击缓冲机制。
|
||
|
||
### 前置条件
|
||
|
||
- 玩家站于地面
|
||
- 在玩家攻击范围内放置测试用敌人
|
||
|
||
### 步骤 A:三段连击完整触发
|
||
|
||
1. 进入 Play Mode
|
||
2. 玩家站立地面,按 **Attack 键**(默认 `J`)
|
||
|
||
**预期**:
|
||
- 播放第 1 段攻击动画(`AnimationConfigSO.GroundAttacks[0]`)
|
||
- 动画前摇结束时 HitBox 激活(可在 Scene 视图 Gizmos 中观察 HitBox 矩形出现)
|
||
- 命中敌人后敌人 HP 减少,玩家方向上有微小横向反嵈位移
|
||
|
||
3. 动画进入后摇阶段后(有效帧结束),再次按 **Attack 键**
|
||
|
||
**预期**:
|
||
- 无缝衔接第 2 段攻击动画(`GroundAttacks[1]`)
|
||
- 第 2 段 HitBox 重新激活
|
||
|
||
4. 同样方式触发第 3 段
|
||
|
||
**预期**:
|
||
- 播放 `GroundAttacks[2]`,第三段攻击完成后动画自然结束
|
||
- 玩家返回 `IdleState` 或 `RunState`
|
||
|
||
### 步骤 B:攻击缓冲(后摇内预输入)
|
||
|
||
1. 按下 **Attack 键** 触发第 1 段攻击
|
||
2. **在后摇期间**(有效帧结束前后)提前再次按 **Attack 键**
|
||
|
||
**预期**:
|
||
- 第 1 段后摇结束后**自动**衔接第 2 段,无需再次按键
|
||
- 缓冲窗口内的输入被 `InputBuffer` 记录并消耗
|
||
|
||
### 步骤 C:攻击中断复位
|
||
|
||
1. 第 1 段攻击完成后等待后摇完全结束(约 1.5 秒不操作)
|
||
2. 再次按 **Attack 键**
|
||
|
||
**预期**:
|
||
- 从第 1 段重新开始,而非继续第 2 段(连击计数已重置)
|
||
|
||
| 检查点 | 期望 | ✓ |
|
||
|--------|------|---|
|
||
| 三段动画各不相同 | 三段 Clip 依次切换 | ☐ |
|
||
| HitBox 仅在有效帧激活 | 前摇期间无碰撞,Gizmos 不显示 HitBox | ☐ |
|
||
| 命中反嵈 | 命中敌人时玩家微小位移(打击感) | ☐ |
|
||
| 攻击缓冲生效 | 后摇内按键可自动续接下一段 | ☐ |
|
||
| 连击计数超时重置 | 久未续接后从第 1 段重新开始 | ☐ |
|
||
| Console 无 Error | 0 个红色 Error | ☐ |
|
||
|
||
---
|
||
|
||
## MT-ABILITY-02:上劈(地面 / 空中)
|
||
|
||
**目的**:验证上劈的组合键判定(Move Y 轴正向 + Attack)、向上 HitBox 激活、空中向下反嵈。
|
||
|
||
### 前置条件
|
||
|
||
- 在玩家正上方位置放置测试用敌人(调高 Y 坐标)
|
||
|
||
### 步骤 A:地面上劈
|
||
|
||
1. 玩家站立地面
|
||
2. 按住 **Move 上方向键**(`W` 或上方向键,Y 轴正向),同时按 **Attack 键**
|
||
|
||
**预期**:
|
||
- 触发上劈动画(`AnimationConfigSO.UpAttack` 对应 Clip)
|
||
- HitBox 在角色**正上方**激活(Scene 视图 Gizmos 可见)
|
||
- 命中正上方敌人,敌人 HP 减少
|
||
- 玩家仍停留地面,无明显上移(地面上劈无空中反嵈)
|
||
|
||
### 步骤 B:空中上劈与向下反嵈
|
||
|
||
1. 跳跃至空中
|
||
2. 在空中按住 **Move 上方向键** + **Attack 键**
|
||
|
||
**预期**:
|
||
- 触发上劈动画
|
||
- **玩家 Y 轴速度减少约 3(向下反嵈)**,可在 Inspector → `Rigidbody2D.velocity.y` 观察到短暂下移
|
||
- 上劈动画结束后进入 `FallState`
|
||
- 命中敌人后敌人 HP 减少
|
||
|
||
### 步骤 C:Move Y 阈值边界
|
||
|
||
1. 空中仅按轻推上方向(Y 轴输入 < 0.5),同时按 **Attack 键**
|
||
|
||
**预期**:
|
||
- 触发普通空中水平攻击,而非上劈(Y 阈值未达到 0.5 不判定为上劈)
|
||
|
||
| 检查点 | 期望 | ✓ |
|
||
|--------|------|---|
|
||
| 地面上劈 HitBox 在上方 | Scene Gizmos 可见上方碰撞盒 | ☐ |
|
||
| 空中上劈向下反嵈 | velocity.y 降低约 3,玩家微向下位移 | ☐ |
|
||
| Y 轴阈值正确 | 轻推不触发上劈 | ☐ |
|
||
| 动画正确 | 播放上劈专属 Clip | ☐ |
|
||
|
||
---
|
||
|
||
## MT-ABILITY-03:下劈与 Pogo 弹起
|
||
|
||
**目的**:验证下劈仅限空中触发、向下速度施加、命中 Pogo 弹起机制(重置空中能力)、未命中继续下落。
|
||
|
||
### 前置条件
|
||
|
||
- 玩家位于测试敌人正上方(高度 ≥ 3 格)
|
||
- 测试敌人带有 `HurtBox` 组件
|
||
|
||
### 步骤 A:空中下劈命中 Pogo
|
||
|
||
1. 跳跃至测试敌人正上方
|
||
2. 按住 **Move 下方向键**(`S` 或下方向键,Y 轴负向),同时按 **Attack 键**
|
||
|
||
**预期**:
|
||
- 触发下劈动画(`AnimationConfigSO.DownAttack` 对应 Clip)
|
||
- 玩家 Y 轴速度被设为约 `-18`,快速向下冲击
|
||
- 下方 HitBox 激活
|
||
- **命中敌人后**:玩家立即向上弹起(`Pogo Jump`,高度固定),与普通跳跃力度相似
|
||
- Pogo 触发后空中冲刺次数重置(`_airDashUsed = false`)
|
||
- Pogo 触发后空中跳跃次数重置(`AirJumpsLeft` 恢复为最大值)
|
||
|
||
### 步骤 B:空中下劈未命中
|
||
|
||
1. 在空旷区域(无敌人/无特殊物体)跳跃
|
||
2. 空中使用下劈
|
||
|
||
**预期**:
|
||
- 下劈动画和向下速度正常
|
||
- **未命中任何目标**:玩家**不弹起**,继续受重力下落至地面
|
||
- 落地后进入 `IdleState`
|
||
|
||
### 步骤 C:地面下劈无效
|
||
|
||
1. 玩家站立地面
|
||
2. 按住 **Move 下方向键** + **Attack 键**
|
||
|
||
**预期**:
|
||
- **触发普通地面水平攻击**(或无响应),不触发下劈
|
||
- 下劈判定限制在空中状态 `!IsGrounded`
|
||
|
||
### 步骤 D:Pogo 后续接二段跳
|
||
|
||
1. 解锁二段跳(`PlayerStats.UnlockAbility(AbilityType.DoubleJump)`,可在 Inspector 勾选)
|
||
2. 空中下劈命中 Pogo 弹起
|
||
3. 弹起途中按 **Jump 键**
|
||
|
||
**预期**:
|
||
- Pogo 重置空中跳跃次数,可再次使用二段跳
|
||
|
||
| 检查点 | 期望 | ✓ |
|
||
|--------|------|---|
|
||
| 仅限空中触发 | 地面不触发下劈 | ☐ |
|
||
| 向下冲击速度 | velocity.y ≈ -18 | ☐ |
|
||
| 命中 Pogo 弹起 | 玩家向上弹起,高度固定 | ☐ |
|
||
| Pogo 重置冲刺次数 | Pogo 后可再次空中冲刺 | ☐ |
|
||
| Pogo 重置跳跃次数 | Pogo 后可再次二段跳(已解锁时) | ☐ |
|
||
| 未命中不弹起 | 继续下落至地面 | ☐ |
|
||
|
||
---
|
||
|
||
## MT-ABILITY-04:空中水平攻击
|
||
|
||
**目的**:验证空中水平攻击(`AirAttackState`)的触发条件、单次攻击、动画、结束返回 FallState。
|
||
|
||
### 步骤 A:空中普通攻击
|
||
|
||
1. 跳跃至空中
|
||
2. 按 **Attack 键**(不按方向,或左右方向,Y 轴绝对值 < 0.5)
|
||
|
||
**预期**:
|
||
- 触发空中水平攻击动画(`AnimationConfigSO.AirAttack`)
|
||
- HitBox 在角色侧面激活(`AttackDirection.Air`)
|
||
- 动画完成后进入 `FallState`,继续下落
|
||
- 命中敌人 HP 减少
|
||
|
||
### 步骤 B:空中攻击不循环
|
||
|
||
1. 空中连续多次按 **Attack 键**
|
||
|
||
**预期**:
|
||
- 空中攻击为单次,不进行连段
|
||
- 每次须等动画结束后重新输入才能再次攻击
|
||
|
||
| 检查点 | 期望 | ✓ |
|
||
|--------|------|---|
|
||
| 触发空中攻击动画 | 播放 AirAttack Clip | ☐ |
|
||
| HitBox 侧面激活 | Scene Gizmos 可见侧面碰撞盒 | ☐ |
|
||
| 结束后 FallState | 攻击完成后继续下落,不卡住 | ☐ |
|
||
| 单次不循环 | 无法连续多段空中攻击 | ☐ |
|
||
|
||
---
|
||
|
||
## MT-ABILITY-05:攻击取消窗口
|
||
|
||
**目的**:验证攻击后摇期间的取消窗口允许通过跳跃/冲刺打断攻击动作。
|
||
|
||
### 步骤 A:攻击后跳跃取消
|
||
|
||
1. 地面攻击触发第 1 段
|
||
2. 在**有效帧结束后、后摇结束前**(取消窗口期间)按 **Jump 键**
|
||
|
||
**预期**:
|
||
- 立即从 `AttackState` 转入 `JumpState`
|
||
- 不必等待后摇完全结束即可起跳
|
||
- 取消窗口关闭后(后摇末尾)按 Jump 则无法打断
|
||
|
||
### 步骤 B:攻击后冲刺取消
|
||
|
||
1. 地面攻击触发第 1 段
|
||
2. 在取消窗口期间按 **Dash 键**
|
||
|
||
**预期**:
|
||
- 立即进入 `DashState`,打断后摇
|
||
|
||
### 步骤 C:取消窗口外不可取消
|
||
|
||
1. 地面攻击触发第 1 段
|
||
2. 在**前摇期间**(有效帧之前)按 **Jump 键**
|
||
|
||
**预期**:
|
||
- 跳跃**无效**,必须等待取消窗口开放后才可取消
|
||
|
||
| 检查点 | 期望 | ✓ |
|
||
|--------|------|---|
|
||
| 取消窗口内可跳跃 | Jump 立即打断后摇 | ☐ |
|
||
| 取消窗口内可冲刺 | Dash 立即打断后摇 | ☐ |
|
||
| 前摇内无法取消 | Jump/Dash 在前摇期间无响应 | ☐ |
|
||
|
||
---
|
||
|
||
## MT-ABILITY-06:抓墙与高度记忆机制
|
||
|
||
**目的**:验证触发条件、无需持续按键维持、高度记忆防无限攀爬逻辑(正常模式静止 / 受限模式下滑)。
|
||
|
||
### 前置条件
|
||
|
||
- 在玩家右侧放置高度 ≥ 6 格的垂直墙壁(Layer=Ground)
|
||
|
||
### 步骤 A:基本抓墙触发
|
||
|
||
1. 向右跳跃,在空中接触右侧墙壁
|
||
2. 按住 **Move 右方向键**(朝向墙壁方向,X 轴正向)
|
||
|
||
**预期**:
|
||
- 进入 `WallSlideState`
|
||
- 播放抓墙动画
|
||
- **松开方向键**后角色仍保持抓墙状态(无需持续按键)
|
||
|
||
### 步骤 B:主动松墙
|
||
|
||
1. 抓墙状态下按下 **Move 左方向键**(反方向键)
|
||
|
||
**预期**:
|
||
- 立即解除抓墙,进入 `FallState`
|
||
|
||
### 步骤 C:正常模式(抓墙高度 ≤ wallGrabY)—— 静止悬挂
|
||
|
||
1. 跳跃至墙壁中部,触发抓墙(记录 `wallGrabY`)
|
||
2. 松开方向键,观察角色是否移动
|
||
|
||
**预期**:
|
||
- 角色**静止悬挂**,Y 坐标不变
|
||
- 可以在此状态触发蹬墙跳
|
||
|
||
### 步骤 D:受限模式(抓墙高度 > wallGrabY)—— 持续下滑且无法蹬墙跳
|
||
|
||
1. 在 C 的基础上,直接跳跃后**更高处再次贴墙**(同一面墙,但 Y 坐标高于 `wallGrabY`)
|
||
2. 触发抓墙,松开方向键
|
||
|
||
**预期**:
|
||
- 角色**持续向下滑动**(受限模式)
|
||
- **无法触发蹬墙跳**(按 Jump 键无效)
|
||
|
||
> **💡 操作技巧**:从地面站立后直接跳上更高位置并贴墙,此时 Y > wallGrabY(因为上一次落地已重置),也可以先蹬墙跳到更高处再贴同一面墙来制造 Y > wallGrabY 的情况。
|
||
|
||
### 步骤 E:落地重置 wallGrabY
|
||
|
||
1. 完成 C 的抓墙后蹬墙跳离墙,落地
|
||
2. 再次跳跃贴同一面墙(高度与 C 中相同)
|
||
|
||
**预期**:
|
||
- 落地后 `wallGrabY` 已重置,该高度重新进入**正常模式**(静止悬挂,可蹬墙跳)
|
||
|
||
### 步骤 F:贴另一面墙重置 wallGrabY
|
||
|
||
1. 在步骤 C 中抓右侧墙壁后,蹬墙跳至左侧墙壁
|
||
2. 在左侧墙壁比之前更高处抓墙
|
||
|
||
**预期**:
|
||
- 切换到另一面墙壁时 `wallGrabY` 重置为新值
|
||
- 该高度为正常模式(可静止,可蹬墙跳)
|
||
|
||
| 检查点 | 期望 | ✓ |
|
||
|--------|------|---|
|
||
| 朝墙方向键触发抓墙 | 接触墙 + 朝墙输入 → WallSlideState | ☐ |
|
||
| 无需持续按键 | 松开方向键后仍保持抓墙 | ☐ |
|
||
| 反向键松墙 | FallState | ☐ |
|
||
| 正常模式静止 | Y 坐标不变,不下滑 | ☐ |
|
||
| 受限模式下滑 | 高于 wallGrabY 时持续下滑 | ☐ |
|
||
| 受限模式无蹬墙跳 | Jump 键在受限模式无效 | ☐ |
|
||
| 落地重置 | 落地后 wallGrabY 清零 | ☐ |
|
||
| 换墙重置 | 抓另一面墙时记录新 wallGrabY | ☐ |
|
||
|
||
---
|
||
|
||
## MT-ABILITY-07:蹬墙跳(背墙跳 / 对墙跳)
|
||
|
||
**目的**:验证背墙跳(Away)和对墙跳(Toward)的方向判定与施力、视为第一段跳、可变高度支持。
|
||
|
||
### 前置条件
|
||
|
||
- 玩家处于正常抓墙状态(高度 ≤ wallGrabY,可触发蹬墙跳)
|
||
|
||
### 步骤 A:背墙跳(Away Jump)
|
||
|
||
1. 抓右侧墙壁,**不按任何水平方向键**(或按左键,即反方向)
|
||
2. 按 **Jump 键**
|
||
|
||
**预期**:
|
||
- 玩家朝**远离右墙方向**弹出(向左上方),播放 WallJumpAway 动画
|
||
- 弹出后有约 0.2-0.3 秒的水平输入锁定(`_inputLockTimer`),锁定结束后可自由控制水平方向
|
||
- `AirJumpsLeft` 恢复(视为第一段跳)
|
||
|
||
### 步骤 B:对墙跳(Toward Jump)
|
||
|
||
1. 抓右侧墙壁,按住 **Move 右方向键**(朝墙方向)
|
||
2. 按 **Jump 键**
|
||
|
||
**预期**:
|
||
- 玩家朝**朝向右墙方向弹出**(向右上方),施力偏向竖直,水平分量较小
|
||
- 播放 WallJumpToward 动画
|
||
- 同样视为第一段跳,`AirJumpsLeft` 恢复
|
||
|
||
### 步骤 C:蹬墙跳后可接二段跳(已解锁时)
|
||
|
||
1. 背墙跳后,在空中再次按 **Jump 键**
|
||
|
||
**预期**:
|
||
- 若已解锁二段跳,此时可触发二段跳(`AirJumpsLeft > 0`)
|
||
- 若未解锁,按 Jump 无效
|
||
|
||
### 步骤 D:蹬墙跳可变高度
|
||
|
||
1. 背墙跳后立即松开 **Jump 键**
|
||
|
||
**预期**:
|
||
- 跳跃高度降低(同普通跳跃可变高度,提前松键截断上升速度)
|
||
|
||
### 步骤 E:受限模式下无蹬墙跳
|
||
|
||
1. 使玩家抓墙高度 > wallGrabY(受限模式)
|
||
2. 按 **Jump 键**
|
||
|
||
**预期**:
|
||
- 蹬墙跳不触发(按 Jump 在受限模式无反应)
|
||
|
||
| 检查点 | 期望 | ✓ |
|
||
|--------|------|---|
|
||
| 背墙跳方向正确 | 远离墙壁斜上方弹出 | ☐ |
|
||
| 对墙跳方向正确 | 朝墙壁斜上方弹出,更偏垂直 | ☐ |
|
||
| 输入锁定(背墙跳) | 弹出后短时无法控制水平方向 | ☐ |
|
||
| 视为第一段跳 | AirJumpsLeft 重置 | ☐ |
|
||
| 可接二段跳 | 蹬墙跳后可再次按 Jump(已解锁) | ☐ |
|
||
| 可变高度 | 提前松键降低弹跳高度 | ☐ |
|
||
| 受限模式无效 | 受限抓墙时 Jump 无响应 | ☐ |
|
||
|
||
---
|
||
|
||
## MT-ABILITY-08:二段跳
|
||
|
||
**目的**:验证二段跳的解锁门控、空中二次起跳、高度低于一段跳、可变高度支持。
|
||
|
||
### 前置条件
|
||
|
||
- 在 `PlayerStats` Inspector 中确认 `AbilityType.DoubleJump` 是否已解锁
|
||
- **步骤 A 使用未解锁状态,步骤 B 及以后需先解锁**
|
||
|
||
### 步骤 A:未解锁时空中 Jump 无效
|
||
|
||
1. 确认二段跳**未解锁**(`AbilityFlags` 中无 `DoubleJump`)
|
||
2. 跳跃至空中
|
||
3. 在下落途中按 **Jump 键**
|
||
|
||
**预期**:
|
||
- Jump 无效,不产生二次跳跃
|
||
|
||
### 步骤 B:解锁后空中二段跳
|
||
|
||
1. 在 Inspector 中勾选 `DoubleJump` 解锁(或调用 `Stats.UnlockAbility(DoubleJump)` 通过 Console)
|
||
2. 跳跃至空中,在最高点前或下落时按 **Jump 键**
|
||
|
||
**预期**:
|
||
- 触发二段跳动画(`DoubleJump` Clip)
|
||
- 玩家再次向上弹起,高度明显低于一段跳
|
||
- `AirJumpsLeft` 由 1 减为 0
|
||
|
||
3. 二段跳后再次按 **Jump 键**
|
||
|
||
**预期**:
|
||
- 无响应(`AirJumpsLeft == 0`,无更多空中跳跃机会)
|
||
|
||
### 步骤 C:二段跳可变高度
|
||
|
||
1. 二段跳后立即松开 **Jump 键**
|
||
|
||
**预期**:
|
||
- 跳跃高度降低(提前松键截断上升速度)
|
||
|
||
### 步骤 D:落地重置二段跳次数
|
||
|
||
1. 使用二段跳后落地
|
||
2. 再次跳跃,在空中按 **Jump 键**
|
||
|
||
**预期**:
|
||
- `AirJumpsLeft` 已重置(落地时 `ResetAirJumps()` 调用),可再次使用二段跳
|
||
|
||
| 检查点 | 期望 | ✓ |
|
||
|--------|------|---|
|
||
| 未解锁时无效 | 空中 Jump 无响应 | ☐ |
|
||
| 解锁后可二段跳 | 空中第二次 Jump 触发 | ☐ |
|
||
| 二段跳高度低于一段 | 弹起高度明显更低 | ☐ |
|
||
| 二段跳次数限制 | 第三次 Jump 无响应 | ☐ |
|
||
| 可变高度 | 提前松键降低高度 | ☐ |
|
||
| 落地后重置 | 落地后可再次二段跳 | ☐ |
|
||
|
||
---
|
||
|
||
## MT-ABILITY-09:无敌冲刺
|
||
|
||
**目的**:验证无敌冲刺的解锁门控、冲刺期间无敌帧(不受伤害)、独立冷却机制。
|
||
|
||
### 前置条件
|
||
|
||
- 在 `PlayerStats` Inspector 中确认 `AbilityType.InvincibleDash` 是否已解锁
|
||
- 在测试场景中放置一个会持续攻击的测试敌人(或使用调试工具手动给玩家造成伤害)
|
||
|
||
### 步骤 A:未解锁时冲刺无无敌帧
|
||
|
||
1. 确认无敌冲刺**未解锁**
|
||
2. 在敌人攻击范围内进行冲刺
|
||
|
||
**预期**:
|
||
- 冲刺期间受到伤害,进入 `HurtState`(基础冲刺无无敌帧)
|
||
|
||
### 步骤 B:解锁后冲刺无敌帧
|
||
|
||
1. 解锁 `InvincibleDash`
|
||
2. 在敌人攻击方向上冲刺穿越
|
||
|
||
**预期**:
|
||
- 冲刺期间**不受伤害**(`Stats.IsInvincible == true` 持续 `DashInvincibilityDuration` 约 0.20s)
|
||
- 无敌期间 Inspector 中 `IsInvincible` 字段为 true
|
||
- 无敌帧结束后恢复正常受伤判定
|
||
|
||
### 步骤 C:无敌冲刺独立冷却
|
||
|
||
1. 无敌冲刺后立即再次冲刺
|
||
|
||
**预期**:
|
||
- 第 2 次冲刺可正常触发(冲刺本身冷却 ≈ 0.4s)
|
||
- 但第 2 次冲刺**无无敌帧**(无敌帧有独立冷却,需等待更长时间恢复)
|
||
- Inspector 中 `_invincibilityCooldownTimer > 0` 时不授予无敌
|
||
|
||
| 检查点 | 期望 | ✓ |
|
||
|--------|------|---|
|
||
| 未解锁无无敌 | 冲刺中受伤进入 HurtState | ☐ |
|
||
| 解锁后有无敌帧 | 冲刺期间 IsInvincible=true,不受伤 | ☐ |
|
||
| 无敌帧持续时间 | 约 0.20s 后 IsInvincible 变 false | ☐ |
|
||
| 独立冷却 | 短时间内第 2 次冲刺无无敌帧 | ☐ |
|
||
|
||
---
|
||
|
||
## MT-ABILITY-10:三形态切换
|
||
|
||
**目的**:验证三形态切换的输入响应、武器/动画随形态更新、技能集随形态更新。
|
||
|
||
### 前置条件
|
||
|
||
- `FormConfigSO` 中三个 FormSO(天魂/地魂/命魂)已配置,各有不同武器 Prefab
|
||
- `SkillManager._formSkillSets` 数组已填写三形态对应的技能 SO
|
||
- `WeaponManager` 已绑定 `FormController`
|
||
|
||
### 步骤 A:切换至天魂(SwitchSkyForm)
|
||
|
||
1. 进入 Play Mode,玩家默认形态
|
||
2. 按 **SwitchSkyForm 键**(默认 `1`)
|
||
|
||
**预期**:
|
||
- `FormController.CurrentForm.formType == TianHun`
|
||
- 武器切换到天魂武器 Prefab(`WeaponManager.ActiveWeapon` 更新)
|
||
- `SkillManager` 中 `_soulSkill`、`_spirit1`、`_spirit2` 替换为天魂对应技能
|
||
- HUD 形态颜色/图标更新(若已实现 UI 订阅)
|
||
- Console 无 Error
|
||
|
||
### 步骤 B:切换至地魂(SwitchEarthForm)
|
||
|
||
1. 按 **SwitchEarthForm 键**(默认 `2`)
|
||
|
||
**预期**:同 A,武器和技能集更换为地魂版本。
|
||
|
||
### 步骤 C:切换至命魂(SwitchDeathForm)
|
||
|
||
1. 按 **SwitchDeathForm 键**(默认 `3`)
|
||
|
||
**预期**:同 A,武器和技能集更换为命魂版本。
|
||
|
||
### 步骤 D:切换后攻击使用新武器
|
||
|
||
1. 切换形态后立即攻击
|
||
|
||
**预期**:
|
||
- HitBox 来自新形态武器实例
|
||
- 伤害值使用新形态武器 `WeaponSO.Damage`
|
||
|
||
### 步骤 E:重复切换同一形态
|
||
|
||
1. 当前已在天魂,再次按 **SwitchSkyForm 键**
|
||
|
||
**预期**:
|
||
- 无副作用(不重复切换,也不报错)
|
||
- `OnFormChanged` 事件视实现可能不触发(取决于 `FormController` 是否有相同形态判断)
|
||
|
||
| 检查点 | 期望 | ✓ |
|
||
|--------|------|---|
|
||
| 形态正确更新 | CurrentForm.formType 对应键值 | ☐ |
|
||
| 武器随形态切换 | WeaponManager.ActiveWeapon 更新 | ☐ |
|
||
| 技能集随形态切换 | SkillManager _soulSkill/_spirit1/_spirit2 更新 | ☐ |
|
||
| 攻击使用新武器 | 新武器 HitBox 生效 | ☐ |
|
||
| Console 无 Error | 0 个红色 Error | ☐ |
|
||
|
||
---
|
||
|
||
## MT-ABILITY-11:三套资源系统
|
||
|
||
**目的**:验证灵力、魄元、灵泉次数三者相互独立、各自消耗/恢复逻辑。
|
||
|
||
### 步骤 A:灵力(SoulPower)—— 攻击积累
|
||
|
||
1. 在 Inspector 中观察 `PlayerStats._currentSoulPower`(初始 = 0)
|
||
2. 攻击命中测试敌人多次
|
||
|
||
**预期**:
|
||
- 每次命中后 `_currentSoulPower` 增加
|
||
- 不影响 `_currentSpiritPower` 和 `_currentSpringCharges`
|
||
|
||
3. 消耗至 0(触发魂技能,见 MT-ABILITY-14)
|
||
|
||
**预期**:
|
||
- `_currentSoulPower` 降低
|
||
- `_currentSpiritPower` 不变
|
||
|
||
### 步骤 B:魄元(SpiritPower)—— 时间恢复
|
||
|
||
1. 使 `_currentSpiritPower` 低于上限(触发魄技能消耗)
|
||
2. 等待约 3 秒
|
||
|
||
**预期**:
|
||
- `_currentSpiritPower` 每秒自动增加(`SpiritRegenRate` 配置值)
|
||
- 不影响 `_currentSoulPower` 和 `_currentSpringCharges`
|
||
|
||
### 步骤 C:灵泉次数(SpringCharges)—— 三者独立
|
||
|
||
1. 攻击积累灵力,同时魄元自然恢复,灵泉次数保持不变
|
||
|
||
**预期**:
|
||
- 三个字段**完全独立变动**,互无影响
|
||
|
||
| 检查点 | 期望 | ✓ |
|
||
|--------|------|---|
|
||
| 灵力随攻击命中增加 | 每次命中 SoulPower 增加 | ☐ |
|
||
| 魄元随时间自动恢复 | 未满时 SpiritPower 逐秒增加 | ☐ |
|
||
| 三者独立 | 任一变化不影响其他两项 | ☐ |
|
||
|
||
---
|
||
|
||
## MT-ABILITY-12:灵泉使用
|
||
|
||
**目的**:验证灵泉使用的前置条件(地面 + 有充能)、HP 恢复、Overlay 动画、充能消耗。
|
||
|
||
### 前置条件
|
||
|
||
- `PlayerStats._currentSpringCharges > 0`(至少 1 次充能)
|
||
- 玩家 HP 不满(降低 HP 以观察回复效果)
|
||
|
||
### 步骤 A:地面使用灵泉
|
||
|
||
1. 玩家站立地面(`IsGrounded == true`)
|
||
2. HP 低于上限
|
||
3. 按 **UseSpring 键**(默认 `E`)
|
||
|
||
**预期**:
|
||
- 播放 Overlay 动画(Layer 1 叠加于 Base Layer 之上),不打断移动动画
|
||
- `_currentSpringCharges` 减少 1
|
||
- `PlayerStats.CurrentHP` 增加(`SpringHealAmount` 配置值)
|
||
- 动画结束后玩家返回正常状态
|
||
|
||
### 步骤 B:空中使用灵泉无效
|
||
|
||
1. 跳跃至空中(`IsGrounded == false`)
|
||
2. 按 **UseSpring 键**
|
||
|
||
**预期**:
|
||
- **无响应**(`OnUseSpring` 检测到非地面,直接返回)
|
||
|
||
### 步骤 C:充能为 0 时无效
|
||
|
||
1. 确认 `_currentSpringCharges == 0`
|
||
2. 地面按 **UseSpring 键**
|
||
|
||
**预期**:
|
||
- **无响应**(充能为 0 时不进入 `SpringState`)
|
||
|
||
### 步骤 D:Overlay 动画不打断移动
|
||
|
||
1. 地面移动中按 **UseSpring 键**
|
||
|
||
**预期**:
|
||
- 使用灵泉 Overlay 动画在 Layer 1 播放
|
||
- 玩家可继续移动(Layer 0 移动动画继续)
|
||
|
||
| 检查点 | 期望 | ✓ |
|
||
|--------|------|---|
|
||
| 地面使用成功 | HP 增加,ChargesCount 减 1 | ☐ |
|
||
| 空中无效 | 空中按键无任何响应 | ☐ |
|
||
| 充能为 0 无效 | 无充能时按键无响应 | ☐ |
|
||
| Overlay 动画叠加 | Layer 1 动画播放,Layer 0 不中断 | ☐ |
|
||
|
||
---
|
||
|
||
## MT-ABILITY-13:灵泉充能(击杀积累)
|
||
|
||
**目的**:验证 `SpringSystem` 通过 EVT_EnemyDied 事件积累点数、达到阈值后自动增加灵泉次数。
|
||
|
||
### 前置条件
|
||
|
||
- 场景内有可被击杀的测试敌人
|
||
- `SpringSystem` 组件挂在玩家或管理器 GameObject 上,`_onEnemyDied` 字段已绑定 `EVT_EnemyDied` SO 事件资产
|
||
- 在 Inspector 中调低 `PlayerStatsSO.SpringKillThreshold`(如设为 2)以方便观察
|
||
|
||
### 步骤 A:击杀积累点数
|
||
|
||
1. 攻击并击杀第 1 个测试敌人
|
||
|
||
**预期**:
|
||
- EVT_EnemyDied 事件触发
|
||
- `PlayerStats._springKillPoints` 增加 1(可在 Inspector 观察)
|
||
|
||
2. 再次击杀一个敌人
|
||
|
||
**预期**:
|
||
- `_springKillPoints` 增加至阈值
|
||
- 自动调用 `RestoreSpringCharges(1)`,`_currentSpringCharges` 增加 1
|
||
- `_springKillPoints` 清零
|
||
|
||
### 步骤 B:充能上限限制
|
||
|
||
1. 将 `_currentSpringCharges` 调至上限(`MaxSpringCharges`)
|
||
2. 继续击杀敌人
|
||
|
||
**预期**:
|
||
- `_springKillPoints` 继续积累
|
||
- 当 `_currentSpringCharges == MaxSpringCharges` 时,`RestoreSpringCharges` 不超过上限
|
||
|
||
### 步骤 C:存档点恢复次数至上限
|
||
|
||
1. 消耗 1 次灵泉
|
||
2. 与测试场景中的存档点 GameObject 交互(或调用 `Stats.RestoreOnSave()`)
|
||
|
||
**预期**:
|
||
- `_currentSpringCharges` 恢复至 `MaxSpringCharges`
|
||
- `_springKillPoints` 清零(存档点重置积累点)
|
||
|
||
| 检查点 | 期望 | ✓ |
|
||
|--------|------|---|
|
||
| EVT_EnemyDied 触发点数积累 | 击杀后 _springKillPoints 增加 | ☐ |
|
||
| 达阈值自动 +1 次充能 | 积累满后 ChargesCount 增加,点数清零 | ☐ |
|
||
| 充能不超上限 | MaxSpringCharges 限制 | ☐ |
|
||
| 存档点恢复至上限 | ChargesCount = MaxSpringCharges | ☐ |
|
||
|
||
---
|
||
|
||
## MT-ABILITY-14:魂技能施放
|
||
|
||
**目的**:验证 `SkillManager` 响应 SoulSkill 输入、消耗灵力、播放技能动画、冷却限制。
|
||
|
||
### 前置条件
|
||
|
||
- 当前形态已配置 `FormSkillSet.soulSkill`(有效的 `FormSkillSO` 资产)
|
||
- `PlayerStats._currentSoulPower` 达到技能消耗量以上
|
||
|
||
### 步骤 A:灵力充足时施放魂技能
|
||
|
||
1. 攻击敌人积累足够灵力(`_currentSoulPower ≥ soulSkill.Cost`)
|
||
2. 按 **SoulSkill 键**(默认 `Q`)
|
||
|
||
**预期**:
|
||
- 技能动画播放(`FormSkillSO.animationClip`)
|
||
- `_currentSoulPower` 减少 `soulSkill.Cost`
|
||
- 技能特效实例化(若 `FormSkillSO` 中配置了 VFX)
|
||
- 技能进入冷却(`_cooldowns[soulSkill]` 开始计时)
|
||
|
||
### 步骤 B:冷却中无法再次施放
|
||
|
||
1. 技能施放后立即再次按 **SoulSkill 键**
|
||
|
||
**预期**:
|
||
- 无响应(冷却剩余 `> 0` 时拒绝施放)
|
||
|
||
### 步骤 C:灵力不足时无法施放
|
||
|
||
1. `_currentSoulPower < soulSkill.Cost`(可通过消耗后立即尝试)
|
||
2. 按 **SoulSkill 键**
|
||
|
||
**预期**:
|
||
- 无响应(灵力不足,`SkillManager` 内部判断拒绝)
|
||
|
||
### 步骤 D:形态切换后技能变更
|
||
|
||
1. 切换到另一个形态
|
||
2. 按 **SoulSkill 键**
|
||
|
||
**预期**:
|
||
- 施放的是**新形态**的魂技能(不同动画、不同效果)
|
||
|
||
| 检查点 | 期望 | ✓ |
|
||
|--------|------|---|
|
||
| 灵力充足时施放成功 | 动画播放,SoulPower 减少 | ☐ |
|
||
| 冷却中无法施放 | 再次按键无响应 | ☐ |
|
||
| 灵力不足无法施放 | 资源不足时无响应 | ☐ |
|
||
| 形态切换后使用新技能 | 切换形态后施放新形态魂技能 | ☐ |
|
||
|
||
---
|
||
|
||
## MT-ABILITY-15:魄技能施放(技能 1 / 2)
|
||
|
||
**目的**:验证 `SpiritSkill1`/`SpiritSkill2` 的消耗魄元、独立冷却、形态切换更新。
|
||
|
||
### 前置条件
|
||
|
||
- 当前形态已配置 `FormSkillSet.spiritSkill1` 和 `spiritSkill2`
|
||
- `PlayerStats._currentSpiritPower` 充足
|
||
|
||
### 步骤 A:施放魄技能 1
|
||
|
||
1. 按 **SpiritSkill1 键**(默认 `R`,长按触发 Start,松开触发 Cancelled)
|
||
|
||
**预期**:
|
||
- 技能动画播放
|
||
- `_currentSpiritPower` 减少 `spiritSkill1.Cost`
|
||
- 若技能为蓄力型,按住触发蓄力动画,松开触发释放
|
||
|
||
### 步骤 B:施放魄技能 2
|
||
|
||
1. 按 **SpiritSkill2 键**(默认 `F`)
|
||
|
||
**预期**:
|
||
- 施放魄技能 2(不同动画/效果)
|
||
- 消耗独立的魄元量(`spiritSkill2.Cost`)
|
||
|
||
### 步骤 C:技能 1 和技能 2 冷却独立
|
||
|
||
1. 依次施放技能 1 和技能 2
|
||
|
||
**预期**:
|
||
- 技能 1 冷却中**不影响**技能 2 的施放(`_cooldowns` 字典各自独立计时)
|
||
|
||
### 步骤 D:魄元自动恢复后可再次施放
|
||
|
||
1. 魄技能消耗魄元后等待恢复
|
||
2. 再次施放
|
||
|
||
**预期**:
|
||
- 魄元恢复至足够后可再次施放(`SpiritRegenRate` 自动回复)
|
||
|
||
| 检查点 | 期望 | ✓ |
|
||
|--------|------|---|
|
||
| 技能 1 施放成功 | 动画播放,SpiritPower 减少 | ☐ |
|
||
| 技能 2 施放成功 | 不同动画,SpiritPower 减少 | ☐ |
|
||
| 冷却独立 | 技能 1 冷却不阻止技能 2 | ☐ |
|
||
| 形态切换后使用新技能 | 新形态的魄技能 1/2 生效 | ☐ |
|
||
| 魄元恢复后可再施放 | 等待后 SpiritPower 恢复,可用 | ☐ |
|
||
|
||
---
|
||
|
||
## MT-ABILITY-16:弹反系统
|
||
|
||
**目的**:验证 `ParrySystem`/`ParryState` 的触发窗口、弹反成功判定、灵力奖励、护盾恢复。
|
||
|
||
### 前置条件
|
||
|
||
- 场景中有能发动近战攻击的测试敌人(或使用 Debug 工具手动触发弹反机会)
|
||
- `ParrySystem` 已绑定 `InputReaderSO`
|
||
- `ShieldComponent` 已配置(若需验证护盾恢复)
|
||
|
||
### 步骤 A:弹反触发与成功
|
||
|
||
1. 敌人发起攻击时,在攻击即将命中前按下**弹反键**(`Parry`,默认 `K`)
|
||
|
||
**预期**:
|
||
- 进入 `ParryState`,播放弹反动画
|
||
- 若在弹反窗口内成功拦截敌人攻击:
|
||
- 弹反命中判定成功,敌人受到弹反反馈(硬直或被弹飞)
|
||
- `PlayerStats._currentSoulPower` 增加(`ParryInfo.SoulGained`)
|
||
- `ShieldComponent.OnParrySuccess()` 调用(护盾恢复)
|
||
- 玩家**不受任何伤害**(弹反期间有效)
|
||
|
||
### 步骤 B:弹反窗口外受伤
|
||
|
||
1. 弹反动画开始后**等待窗口结束**(约 0.2-0.3s 后),此时再受到敌人攻击
|
||
|
||
**预期**:
|
||
- 玩家正常受伤,进入 `HurtState`(弹反窗口已关闭)
|
||
|
||
### 步骤 C:空弹反(无敌人攻击)
|
||
|
||
1. 地面不受攻击时按弹反键
|
||
|
||
**预期**:
|
||
- 播放弹反动画
|
||
- 弹反窗口结束后自动返回 `IdleState`
|
||
- 无报错,无副作用
|
||
|
||
| 检查点 | 期望 | ✓ |
|
||
|--------|------|---|
|
||
| 弹反动画播放 | ParryState 正确进入 | ☐ |
|
||
| 弹反成功不受伤 | 窗口内拦截,玩家 HP 不减少 | ☐ |
|
||
| 灵力奖励 | SoulPower 增加 SoulGained 值 | ☐ |
|
||
| 护盾恢复 | ShieldComponent.OnParrySuccess 调用 | ☐ |
|
||
| 窗口外受伤 | 窗口关闭后受攻击进入 HurtState | ☐ |
|
||
| 空弹反无副作用 | 无敌人攻击时弹反后正常返回 Idle | ☐ |
|
||
|
||
---
|
||
|
||
## 附录 A:Inspector 调试辅助字段
|
||
|
||
以下字段在 Play Mode 下可在 Inspector 中实时观察,便于调试:
|
||
|
||
| GameObject | 组件 | 字段 | 含义 |
|
||
|-----------|------|------|------|
|
||
| Player | `PlayerController` | `_dbg_CurrentState` | 当前 FSM 状态名 |
|
||
| Player | `PlayerController` | `_dbg_IsGrounded` | 是否落地 |
|
||
| Player | `PlayerController` | `_dbg_AirJumpsLeft` | 剩余空中跳跃次数 |
|
||
| Player | `PlayerController` | `_dbg_CanDash` | 当前是否可冲刺 |
|
||
| Player | `PlayerController` | `_dbg_IsInvincible` | 当前是否无敌 |
|
||
| Player | `PlayerStats` | `_currentHP` | 当前 HP |
|
||
| Player | `PlayerStats` | `_currentSoulPower` | 当前灵力 |
|
||
| Player | `PlayerStats` | `_currentSpiritPower` | 当前魄元 |
|
||
| Player | `PlayerStats` | `_currentSpringCharges` | 当前灵泉次数 |
|
||
| Player | `PlayerStats` | `_springKillPoints` | 当前灵泉积累点 |
|
||
| Player | `FormController` | `CurrentForm` | 当前形态 SO |
|
||
| Player | `SkillManager` | `_soulSkill` | 当前魂技能 |
|
||
| Player | `SkillManager` | `_spirit1` | 当前魄技能 1 |
|
||
| Player | `SkillManager` | `_spirit2` | 当前魄技能 2 |
|
||
|
||
---
|
||
|
||
## 附录 B:常见问题排查
|
||
|
||
| 问题现象 | 可能原因 | 排查步骤 |
|
||
|---------|---------|---------|
|
||
| 攻击无 HitBox 效果 | WeaponManager 武器未实例化 | Inspector 检查 `WeaponManager.ActiveHitBoxInstance` 是否为 null |
|
||
| 形态切换后技能未更新 | `SkillManager._formController` 未赋值 | 确认 Inspector 中已拖入 `FormController` 引用,并检查 `_formSkillSets` 数组长度 ≥ 3 |
|
||
| 灵泉使用无响应 | `UseSpringEvent` 未绑定 | 检查 `InputReaderSO` 中 UseSpring Action 名称拼写,确认 PlayerController 已订阅 |
|
||
| 抓墙后立即滑落 | WallDetector 未检测到墙 | 检查墙壁 Layer 是否为 `Ground`,`PlayerWallDetector` 的 `wallLayer` 掩码包含该 Layer |
|
||
| 蹬墙跳无效 | 受限模式(高于 wallGrabY) | 在受限模式(下滑状态)时蹬墙跳设计上不可用,属预期行为 |
|
||
| 魄技能冷却独立验证失败 | 技能 SO 资产共用同一实例 | 确认技能 1、技能 2 使用的是**不同** `FormSkillSO` 资产(不同 `.asset` 文件) |
|
||
| Pogo 未弹起 | DownAttackState `OnDownHitConfirmed` 未订阅 | 检查 `PlayerCombat._currentHitBoxInstance` 是否正确订阅,Console 查看 `HandleWeaponChanged` 是否调用 |
|
||
| Console 出现 NullReferenceException | Inspector 中某 SO/组件字段未赋值 | 运行时在 `PlayerController.Awake` 的 `Debug.Assert` 输出中查看具体缺失字段 |
|