953 lines
41 KiB
Markdown
953 lines
41 KiB
Markdown
# 03 · 玩家系统
|
||
|
||
> **命名空间** `BaseGames.Player` · `BaseGames.Player.States`
|
||
> **所属文档集** [← 返回索引](./README.md) · [总览](./00_Overview.md)
|
||
> **依赖** `BaseGames.Input` · `BaseGames.Combat` · `BaseGames.Parry` · Animancer Pro
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [系统总览](#1-系统总览)
|
||
2. [PlayerController — 协调器](#2-playercontroller--协调器)
|
||
3. [PlayerMovement — 物理移动](#3-playermovement--物理移动)
|
||
4. [PlayerStats — 属性系统](#4-playerstats--属性系统)
|
||
5. [PlayerCombat — 战斗管理](#5-playercombat--战斗管理)
|
||
6. [FormController — 形态系统](#6-formcontroller--形态系统)
|
||
7. [Animancer 双层动画系统](#7-animancer-双层动画系统)
|
||
8. [PlayerAnimationConfigSO](#8-playeranimationconfigso)
|
||
9. [FSM 状态体系](#9-fsm-状态体系)
|
||
10. [状态详细设计](#10-状态详细设计)
|
||
11. [能力解锁系统](#11-能力解锁系统)
|
||
12. [PlayerMovementConfigSO](#12-playermovementconfigso)
|
||
13. [编辑器友好设计](#13-编辑器友好设计)
|
||
14. [空中冲刺(Aerial Dash)](#14-空中冲刺aerialDash)
|
||
|
||
---
|
||
|
||
## 1. 系统总览
|
||
|
||
```
|
||
Player Prefab
|
||
├── PlayerController ← 协调器(无业务逻辑)
|
||
│ ├── PlayerMovement ← 物理移动(Rigidbody2D)
|
||
│ ├── PlayerStats ← 属性:HP/灵力/魄元/灵泉/Geo/能力
|
||
│ ├── PlayerCombat ← HitBox 开关、连击链(读取 WeaponManager)
|
||
│ ├── FormController ← 形态管理(天魂/地魂/命魂,见 §6)
|
||
│ ├── WeaponManager ← 武器切换管理,形态联动(见 53_WeaponSystem)
|
||
│ ├── SkillModifierRegistry← 技能修改器(见 21_SpellSystem §5)
|
||
│ ├── SkillManager ← 魂技能/魄技能执行(见 21_SkillSystem)
|
||
│ ├── SpringSystem ← 灵泉使用次数管理(见 §4.3)
|
||
│ ├── ParrySystem ← 弹反状态(见 05_ParrySystem)
|
||
│ └── AnimancerComponent ← Animancer 动画主入口
|
||
│
|
||
├── HurtBox ← 受击区域(路由 DamageInfo 给 PlayerController)
|
||
├── HitBox_Ground ← 地面水平攻击判定
|
||
├── HitBox_Up ← 上劈攻击判定
|
||
├── HitBox_Down ← 下劈攻击判定(Pogo)
|
||
├── HitBox_Air ← 空中攻击判定(各形态独立闸题)
|
||
├── PlayerFeedback ← Feel MMF_Player 集合
|
||
└── SpriteRenderer
|
||
```
|
||
|
||
**设计要点**:`PlayerController` 是**纯协调器**,只负责:
|
||
1. 初始化并持有子组件引用
|
||
2. 维护 Animancer FSM(当前状态切换)
|
||
3. 在受击时调用 `ForceState(HurtState)`
|
||
4. 在死亡时触发 `OnPlayerDied` 事件频道
|
||
|
||
所有具体行为(移动计算、攻击判定、弹反逻辑)委托给各子组件或各 State。
|
||
|
||
---
|
||
|
||
## 2. PlayerController — 协调器
|
||
|
||
### 持有引用(Inspector 序列化)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `_inputReader` | `InputReaderSO` | 输入频道(SO,全局共享)|
|
||
| `_movementConfig` | `PlayerMovementConfigSO` | 移动参数配置 |
|
||
| `_animConfig` | `PlayerAnimationConfigSO` | 动画配置 |
|
||
| `_statsConfig` | `PlayerStatsSO` | 初始属性配置 |
|
||
| `_formConfig` | `FormConfigSO` | 形态配置(天魂/地魂/命魂 SO 引用)|
|
||
| `_onPlayerDied` | `VoidEventChannelSO` | 死仪事件频道(SO)|
|
||
| `_onHPChanged` | `IntEventChannelSO` | HP 变化频道(SO)|
|
||
|
||
### 公开接口(供外部调用)
|
||
|
||
| 方法/属性 | 调用方 | 说明 |
|
||
|----------|--------|------|
|
||
| `ForceState(PlayerStateBase)` | HurtBox / ParrySystem | 强制切换状态(受击/死亡)|
|
||
| `TryTransitionState(PlayerStateBase)` | 各 State 内部 | 正常状态转换(带优先级检查)|
|
||
| `IsGrounded` | 各 State | 地面检测结果(只读)|
|
||
| `FacingDirection` | 各 State / PlayerCombat | 当前朝向 (+1 右 / -1 左)|
|
||
| `Rb` | PlayerMovement / 各 State | Rigidbody2D 引用 |
|
||
| `Animancer` | 各 State | AnimancerComponent 引用 |
|
||
|
||
---
|
||
|
||
## 3. PlayerMovement — 物理移动
|
||
|
||
`PlayerMovement` 封装所有 `Rigidbody2D` 操作,各 State 通过调用其方法间接控制物理:
|
||
|
||
### 移动接口
|
||
|
||
| 方法 | 调用时机 | 说明 |
|
||
|------|---------|------|
|
||
| `Move(float speedX)` | `RunState.OnStateFixedUpdate` | 设置水平速度 |
|
||
| `Jump(bool isVariable)` | `AirState.OnStateEnter` | 施加跳跃冲量 |
|
||
| `CutJump()` | `AirState`(松开跳跃键)| 减半垂直速度(可变跳跃高度)|
|
||
| `Dash(Vector2 direction)` | `DashState.OnStateEnter` | 施加冲刺速度 |
|
||
| `ApplyKnockback(DamageInfo)` | `HurtState.OnStateEnter` | 根据 DamageInfo 施加击退 |
|
||
| `ZeroVelocity()` | `IdleState.OnStateEnter` | 清零速度 |
|
||
| `ZeroHorizontalVelocity()` | `HurtState` 等 | 仅清零水平速度 |
|
||
|
||
### 地面检测
|
||
|
||
- 使用 `Physics2D.OverlapBox` 在脚部检测 Ground Layer
|
||
- 结果每帧更新,通过 `PlayerController.IsGrounded` 对外暴露
|
||
- 支持单向平台(`OneWayPlatform` Layer,向下跳跃时临时忽略)
|
||
|
||
### 墙壁检测
|
||
|
||
- 左右两侧各一个 `Physics2D.Raycast` 检测 Wall Layer
|
||
- `IsWallLeft` / `IsWallRight` 供 `AirState` 判断墙跳/墙滑
|
||
|
||
---
|
||
|
||
## 4. PlayerStats — 属性系统
|
||
|
||
`PlayerStats` 持有玩家当前**运行时属性**,初始值来自 `PlayerStatsSO`,通过事件频道对外广播变化:
|
||
|
||
### 属性表
|
||
|
||
| 属性 | 类型 | 初始值 | 说明 |
|
||
|------|------|--------|------|
|
||
| `CurrentHP` | `int` | `StatsSO.MaxHP` | 当前生命值 |
|
||
| `MaxHP` | `int` | 配置 SO | 最大生命值(心脏容器增加)|
|
||
| `CurrentSoulPower` | `int` | 0 | 当前灵力(0-100),近战命中积累,用于魂技能 |
|
||
| `MaxSoulPower` | `int` | 100 | 灵力上限 |
|
||
| `CurrentSpiritPower` | `int` | `StatsSO.MaxSpiritPower` | 当前魄元(0-100),随时间自动回复,用于魄技能 |
|
||
| `MaxSpiritPower` | `int` | 配置 SO | 魄元上限 |
|
||
| `CurrentSpringCharges` | `int` | `StatsSO.MaxSpringCharges` | 当前灵泉使用次数,击杀敌人积累点数达阙后增加 |
|
||
| `MaxSpringCharges` | `int` | 配置 SO | 灵泉次数上限(探索可升级)|
|
||
| `SpringKillPoints` | `int` | 0 | 当前击杀点数(达阙后清零)|
|
||
| `SpringKillThreshold` | `int` | 配置 SO | 增加 1 次灵泉所需击杀点数 |
|
||
| `CurrentGeo` | `int` | 0 | 当前货币 |
|
||
| `InvincibleTimer` | `float` | 0 | 无敌帧剩余时间 |
|
||
| `IsInvincible` | `bool` | false | 是否处于无敌状态 |
|
||
| `CurrentGeo` | `int` | 0 | 当前货币 |
|
||
| `InvincibleTimer` | `float` | 0 | 无敌帧剩余时间 |
|
||
| `IsInvincible` | `bool` | false | 是否处于无敌状态 |
|
||
|
||
### 方法(供外部调用)
|
||
|
||
| 方法 | 调用方 | 说明 |
|
||
|------|--------|------|
|
||
| `TakeDamage(int amount)` | `HurtBox` → `PlayerController` | 扣血,触发 `OnHPChanged` 频道 |
|
||
| `HealHP(int amount)` | `SpringState` | 回血,触发频道 |
|
||
| `AddSoulPower(int amount)` | 攻击命中调用 | 增加灵力(上限 MaxSoulPower)|
|
||
| `ConsumeSoulPower(int amount)` | `SkillManager`(魂技能) | 消耗灵力,返回 bool 表示是否成功 |
|
||
| `AddSpiritPower(int amount)` | 自动恢复计时器 | 增加魄元 |
|
||
| `ConsumeSpiritPower(int amount)` | `SkillManager`(魄技能) | 消耗魄元,返回 bool |
|
||
| `AddKillPoints(int points)` | 敌人死亡调用 | 增加击杀点数,自动检查是否增加灵泉次数 |
|
||
| `UseSpring()` | `SpringState` | 消耗 1 次灵泉,返回 bool |
|
||
| `RestoreSpringCharges()` | `SavePoint` 交互 | 恢复灵泉次数至上限 |
|
||
| `AddGeo(int amount)` | `Collectible.OnPickup` | 增加 Geo,触发频道 |
|
||
| `BeginInvincibility(float duration)` | `HurtState` / `DashState` | 开始无敌帧(带计时)|
|
||
| `HasAbility(AbilityType)` | 各 State | 检查能力是否解锁 |
|
||
| `UnlockAbility(AbilityType)` | `AbilityUnlock` 世界物件 | 解锁能力 |
|
||
|
||
### Soul 获取规则
|
||
|
||
> **灵力**:近战技能驱动资源
|
||
|
||
| 来源 | 灵力增量 |
|
||
|------|----------|
|
||
| 攻击命中普通敌人 | +10 |
|
||
| 攻击命中 Boss | +5 |
|
||
| 弹反成功 | +20 |
|
||
| 受到伤害 | +0 |
|
||
|
||
> **魄元**:自动回复资源
|
||
|
||
| 回复时机 | 魄元回复量 |
|
||
|---------|----------|
|
||
| 每秒自动回复 | +`SpiritRegenRate`(配置 SO)|
|
||
| 使用魄技能 | -技能消耗 |
|
||
|
||
> **灵泉使用次数**:击杀驱动资源
|
||
|
||
| 来源 | 点数 |
|
||
|------|------|
|
||
| 击杀景通敌人 | +1 点 |
|
||
| 击杀精英怪 | +3 点 |
|
||
| 击杀 Boss | +5 点 |
|
||
点数积累至 `SpringKillThreshold` 后自动+1次灵泉使用次数,点数清零。
|
||
|
||
### 三资源生命周期总览
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────┐
|
||
│ 灵力(SoulPower) 资源性质:近战积累,不自动恢复 │
|
||
├──────────────────────────────────────────────────────────────────┤
|
||
│ 增加来源: │
|
||
│ 近战命中普通敌人 +10 (PlayerCombat → OnHitBoxTriggered) │
|
||
│ 近战命中 Boss +5 (PlayerCombat → OnHitBoxTriggered) │
|
||
│ 弹反成功 +33 (ParrySystem → OnParrySuccess) │
|
||
│ 消耗: │
|
||
│ 魂技能施放 −技能 baseCost (SkillManager.TryCastSoul) │
|
||
│ 重置为 0: │
|
||
│ 玩家死亡 (HurtState Death 进入时) │
|
||
│ 存档点读取时按 SaveData 恢复 │
|
||
└──────────────────────────────────────────────────────────────────┘
|
||
|
||
┌──────────────────────────────────────────────────────────────────┐
|
||
│ 魄元(SpiritPower) 资源性质:自动恢复,持续消耗 │
|
||
├──────────────────────────────────────────────────────────────────┤
|
||
│ 增加来源: │
|
||
│ 时间自动恢复 +SpiritRegenRate/s (PlayerStats.Update) │
|
||
│ 消耗: │
|
||
│ 魄技能 1/2 施放 −技能 baseCost (SkillManager.TryCastSpirit) │
|
||
│ 重置为最大: │
|
||
│ 死亡复活(返回最近存档点) │
|
||
│ 存档点交互(同时恢复灵泉) │
|
||
│ 不受影响: │
|
||
│ 受伤不消耗魄元 │
|
||
└──────────────────────────────────────────────────────────────────┘
|
||
|
||
┌──────────────────────────────────────────────────────────────────┐
|
||
│ 灵泉次数(SpringCharges) 资源性质:击杀驱动,有限次数 │
|
||
├──────────────────────────────────────────────────────────────────┤
|
||
│ 增加来源(击杀点数): │
|
||
│ 击杀普通敌人 +1 pt → 积累到阈值 → +1次 (PlayerStats) │
|
||
│ 击杀精英怪 +3 pt → 积累到阈值 → +1次 │
|
||
│ 击杀 Boss +5 pt → 积累到阈值 → +1次 │
|
||
│ 消耗: │
|
||
│ 使用灵泉(按键) −1次 (SpringState → PlayerStats.UseSpring) │
|
||
│ 恢复为最大: │
|
||
│ 与存档点(SavePoint)交互 │
|
||
│ 死亡复活(不恢复,死亡惩罚的一部分) │
|
||
│ 上限升级: │
|
||
│ 拾取"灵泉上限容器"(类 Hollow Knight 心脏容器) │
|
||
│ → PlayerStats.MaxSpringCharges++ │
|
||
└──────────────────────────────────────────────────────────────────┘
|
||
|
||
三资源联动规则:
|
||
灵力满格(100)→ 魂技能可用(SkillManager 检查 CurrentSoulPower ≥ baseCost)
|
||
魄元满格(100)→ 魄技能可多次连用(无额外奖励,数值设计时注意不要让满魄元过强)
|
||
灵泉耗尽(0) → UseSpring 输入被 PlayerStats.UseSpring() 拒绝(返回 false),
|
||
SpringState 不进入
|
||
```
|
||
|
||
---
|
||
|
||
## 5. PlayerCombat — 战斗管理
|
||
|
||
`PlayerCombat` 管理攻击**连击题**和 **HitBox 开关**,并考虑**当前形态**的攻击动画适配:
|
||
|
||
### 连击状态机
|
||
|
||
```
|
||
ComboState 枚举:
|
||
None → Attack1 → Attack2 → Attack3 → None (重置)
|
||
↑____连击窗口内输入_____| (各形态均 3 连击,岗開/动作组各形态独立)
|
||
```
|
||
|
||
| 连击段 | 触发方式 | HitBox | 伤害倍率 | 说明 |
|
||
|--------|---------|--------|---------|------|
|
||
| `Attack1` | 第一次攻击输入 | `HitBox_Ground` | ×1 | 水平支行(各形态动画不同)|
|
||
| `Attack2` | 连击窗口内再按攻击 | `HitBox_Ground` | ×1 | 连击第 2 击 |
|
||
| `Attack3` | 连击窗口内再按攻击 | `HitBox_Ground` | ×2 | 第 3 击(重击)|
|
||
| `AirAttack` | 空中攻击 | `HitBox_Air` | ×1 | 空中水平支行 |
|
||
| `UpAttack` | MoveY > 0.5 时攻击 | `HitBox_Up` | ×1 | 上劈,角色微小下移 |
|
||
| `DownAttack` | 空中 + MoveY < -0.5 | `HitBox_Down` | ×1 | 下劈,命中弹起(Pogo)|
|
||
| `ParryCounter` | 弹反成功后攻击 | `HitBox_Ground` | ×3 | 不可被弹反 |
|
||
|
||
### 形态攻击适配
|
||
|
||
各形态的连击段数和功能一致(3 连击 + 空中/上/下),但动作组、HitBox 尺寸、伤害数值完全不同。`PlayerCombat` 订阅 `WeaponManager.OnWeaponChanged`,形态切换时自动刷新所有攻击数据:
|
||
|
||
```csharp
|
||
// PlayerCombat 监听武器切换事件(形态切换 → WeaponManager → PlayerCombat)
|
||
_weaponManager.OnWeaponChanged += RefreshWeaponData;
|
||
|
||
// RefreshWeaponData:刷新动画片段 + DamageSource + HitBox 尺寸 + ResetCombo()
|
||
// 详见 53_WeaponSystem §5
|
||
```
|
||
|
||
### 连击窗口时长
|
||
|
||
| 参数 | 值 | 位置 |
|
||
|------|-----|------|
|
||
| `ComboWindowDuration` | 0.5s | `PlayerCombatConfigSO` |
|
||
| `ComboResetDelay` | 0.3s(Attack3 后)| `PlayerCombatConfigSO` |
|
||
|
||
连击窗口由 `AttackState.OnAttackAnimationComplete` 事件触发开启,超时后 `ComboState` 回到 `None`。
|
||
|
||
---
|
||
|
||
## 6. FormController — 形态系统
|
||
|
||
`FormController` 管理三种形态的切换、当前形态的技能居子、以及形态切换过渡。
|
||
|
||
### FormSO — 形态数据
|
||
|
||
```csharp
|
||
[CreateAssetMenu(menuName = "Player/Form")]
|
||
public class FormSO : ScriptableObject
|
||
{
|
||
[Header("形态标识")]
|
||
public string formId; // 存档 + 识别 ID,如 "SkyForm"(替代枚举,彻底数据驱动)
|
||
public string displayName; // "天魂"
|
||
public Sprite formIcon; // HUD 图标
|
||
public Color formAccentColor; // HUD/动画特效色调
|
||
|
||
[Header("默认武器")]
|
||
/// <summary>该形态绑定的默认武器 SO(替代旧 FormAttackConfig 内联结构体)</summary>
|
||
public WeaponSO defaultWeapon; // 连击动画 + DamageSources + HitBox 配置,见 53_WeaponSystem
|
||
|
||
[Header("技能配置")]
|
||
public FormSkillSO soulSkill; // 魂技能(消耗灵力)
|
||
public FormSkillSO spiritSkill1; // 魄技能 1(消耗魄元)
|
||
public FormSkillSO spiritSkill2; // 魄技能 2(消耗魄元)
|
||
}
|
||
// 注:FormType 枚举已移除。使用 formId 字符串识别形态。
|
||
// 新增形态只需:① 创建新 FormSO 资产 ② 加入 FormConfigSO.forms[] ③ 配置对应输入槽
|
||
// FormAttackConfig 已废弃——其所有字段已迁移至 WeaponSO(见 53_WeaponSystem §2)。
|
||
```
|
||
|
||
### FormController 组件
|
||
|
||
```csharp
|
||
namespace BaseGames.Player
|
||
{
|
||
/// <summary>
|
||
/// 形态控制器。通过 FormConfigSO 持有所有可用形态,
|
||
/// 不硬编码具体形态数量——新增形态只需创建新 FormSO 并加入 FormConfigSO.forms[]。
|
||
/// </summary>
|
||
public class FormController : MonoBehaviour
|
||
{
|
||
[SerializeField] FormConfigSO _config; // 持有所有可用形态的配置 SO
|
||
[SerializeField] InputReaderSO _input;
|
||
|
||
[Header("事件频道—发布")]
|
||
[SerializeField] FormEventChannelSO _onFormChanged; // HUD、动画层监听
|
||
|
||
public FormSO CurrentForm { get; private set; }
|
||
public FormSO[] AllForms => _config.forms; // WeaponManager 全形态枚举时使用
|
||
public event Action OnFormChanged;
|
||
|
||
void Awake()
|
||
{
|
||
// 默认激活第一个形态(Index 0 = 天魂)
|
||
if (_config.forms.Length > 0)
|
||
CurrentForm = _config.forms[0];
|
||
}
|
||
|
||
void OnEnable()
|
||
{
|
||
// 形态切换按固定槽位 Index 映射输入事件:
|
||
// Slot 0 (SkyForm) → SwitchSkyFormEvent (键 1 / D-Pad Left)
|
||
// Slot 1 (EarthForm) → SwitchEarthFormEvent (键 2 / D-Pad Down)
|
||
// Slot 2 (DeathForm) → SwitchDeathFormEvent (键 3 / D-Pad Right)
|
||
// 若未来扩展第 4 形态,只需在 InputReaderSO 中增加 SwitchForm4Event 并绑定此处
|
||
_input.SwitchSkyFormEvent += () => SwitchToIndex(0);
|
||
_input.SwitchEarthFormEvent += () => SwitchToIndex(1);
|
||
_input.SwitchDeathFormEvent += () => SwitchToIndex(2);
|
||
}
|
||
|
||
void OnDisable()
|
||
{
|
||
_input.SwitchSkyFormEvent -= () => SwitchToIndex(0);
|
||
_input.SwitchEarthFormEvent -= () => SwitchToIndex(1);
|
||
_input.SwitchDeathFormEvent -= () => SwitchToIndex(2);
|
||
}
|
||
|
||
void SwitchToIndex(int index)
|
||
{
|
||
if (index < 0 || index >= _config.forms.Length) return;
|
||
SwitchForm(_config.forms[index]);
|
||
}
|
||
|
||
void SwitchForm(FormSO newForm)
|
||
{
|
||
if (newForm == null || newForm == CurrentForm) return;
|
||
CurrentForm = newForm;
|
||
_onFormChanged.Raise(newForm);
|
||
OnFormChanged?.Invoke();
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### FormConfigSO — 形态配置容器
|
||
|
||
```csharp
|
||
/// <summary>
|
||
/// 持有全部可用形态的有序列表。
|
||
/// Index 对应输入槽(0 = SkyForm, 1 = EarthForm, 2 = DeathForm …)。
|
||
/// 新增形态时:① 创建 FormSO ② 加入此数组尾部 ③ 配置新输入槽,无需改动 FormController。
|
||
/// </summary>
|
||
[CreateAssetMenu(menuName = "Player/FormConfig")]
|
||
public class FormConfigSO : ScriptableObject
|
||
{
|
||
public FormSO[] forms; // 有序列表,Index 即输入槽编号
|
||
}
|
||
```
|
||
|
||
**资产存放**:`Assets/ScriptableObjects/Player/FormConfig.asset`
|
||
**检验规范**:Custom Inspector 在 `forms.Length == 0` 时显示黄色警告框。
|
||
|
||
### FormEventChannelSO
|
||
|
||
```csharp
|
||
[CreateAssetMenu(menuName = "Events/FormEventChannel")]
|
||
public class FormEventChannelSO : ScriptableObject
|
||
{
|
||
public event Action<FormSO> OnEventRaised;
|
||
public void Raise(FormSO form) => OnEventRaised?.Invoke(form);
|
||
}
|
||
```
|
||
|
||
### HUD 形态切换预设效果
|
||
|
||
形态切换本身**无前摇**(不中断当前动作),下一次攻击/技能输入时输出新形态动作。如需前摇,在 `FormSO.SwitchInAnimation`(可选)中配置。
|
||
|
||
---
|
||
```
|
||
|
||
| 连击段 | 触发方式 | HitBox | 伤害倍率 | 说明 |
|
||
|--------|---------|--------|---------|------|
|
||
| `Attack1` | 第一次攻击输入 | `HitBox_Ground` | ×1 | 横向挥击 |
|
||
| `Attack2` | 连击窗口内再按攻击 | `HitBox_Ground` | ×1 | 斜上挥击 |
|
||
| `Attack3` | 连击窗口内再按攻击 | `HitBox_Ground` | ×2 | 下砸重击 |
|
||
| `AirAttack` | 空中攻击 | `HitBox_Air` | ×1 | 下斩 |
|
||
| `UpAttack` | MoveY > 0.5 时攻击 | `HitBox_Up` | ×1 | 上挑 |
|
||
| `ParryCounter` | 弹反成功后攻击 | `HitBox_Ground` | ×3 | 不可被弹反 |
|
||
|
||
### 连击窗口时长
|
||
|
||
| 参数 | 值 | 位置 |
|
||
|------|-----|------|
|
||
| `ComboWindowDuration` | 0.5s | `PlayerCombatConfigSO` |
|
||
| `ComboResetDelay` | 0.3s(Attack3 后)| `PlayerCombatConfigSO` |
|
||
|
||
连击窗口由 `AttackState.OnAttackAnimationComplete` 事件触发开启,超时后 `ComboState` 回到 `None`。
|
||
|
||
---
|
||
|
||
## 7. Animancer 双层动画系统
|
||
|
||
```
|
||
AnimancerComponent
|
||
├── Layer[0] — 全身动画
|
||
│ 权重: 1.0,AvatarMask: 全身
|
||
│ 播放: Idle / Run / Fall / Land / Hurt / Death / Dash / UseSpring / Interact / WallGrab
|
||
│
|
||
└── Layer[1] — 上半身覆盖(攻击/弹反/技能)
|
||
权重: 0.0 → 1.0(攻击/技能时),结束后淡出回 0.0
|
||
AvatarMask: UpperBody(脊椎以上所有骨骼)
|
||
IsAdditive: false(覆盖模式)
|
||
播放: Attack1/2/3 / AirAttack / UpAttack / DownAttack / SoulSkill / SpiritSkill1 / SpiritSkill2
|
||
/ ParryPose / ParrySuccess / ParryCounter
|
||
```
|
||
|
||
**双层动画效果**:
|
||
|
||
| 玩家状态 | Layer[0] | Layer[1] | 视觉效果 |
|
||
|---------|---------|---------|---------|
|
||
| 奔跑 | Run | 无(权重=0)| 仅奔跑 |
|
||
| 奔跑时攻击 | Run(继续)| Attack1 | 腿部奔跑 + 上身攻击 |
|
||
| 空中攻击 | Fall | AirAttack | 下落姿态 + 下斩动作 |
|
||
| 弹反 | ParryPose(全身)| 无 | 弹反姿态(全身,Layer[1]关闭)|
|
||
|
||
---
|
||
|
||
## 7. PlayerAnimationConfigSO
|
||
|
||
`PlayerAnimationConfigSO` 存放所有 `ClipTransition`,Inspector 中拖入对应 AnimationClip:
|
||
|
||
### Layer[0] 全身动画
|
||
|
||
| 字段名 | Fade 时长 | 说明 |
|
||
|--------|----------|------|
|
||
| `Idle` | 0.2s | 待机循环 |
|
||
| `Run` | 0.1s | 奔跑循环 |
|
||
| `JumpRise` | 0.05s | 跳跃上升 |
|
||
| `JumpFall` | 0.15s | 下落 |
|
||
| `Land` | 0.05s | 落地(接 Idle/Run)|
|
||
| `Dash` | 0.05s | 冲刺 |
|
||
| `Hurt` | 0.05s | 受击 |
|
||
| `Death` | 0.1s | 死亡(不循环)|
|
||
| `Interact` | 0.1s | 交互动作 |
|
||
| `WallSlide` | 0.1s | 墙壁滑行(解锁后)|
|
||
| `Swim` | 0.2s | 游泳循环(P1,解锁后)|
|
||
|
||
### Layer[1] 上半身动画
|
||
|
||
| 字段名 | Fade 时长 | AnimationEvent | 说明 |
|
||
|--------|----------|---------------|------|
|
||
| `Attack1` | 0.05s | `EnableHitBox` Frame 3, `DisableHitBox` Frame 8 | 第1击 |
|
||
| `Attack2` | 0.05s | `EnableHitBox` Frame 3, `DisableHitBox` Frame 7 | 第2击 |
|
||
| `Attack3` | 0.05s | `EnableHitBox` Frame 2, `DisableHitBox` Frame 10 | 第3击(重)|
|
||
| `AirAttack` | 0.05s | `EnableHitBox` Frame 2, `DisableHitBox` Frame 8 | 空中下斩 |
|
||
| `UpAttack` | 0.05s | `EnableHitBox` Frame 3, `DisableHitBox` Frame 7 | 上挑 |
|
||
| `ParryPose` | 0.08s | — | 弹反等待姿态(Layer[0]) |
|
||
| `ParrySuccess` | 0.05s | — | 弹反成功(Layer[0])|
|
||
| `ParryCounter` | 0.05s | `EnableHitBox` Frame 2 | 弹反反击 |
|
||
|
||
---
|
||
|
||
## 9. FSM 状态体系
|
||
|
||
使用 `Animancer.FSM.StateMachine<PlayerStateBase>` 作为 FSM 骨架:
|
||
|
||
### 状态优先级(数字越高越优先抢占)
|
||
|
||
| 优先级 | 状态 | 说明 |
|
||
|--------|------|------|
|
||
| 100 | `HurtState` (Death) | 死亡,不可被打断 |
|
||
| 90 | `HurtState` (Alive) | 受击,只能被死亡打断 |
|
||
| 80 | `DashState` | 冲刺无敌,只能被受伤死亡打断 |
|
||
| 75 | `SoulSkillState` | 魂技能前摇,可被冲刺/受伤打断 |
|
||
| 72 | `SpiritSkillState` | 魄技能前摇,可被冲刺/受伤打断 |
|
||
| 70 | `ParryState` | 弹反,可被受伤打断 |
|
||
| 60 | `AttackState` | 攻击(Layer[1]),可被弹反/受伤打断 |
|
||
| 55 | `SpringState` | 用灵泉(前摇可打断)|
|
||
| 40 | `InteractState` | 交互,可被大多数行为打断 |
|
||
| 35 | `WallGrabState` | 抓墙悬挂,可被冲刺/技能打断 |
|
||
| 30 | `AirState` | 空中,可被攻击/弹反/冲刺打断 |
|
||
| 20 | `RunState` | 奔跑 |
|
||
| 10 | `SwimState` | 游泳(P1)|
|
||
| 0 | `IdleState` | 待机(最低)|
|
||
|
||
### 状态转换规则
|
||
|
||
```
|
||
任意状态
|
||
├─ 受击(HP > 0) → HurtState (Alive) [强制 ForceState]
|
||
├─ 受击(HP = 0) → HurtState (Death) [强制 ForceState]
|
||
└─ (具体见下方状态详细设计)
|
||
|
||
IdleState
|
||
├─ 移动输入 → RunState
|
||
├─ 跳跃输入 → AirState
|
||
├─ 攻击输入 → AttackState (Layer 1)
|
||
├─ 弹反输入 → ParryState
|
||
├─ 冲刺输入 → DashState
|
||
├─ SoulSkill + 灵力足够 → SoulSkillState
|
||
├─ SpiritSkill1/2 + 魄元足够 → SpiritSkillState
|
||
├─ UseSpring + 灵泉次数≥1 → SpringState
|
||
└─ 交互输入 + 有互动目标 → InteractState
|
||
|
||
RunState
|
||
├─ 无移动输入 → IdleState
|
||
├─ 跳跃输入 → AirState
|
||
├─ 攻击输入 → AttackState (Layer 1)(保持 RunState,Layer[1] 叠加)
|
||
├─ 弹反输入 → ParryState
|
||
├─ 冲刺输入 → DashState
|
||
├─ SoulSkill/SpiritSkill → 对应技能 State
|
||
└─ UseSpring → SpringState
|
||
|
||
AirState(统一处理跳跃/双跳/下落)
|
||
├─ 落地 → IdleState 或 RunState(视水平输入)
|
||
├─ 跳跃输入(双跳可用)→ 重置 AirState(在 AirState 内部处理,不切换状态)
|
||
├─ 贴墙 + 朝墙输入 → WallGrabState(见 26_WallMechanicsSystem)
|
||
├─ 攻击输入 → AttackState (Layer 1)
|
||
├─ 弹反输入 → ParryState
|
||
└─ 冲刺输入 + 冲刺可用 → DashState
|
||
|
||
WallGrabState(抓墙悬挂,见 26_WallMechanicsSystem)
|
||
├─ 跳跃输入 → 蹬墙跳(背墙/对墙,内部判断)→ AirState
|
||
├─ 按反方向键或落地 → AirState / IdleState
|
||
├─ 冲刺输入 → DashState
|
||
└─ 技能输入 → 对应技能 State
|
||
|
||
DashState
|
||
├─ 冲刺时长结束 → AirState 或 IdleState/RunState
|
||
└─ (冲刺无敌,忽略受击)
|
||
|
||
AttackState(Layer[1] 叠加,Layer[0] 保持原状态)
|
||
├─ 动画结束 → Layer[1] 淡出,恢复原状态(Idle/Run/Air)
|
||
├─ 连击窗口内攻击输入 → 切换至下一连击段
|
||
└─ 弹反输入 → ParryState(打断攻击,Layer[1] 立即淡出)
|
||
|
||
SoulSkillState / SpiritSkillState(Layer[1] 叠加)
|
||
├─ 前摇动画结束 → 执行技能效果
|
||
└─ 冲刺/受伤打断 → DashState / HurtState(技能费用已扣除)
|
||
|
||
SpringState
|
||
├─ 前摇完成 → 立即回血,进入后摇
|
||
├─ 前摇被打断 → IdleState(灵泉次数已扣,不返还)
|
||
└─ 后摇结束 → IdleState/RunState
|
||
|
||
ParryState
|
||
├─ 弹反成功 → 播放 ParrySuccess,开启反击窗口
|
||
│ ↓ 反击窗口内攻击 → AttackState (ParryCounter)
|
||
│ ↓ 反击窗口超时 → IdleState/RunState
|
||
└─ 弹反超时 → IdleState/RunState
|
||
|
||
HurtState
|
||
└─ 硬直时长结束 → IdleState
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 状态详细设计
|
||
|
||
### PlayerAirState — 空中状态
|
||
|
||
AirState 内部管理多种空中子行为(不拆分为多个 State,避免频繁切换):
|
||
|
||
| 空中行为 | 触发条件 | 说明 |
|
||
|---------|---------|------|
|
||
| **初始跳跃** | 地面按下跳跃 / Coyote Time | 施加 `JumpForce` 冲量 |
|
||
| **可变跳跃高度** | 松开跳跃键时速度仍向上 | `CutJump()`:垂直速度截断 |
|
||
| **双跳** | 空中按下跳跃 + `HasDoubleJump` + 未使用过 | 重置垂直速度 + 施加 `DoubleJumpForce` |
|
||
| **跳跃顶点缓降** | 上升速度接近 0 | 重力系数降低,轻盈漂浮感 |
|
||
| **下垂加速** | 垂直速度 < 0 | 额外重力 × `FallMultiplier` |
|
||
| **抑制单向平台** | 下方输入 + 跳跃键 | 临时忽略 `OneWayPlatform` Layer |
|
||
|
||
### PlayerDashState — 冲刺状态
|
||
|
||
| 参数 | 值 | 位置 |
|
||
|------|-----|------|
|
||
| `DashSpeed` | 18 units/s | `PlayerMovementConfigSO` |
|
||
| `DashDuration` | 0.18s | `PlayerMovementConfigSO` |
|
||
| `DashCooldown` | 0.5s | `PlayerMovementConfigSO` |
|
||
| `InvincibleDuringDash` | true | 冲刺全程无敌帧 |
|
||
| `CanDashInAir` | false(P0) / true(解锁后)| `PlayerStats.HasAerialDash` |
|
||
|
||
### PlayerHurtState — 受击/死亡状态
|
||
|
||
```
|
||
OnStateEnter(DamageInfo info)
|
||
1. 播放 Hurt 动画(Layer[0])
|
||
2. PlayerMovement.ApplyKnockback(info)
|
||
3. PlayerStats.BeginInvincibility(IFrameDuration)
|
||
4. PlayerFeedback.OnTakeHit()
|
||
↓
|
||
若 HP = 0:
|
||
5. 播放 Death 动画
|
||
6. 禁用所有输入(InputReader.DisableAllInput)
|
||
7. 延迟 1.2s 后触发 OnPlayerDied 事件频道
|
||
```
|
||
|
||
---
|
||
|
||
## 11. 能力解锁系统
|
||
|
||
`AbilityType` 枚举定义所有可解锁能力,`PlayerStats.HasAbility(type)` 在各 State 中查询:
|
||
|
||
| 能力枚举值 | 触发状态改变 | 说明 |
|
||
|-----------|-----------|------|
|
||
| `DoubleJump` | `AirState`:允许双跳 | 默认锁定 |
|
||
| `WallGrab` | `WallGrabState`:允许抓墙 | 默认**解锁**(基础能力)|
|
||
| `AerialDash` | `DashState`:允许空中冲刺 | 默认锁定 |
|
||
| `InvincibleDash` | `DashState`:冲刺全程无敌 | 默认锁定(升级冲刺)|
|
||
| `Swim` | 接触水体不死亡,进入 `SwimState` | 默认锁定(P1)|
|
||
| `Parry` | `ParryState`:弹反可用 | 默认**解锁** |
|
||
|
||
能力解锁通过 `AbilityUnlock` 世界物件(见 `08_WorldSystem`)触发,调用 `PlayerStats.UnlockAbility(type)`,同步到 `SaveData`。
|
||
|
||
## 12. PlayerMovementConfigSO
|
||
|
||
所有移动相关数值,策划友好配置:
|
||
|
||
| 参数 | 类型 | 推荐值 | 说明 |
|
||
|------|------|--------|------|
|
||
| `WalkSpeed` | `float` | 6 | 地面行走速度(units/s)|
|
||
| `RunSpeed` | `float` | 9 | 奔跑速度(触发加速后)|
|
||
| `JumpForce` | `float` | 16 | 跳跃冲量 |
|
||
| `DoubleJumpForce` | `float` | 14 | 双跳冲量 |
|
||
| `WallJumpAwayHForce` | `float` | 10 | 蓬墙跳—背墙跳水平分量 |
|
||
| `WallJumpTowardHForce` | `float` | 3 | 蓬墙跳—对墙跳水平分量 |
|
||
| `WallJumpAwayHForce` | `float` | 10 | 蓬墙跳—背墙跳水平分量 |
|
||
| `WallJumpTowardHForce` | `float` | 3 | 蓬墙跳—对墙跳水平分量 |
|
||
| `WallJumpVerticalForce` | `float` | 15 | 蓬墙跳垂直分量(两种共用)|
|
||
| `WallGrabSlideSpeed` | `float` | 2 | 抓墙超出高度记忆值时强制下滑速度 |记忆值时强制下滑速度 |
|
||
| `DashSpeed` | `float` | 18 | 冲刺速度 |
|
||
| `DashDuration` | `float` | 0.18 | 冲刺持续时间 |
|
||
| `DashCooldown` | `float` | 0.5 | 冲刺冷却 |
|
||
| `FallMultiplier` | `float` | 2.5 | 下坠额外重力倍率 |
|
||
| `LowJumpMultiplier` | `float` | 2.0 | 短跳额外重力倍率 |
|
||
| `CoyoteTimeDuration` | `float` | 0.12 | 土狼时间 |
|
||
| `JumpBufferDuration` | `float` | 0.15 | 跳跃缓冲窗口 |
|
||
| `GroundCheckDistance` | `float` | 0.05 | 地面检测距离 |
|
||
| `InvincibleAfterHit` | `float` | 1.2 | 受击无敌帧时长 |
|
||
|
||
---
|
||
|
||
## 12. 编辑器友好设计
|
||
|
||
### PlayerController 自定义 Inspector
|
||
|
||
Play Mode 下显示完整运行时状态:
|
||
|
||
```
|
||
┌─ PlayerController ─────────────────────────────┐
|
||
│ Current State : WallGrabState │
|
||
│ 形态 : 天魂 [Sky] │
|
||
│ HP : ████████████░░ 8 / 10 │
|
||
│ 灵力 : ███████░░░░░░░ 70 / 100 │
|
||
│ 魄元 : ████████████░░ 85 / 100 │
|
||
│ 灵泉 : 3 / 5 次 │
|
||
│ Geo : 340 │
|
||
│ IsGrounded : ✓ │
|
||
│ FacingDir : → (+1) │
|
||
│ Invincible : ░░░░░░░░░░ (0.00s) │
|
||
│ ─────────────────────────────────────────────── │
|
||
│ Abilities: [DoubleJump ✓] [WallGrab ✓] │
|
||
│ [AerialDash ✗] [InvDash ✗] │
|
||
│ ─────────────────────────────────────────────── │
|
||
│ [强制 Idle] [强制 Run] [强制 Air] [Hurt 2] │
|
||
│ [AerialDash ✗] [InvDash ✗] │
|
||
│ ─────────────────────────────────────────────── │
|
||
│ [强制 Idle] [强制 Run] [强制 Air] [Hurt 2] │
|
||
└───────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Scene 视图 Gizmos
|
||
|
||
- **地面检测区域**:绘制绿色/红色矩形(绿=接触地面,红=空中)
|
||
- **HitBox 范围**:绘制各方向 HitBox 橙色矩形(仅激活时显示)
|
||
- **墙体检测 Raycast**:绘制左右两侧蓝色射线
|
||
|
||
---
|
||
|
||
## 14. 空中冲刺(Aerial Dash)
|
||
|
||
> **依赖能力**:`AbilityType.AerialDash`(默认锁定,探索解锁)
|
||
|
||
空中冲刺是《丝之歌》的核心移动机制:玩家在空中获得一次额外冲刺机会,大幅扩展垂直与横向移动自由度。
|
||
|
||
### 13.1 设计参数
|
||
|
||
| 参数 | 值 | 位置 |
|
||
|------|-----|------|
|
||
| `AerialDashSpeed` | 15 units/s(地面冲刺的 83%)| `PlayerMovementConfigSO` |
|
||
| `AerialDashDuration` | 0.20s | `PlayerMovementConfigSO` |
|
||
| `AerialDashCooldown` | 独立计次(见下方)| — |
|
||
| `InvincibleDuringAerialDash` | true(与地面冲刺相同)| — |
|
||
| `AerialDashCharges` | 1(固定,护符可扩展至 2)| `PlayerStats` |
|
||
| `AerialDashDirection` | 8 方向(左摇杆/键盘方向)| `PlayerMovement` |
|
||
|
||
### 13.2 充能重置条件
|
||
|
||
空中冲刺充能(Charge)在以下情况**重置为最大值**:
|
||
|
||
| 条件 | 说明 |
|
||
|------|------|
|
||
| 落地 | 触地瞬间立即恢复 |
|
||
| 墙跳触发后 | 墙跳给予新的空中冲刺机会(允许垂直攀升)|
|
||
| 命中敌人(HitBox 触发有效命中)| 《丝之歌》核心机制:攻击命中即恢复(鼓励进攻性空中移动)|
|
||
| 弹反成功 | 弹反也视为"命中",给予充能恢复 |
|
||
|
||
```csharp
|
||
// PlayerStats 追踪充能
|
||
int _aerialDashChargesRemaining;
|
||
|
||
public void OnAerialDashUsed() => _aerialDashChargesRemaining--;
|
||
public void ResetAerialDashCharges() => _aerialDashChargesRemaining = MaxAerialDashCharges;
|
||
public bool HasAerialDashCharge => _aerialDashChargesRemaining > 0
|
||
&& HasAbility(AbilityType.AerialDash);
|
||
```
|
||
|
||
```csharp
|
||
// PlayerCombat:有效命中时通知充能重置
|
||
void OnHitBoxTriggered(HurtInfo hurt)
|
||
{
|
||
if (hurt.hitCount > 0) // 有效命中(不是空挥)
|
||
_stats.ResetAerialDashCharges();
|
||
}
|
||
```
|
||
|
||
### 13.3 DashState 扩展(空中冲刺分支)
|
||
|
||
`DashState` 内部区分地面冲刺和空中冲刺:
|
||
|
||
```csharp
|
||
public class DashState : PlayerStateBase
|
||
{
|
||
bool _isAerialDash;
|
||
|
||
public override void OnStateEnter()
|
||
{
|
||
var isGrounded = _movement.IsGrounded;
|
||
_isAerialDash = !isGrounded;
|
||
|
||
float speed = _isAerialDash ? _config.AerialDashSpeed : _config.DashSpeed;
|
||
float duration = _isAerialDash ? _config.AerialDashDuration : _config.DashDuration;
|
||
|
||
// 方向:空中冲刺支持 8 方向;地面冲刺仅水平
|
||
Vector2 dir = _isAerialDash
|
||
? GetAerialDashDirection()
|
||
: new Vector2(_movement.FacingDir, 0);
|
||
|
||
_movement.BeginDash(dir, speed, duration);
|
||
_stats.BeginInvincibility(duration);
|
||
|
||
if (_isAerialDash)
|
||
_stats.OnAerialDashUsed();
|
||
|
||
PlayDashAnimation(_isAerialDash, dir);
|
||
}
|
||
|
||
Vector2 GetAerialDashDirection()
|
||
{
|
||
var input = _inputReader.MoveInput;
|
||
// 若无输入,默认朝向当前面朝方向(水平)
|
||
return input.sqrMagnitude > 0.01f
|
||
? GetClosest8Direction(input)
|
||
: new Vector2(_movement.FacingDir, 0);
|
||
}
|
||
|
||
// 将任意输入方向量化为 8 个标准方向之一
|
||
static Vector2 GetClosest8Direction(Vector2 input)
|
||
{
|
||
float angle = Mathf.Atan2(input.y, input.x) * Mathf.Rad2Deg;
|
||
float snapped = Mathf.Round(angle / 45f) * 45f;
|
||
float rad = snapped * Mathf.Deg2Rad;
|
||
return new Vector2(Mathf.Cos(rad), Mathf.Sin(rad)).normalized;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 13.4 可进入 DashState 的新条件
|
||
|
||
在 FSM 转换中,`AirState` 现增加额外判断:
|
||
|
||
```
|
||
AirState
|
||
...(原有转换)
|
||
└─ 冲刺输入 + HasAerialDashCharge → DashState(空中冲刺)
|
||
```
|
||
|
||
`CanDashInAir` 由 `PlayerStats.HasAerialDashCharge` 替代,原 `CanDashInAir` bool 参数废弃。
|
||
|
||
### 13.5 视觉反馈
|
||
|
||
| 阶段 | 效果 | 组件 |
|
||
|------|------|------|
|
||
| 空中冲刺激活 | 冲刺拖影颜色变为蓝紫色(区别于地面冲刺白色)| `TrailRenderer` 参数切换 |
|
||
| 充能耗尽 | HitBox 周围短暂显示灰色粒子(无充能警示)| `OnAerialDashUsed` 调用 Feel FB |
|
||
| 充能恢复(命中)| 玩家周围一圈蓝色闪光 | `OnHitBoxTriggered` 调用 Feel FB |
|
||
| 落地恢复 | 轻微光环扩散 | `OnLand` 事件触发(可选,避免频繁播放)|
|
||
|
||
### 13.6 参数更新(PlayerMovementConfigSO)
|
||
|
||
在 §11 参数表末尾新增:
|
||
|
||
| 参数 | 类型 | 推荐值 | 说明 |
|
||
|------|------|--------|------|
|
||
| `AerialDashSpeed` | `float` | 15 | 空中冲刺速度 |
|
||
| `AerialDashDuration` | `float` | 0.20 | 空中冲刺持续时间 |
|
||
| `MaxAerialDashCharges` | `int` | 1 | 默认最大充能(护符扩展至 2)|
|
||
|
||
---
|
||
|
||
## 14. 下冲(Plunge / Down Dash)
|
||
|
||
> **依赖能力**:无(基础能力)
|
||
> **触发条件**:空中 + 向下输入 + 冲刺键
|
||
|
||
下冲是垂直向下的特殊冲刺,落地时产生冲击波效果,可对地面敌人造成 AoE 伤害。
|
||
|
||
### 14.1 设计参数
|
||
|
||
| 参数 | 值 | 说明 |
|
||
|------|-----|------|
|
||
| `PlungeSpeed` | 28 units/s | 下冲速度(比空中冲刺更快)|
|
||
| `PlungeImpactRadius` | 2.0 units | 落地冲击波范围 |
|
||
| `PlungeImpactDamage` | `BaseDamage × 2.0` | 冲击波伤害 |
|
||
| `PlungeStagger` | 强硬直(0.8s)| 范围内所有敌人 |
|
||
| `PlungeInvincible` | true(下冲全程)| 无敌帧 |
|
||
| `PlungeConsumesDash` | true | 消耗冲刺充能(与空中冲刺互斥,同一次空中只能选一个)|
|
||
|
||
### 14.2 触发条件与优先级
|
||
|
||
```
|
||
AirState 内部判断(优先级从高到低):
|
||
1. 向下输入 + 冲刺键 + 非下落过快 → PlungeState(下冲)
|
||
2. 任意方向输入 + 冲刺键 + HasAerialDashCharge → DashState(空中冲刺)
|
||
3. 冲刺键(无方向输入) + HasAerialDashCharge → DashState(水平空中冲刺)
|
||
```
|
||
|
||
### 14.3 PlungeState 实现
|
||
|
||
```csharp
|
||
public class PlungeState : PlayerStateBase
|
||
{
|
||
bool _hasLanded;
|
||
|
||
public override void OnStateEnter()
|
||
{
|
||
_hasLanded = false;
|
||
// 取消垂直速度,施加极大向下速度
|
||
_movement.SetVelocity(new Vector2(_movement.Velocity.x * 0.2f, -_config.PlungeSpeed));
|
||
_stats.BeginInvincibility(_config.PlungeDuration);
|
||
_stats.OnAerialDashUsed(); // 消耗充能(共用空中冲刺充能)
|
||
PlayAnimation("Plunge_Descend");
|
||
}
|
||
|
||
public override void OnStateFixedUpdate()
|
||
{
|
||
if (_hasLanded) return;
|
||
if (_movement.IsGrounded)
|
||
TriggerImpact();
|
||
}
|
||
|
||
void TriggerImpact()
|
||
{
|
||
_hasLanded = true;
|
||
PlayAnimation("Plunge_Impact");
|
||
|
||
// 冲击波 AoE
|
||
var hits = Physics2D.OverlapCircleAll(
|
||
_movement.transform.position,
|
||
_config.PlungeImpactRadius,
|
||
_enemyLayer);
|
||
|
||
foreach (var hit in hits)
|
||
{
|
||
if (hit.TryGetComponent<IDamageable>(out var target))
|
||
target.TakeDamage(new DamageInfo
|
||
{
|
||
damage = _combat.CalculateDamage(_config.PlungeImpactDamage),
|
||
knockback = (hit.transform.position - _movement.transform.position).normalized * 5f,
|
||
isParryable = false, // 落地冲击不可弹反
|
||
staggerDuration = 0.8f,
|
||
});
|
||
}
|
||
|
||
// 演出
|
||
_feedback.PlayFeedbacks("PlungeImpact");
|
||
|
||
// 轻微回弹(保持手感)
|
||
_movement.SetVelocity(new Vector2(_movement.Velocity.x, 4f));
|
||
|
||
// 延迟后转换到 Idle/Run
|
||
StartCoroutine(ExitAfterDelay(0.15f));
|
||
}
|
||
|
||
IEnumerator ExitAfterDelay(float delay)
|
||
{
|
||
yield return new WaitForSeconds(delay);
|
||
_fsm.TrySetState(_movement.IsGrounded
|
||
? (PlayerStateBase)_idle
|
||
: _air);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 14.4 视觉反馈
|
||
|
||
| 阶段 | 效果 |
|
||
|------|------|
|
||
| 下冲开始 | 玩家 Sprite 变形(向上拉伸 × 1.3,Feel Squash) |
|
||
| 下冲中 | 速度线粒子(向上冒出)|
|
||
| 落地冲击 | 地面冲击圈(白色扩散环)+ 屏幕震动(Feel Shake Camera)+ 尘土粒子 |
|
||
| 冲击范围可视(仅 Debug)| Gizmos 绘制绿色圆圈 |
|