chore: initial commit

This commit is contained in:
2026-05-08 11:04:00 +08:00
commit f55d2a57c3
6278 changed files with 866081 additions and 0 deletions

View File

@@ -0,0 +1,952 @@
# 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.3sAttack3 后)| `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.3sAttack3 后)| `PlayerCombatConfigSO` |
连击窗口由 `AttackState.OnAttackAnimationComplete` 事件触发开启,超时后 `ComboState` 回到 `None`。
---
## 7. Animancer 双层动画系统
```
AnimancerComponent
├── Layer[0] — 全身动画
│ 权重: 1.0AvatarMask: 全身
│ 播放: 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)(保持 RunStateLayer[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
└─ (冲刺无敌,忽略受击)
AttackStateLayer[1] 叠加Layer[0] 保持原状态)
├─ 动画结束 → Layer[1] 淡出恢复原状态Idle/Run/Air
├─ 连击窗口内攻击输入 → 切换至下一连击段
└─ 弹反输入 → ParryState打断攻击Layer[1] 立即淡出)
SoulSkillState / SpiritSkillStateLayer[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` | falseP0 / 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.3Feel Squash |
| 下冲中 | 速度线粒子(向上冒出)|
| 落地冲击 | 地面冲击圈(白色扩散环)+ 屏幕震动Feel Shake Camera+ 尘土粒子 |
| 冲击范围可视(仅 Debug| Gizmos 绘制绿色圆圈 |