# 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("默认武器")] /// 该形态绑定的默认武器 SO(替代旧 FormAttackConfig 内联结构体) 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 { /// /// 形态控制器。通过 FormConfigSO 持有所有可用形态, /// 不硬编码具体形态数量——新增形态只需创建新 FormSO 并加入 FormConfigSO.forms[]。 /// 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 /// /// 持有全部可用形态的有序列表。 /// Index 对应输入槽(0 = SkyForm, 1 = EarthForm, 2 = DeathForm …)。 /// 新增形态时:① 创建 FormSO ② 加入此数组尾部 ③ 配置新输入槽,无需改动 FormController。 /// [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 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` 作为 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(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 绘制绿色圆圈 |