# 40 · 液态区域与游泳系统(Liquid & Swim System)
> **命名空间** `BaseGames.World.Liquid`
> **所属文档集** [← 返回索引](./README.md) · [总览](./00_Overview.md)
> **依赖** `BaseGames.Player`(PlayerController · AbilitiesSaveData)· `BaseGames.Core.Events`
> **关联** 08_WorldSystem §6(HazardZone)· 03_PlayerSystem §4(PlayerState FSM)· 31_SaveDataSchema §2(`abilities.swim`)
---
## 目录
1. [系统总览](#1-系统总览)
2. [LiquidZone — 液态区域组件](#2-liquidzone--液态区域组件)
3. [液体类型与效果表](#3-液体类型与效果表)
4. [SwimState — 游泳状态](#4-swimstate--游泳状态)
5. [玩家进出液体流程](#5-玩家进出液体流程)
6. [酸液/熔岩特化(InstantKill 液体)](#6-酸液熔岩特化instantkill-液体)
7. [浮力与水下物理参数](#7-浮力与水下物理参数)
8. [水下视觉效果](#8-水下视觉效果)
9. [SaveData 集成](#9-savedata-集成)
10. [事件频道](#10-事件频道)
11. [编辑器友好设计](#11-编辑器友好设计)
---
## 1. 系统总览
液体系统处理地图中的水体、酸液、熔岩等可进入区域,解决:
- **水体**:进入后切换为游泳移动模式(`SwimState`),需要 `swim` 能力解锁
- **酸液/熔岩**:接触即触发即死(委托 `HazardZone InstantKill`,见 08_WorldSystem §6)
- **浮力**:进入液体后重力系数降低,玩家可上浮/下沉
- **水面特效**:进出液体时溅水粒子、水下后处理 Profile
```
液体系统职责:
├─ LiquidZone → 标记液态区域类型、触发进出液体事件
├─ SwimState → PlayerController FSM 中的水下游泳状态
├─ LiquidPhysicsConfig → ScriptableObject,水下物理参数
└─ WaterSurfaceEffect → 进出水面溅水粒子 + 音效
```
**零耦合原则**:`LiquidZone` 通过 SO 事件频道发布进出液体事件,`PlayerController` 订阅后自行切换状态,互不直接引用。
---
## 2. LiquidZone — 液态区域组件
```csharp
namespace BaseGames.World.Liquid
{
///
/// 挂在液态区域的根 GameObject 上。
/// 子物件 [Surface] 挂载水面特效触发器;子物件 [Body] 挂载主区域碰撞体。
///
[RequireComponent(typeof(Collider2D))]
public class LiquidZone : MonoBehaviour
{
[Header("液体类型")]
[SerializeField] LiquidType _liquidType = LiquidType.Water;
[Header("物理配置")]
[SerializeField] LiquidPhysicsConfigSO _physicsConfig;
[Header("事件频道")]
[SerializeField] LiquidEventChannelSO _onPlayerEntered; // payload: LiquidZone
[SerializeField] LiquidEventChannelSO _onPlayerExited; // payload: LiquidZone
[Header("视觉反馈")]
[SerializeField] MMF_Player _splashEnterFeedback;
[SerializeField] MMF_Player _splashExitFeedback;
public LiquidType Type => _liquidType;
public LiquidPhysicsConfigSO Physics => _physicsConfig;
}
}
```
### LiquidZone Prefab 层级结构
```
[LiquidZone_River_01]
├── SpriteRenderer(水体图块精灵,带流动 Shader)
├── PolygonCollider2D (IsTrigger) ← 主区域,检测 Player/Enemy
├── LiquidZone.cs
│
├── [Surface] ← 水面层(分离触发器,检测进出水面)
│ ├── BoxCollider2D (IsTrigger, 1px 高)
│ └── WaterSurfaceEffect.cs ← 触发溅水粒子
│
└── [Hazard](酸液/熔岩时存在)
└── HazardZone.cs (_hazardType = InstantKill)
```
---
## 3. 液体类型与效果表
| `LiquidType` | 行为 | 需要能力 | 特殊说明 |
|-------------|------|---------|---------|
| `Water` | 进入 → 切换 SwimState(需 `swim` 解锁)| `swim` | 无 `swim` 时浮于水面(触发溺水计时)|
| `ShallowWater` | 仅减速,不切换 SwimState,无需能力 | 无 | 水深 ≤ 1.5 tile,适用于浅滩、水坑 |
| `Acid` | 接触即死(HazardZone InstantKill) | — | 效果与 HazardZone 完全相同,LiquidZone 仅负责视觉层 |
| `Lava` | 接触即死 + 持续灼烧特效 | — | 同上,另加火焰粒子 |
| `Mud` | 减速 50%,不切换状态 | 无 | 可结合谜题(踩过留脚印 SaveData 标记)|
---
## 4. SwimState — 游泳状态
`SwimState` 插入 `PlayerController` 的 **Base Layer FSM**,在 `InWater` 分支下工作:
```
Base Layer FSM(新增分支):
AnyState ─── OnEnteredWater ──► SwimState
(LiquidType.Water && HasAbility.Swim)
├─ SwimIdleState
├─ SwimMoveState
├─ SwimDiveState
└─ SwimSurfaceState
SwimState ─── OnExitedWater ──► FallState(离开液体继承速度)
```
### SwimState 子状态
| 子状态 | 触发条件 | 说明 |
|--------|---------|------|
| `SwimIdleState` | 无方向输入 | 缓慢下沉(可配置),循环游泳动画 |
| `SwimMoveState` | 有方向输入(↑↓←→) | 8 向移动,速度受 `swimSpeed` 限制 |
| `SwimDiveState` | 按住 ↓ | 快速下潜,速度 × `diveSpeedMultiplier` |
| `SwimSurfaceState` | 接近水面 + 按 Jump | 跃出水面,施加 `surfaceJumpForce` 冲量 |
### 无 `swim` 能力时的溺水行为
若玩家进入 `LiquidType.Water` 时 **未解锁 `swim` 能力**:
1. 进入 `WaterDangerState`(新增子状态)
2. 玩家可在水面漂浮(按住 Jump 保持)
3. 启动 **溺水计时器**(`drownTime` 秒,默认 3.0s,可配置)
4. 计时器归零 → 触发 `OnPlayerDied`(与其他即死处理一致)
5. 若玩家在溺水期间离开液体,计时器重置
---
## 5. 玩家进出液体流程
### 进入液体
```
Player Rigidbody2D 进入 LiquidZone PolygonCollider2D(IsTrigger)
→ LiquidZone.OnTriggerEnter2D(Player)
├─ 播放 _splashEnterFeedback(MMF_Player:溅水粒子 + 入水音效)
├─ 发布 _onPlayerEntered 频道(payload: this LiquidZone)
└─ (HazardZone 子物件自动处理 InstantKill 逻辑,互不干扰)
PlayerController 监听 _onPlayerEntered:
→ 查询 _liquidType
├─ Water:检查 PlayerStats.HasAbility(swim)
│ ├─ [有] → FSM 切换至 SwimState
│ └─ [无] → FSM 切换至 WaterDangerState(溺水倒计时)
├─ ShallowWater:应用 LiquidPhysicsConfig.shallowSpeedScale
└─ Mud:应用 LiquidPhysicsConfig.mudSpeedScale
```
### 离开液体
```
Player Rigidbody2D 退出 LiquidZone(OnTriggerExit2D)
→ LiquidZone 发布 _onPlayerExited 频道
PlayerController:
→ 恢复正常 Gravity Scale(从 LiquidPhysicsConfig.underwaterGravityScale 恢复)
→ FSM 切换至 FallState(继承当前 Rigidbody2D.velocity)
→ 播放 _splashExitFeedback
```
---
## 6. 酸液/熔岩特化(InstantKill 液体)
酸液和熔岩本质上是 **视觉增强的 HazardZone**,实际致死逻辑复用 `HazardZone(InstantKill)`:
```
[AcidZone_Cave_01]
├── SpriteRenderer(酸液精灵 + 涌动 Shader)
├── LiquidZone.cs (_liquidType = Acid) ← 负责视觉特效触发
├── [Surface]
│ └── WaterSurfaceEffect.cs(酸液飞溅粒子)
└── [Hazard]
└── HazardZone.cs (_hazardType = InstantKill) ← 负责致死逻辑
```
`LiquidZone`(Acid/Lava)在 `OnTriggerEnter2D` 中**不**发布 `_onPlayerEntered` 切换状态事件,只播放视觉反馈;致死由 `HazardZone` 独立处理,避免逻辑重叠。
---
## 7. 浮力与水下物理参数
### LiquidPhysicsConfigSO
```csharp
[CreateAssetMenu(menuName = "World/Liquid/LiquidPhysicsConfig")]
public class LiquidPhysicsConfigSO : ScriptableObject
{
[Header("水下重力")]
public float underwaterGravityScale = 0.3f; // 正常重力 × 0.3 → 缓慢下沉
public float sinkSpeed = 1.5f; // 无操作时下沉速度上限 (u/s)
[Header("游泳速度")]
public float swimSpeed = 5.0f; // 最大游泳速度 (u/s)
public float swimAcceleration = 12.0f; // 加速度
public float diveSpeedMultiplier = 1.8f; // 下潜速度倍率
[Header("跃出水面")]
public float surfaceJumpForce = 12.0f; // 出水跳跃冲量 Y 分量
[Header("浅水/泥泞")]
public float shallowSpeedScale = 0.65f; // 浅水移动速度缩放
public float mudSpeedScale = 0.50f; // 泥泞移动速度缩放
[Header("溺水")]
public float drownTime = 3.0f; // 无能力时溺水计时(秒)
}
```
### 参考调优值(游戏手感)
| 参数 | 推荐值 | 说明 |
|------|-------|------|
| `underwaterGravityScale` | 0.25–0.35 | 越小越"飘",Hollow Knight 水感约 0.3 |
| `swimSpeed` | 4.5–5.5 | 略低于地面奔跑速度(约 70%)|
| `surfaceJumpForce` | 10–14 | 跃出水面感觉应比普通跳跃稍低 |
| `drownTime` | 2.5–3.5 | 太短会惩罚探索;太长失去压迫感 |
---
## 8. 水下视觉效果
### 水下后处理 Profile
进入 `LiquidType.Water` 时,`LiquidZone` 通过 SO 事件频道触发 Post-processing Volume 切换:
```
_onPlayerEntered 广播
→ UnderwaterPostProcessingController 监听
→ 激活 Volume_Underwater(Color Adjustment:青绿色 Filter + 亮度 -0.3)
→ 激活 ChromaticAberration(intensity = 0.4,模拟折射)
→ 屏幕边缘 Vignette 增强(intensity = 0.45)
_onPlayerExited 广播
→ UnderwaterPostProcessingController
→ 平滑过渡回 Volume_Default(过渡时长 0.4s)
```
### WaterSurfaceEffect 组件
```csharp
public class WaterSurfaceEffect : MonoBehaviour
{
[SerializeField] MMF_Player _playerEnterFeedback; // 入水溅水粒子 + 音效
[SerializeField] MMF_Player _playerExitFeedback; // 出水溅水粒子
void OnTriggerEnter2D(Collider2D col)
{
if (col.CompareTag("Player"))
_playerEnterFeedback?.PlayFeedbacks(col.transform.position);
}
void OnTriggerExit2D(Collider2D col)
{
if (col.CompareTag("Player"))
_playerExitFeedback?.PlayFeedbacks(col.transform.position);
}
}
```
---
## 9. SaveData 集成
`abilities.swim` 字段由 `AbilitiesSaveData` 负责(见 31_SaveDataSchema_Unified §3):
```json
"abilities": {
"doubleJump": false,
"wallGrab": false,
"dash": false,
"airDash": false,
"groundSlam": false,
"swim": false
}
```
- 初始值为 `false`(游戏开始时无游泳能力)
- 通过 `AbilityUnlock`(见 08_WorldSystem §8)交互后 `PlayerStats.UnlockAbility(AbilityType.Swim)` 写入存档
- 读档时 `PlayerStats.Init(SaveData)` 将 `swim` 字段同步到 `PlayerStats._abilities` 集合
> **注意**:`LiquidZone` 本身不持有 SaveData 字段;区域是否探索由 `MapSystem`(`discoveredRooms`)统一管理。
---
## 10. 事件频道
| 频道 SO | 类型 | 用途 |
|---------|------|------|
| `OnPlayerEnteredLiquid` | `LiquidEventChannelSO`(payload: `LiquidZone`)| 玩家进入液体 → PlayerController 切换状态 |
| `OnPlayerExitedLiquid` | `LiquidEventChannelSO`(payload: `LiquidZone`)| 玩家离开液体 → 恢复正常物理 |
| `OnPlayerDrownStart` | `VoidEventChannelSO` | 溺水计时开始 → HUD 显示溺水警告UI |
| `OnPlayerDrownCancel` | `VoidEventChannelSO` | 溺水计时取消(离开水体)|
```
LiquidEventChannelSO.cs:
[CreateAssetMenu(menuName = "Events/LiquidEventChannel")]
public class LiquidEventChannelSO : ScriptableObject
{
public event Action OnEventRaised;
public void Raise(LiquidZone zone) => OnEventRaised?.Invoke(zone);
}
```
---
## 11. 编辑器友好设计
### Custom Inspector(LiquidZone)
```
LiquidZone Inspector:
[液体属性]
LiquidType: ● Water ○ ShallowWater ○ Acid ○ Lava ○ Mud
Physics Config: [LiquidPhysicsConfigSO 引用]
[事件频道] (折叠)
[视觉反馈]
SplashEnter: [MMF_Player 引用]
SplashExit: [MMF_Player 引用]
─────────────────────────────
[▶ 预览入水特效] [▶ 预览出水特效] ← Editor Only 按钮
```
### Gizmos
- 编辑模式下 `LiquidZone` 用**半透明蓝色**(Water)/**半透明绿色**(Acid)/**半透明红色**(Lava)填充 PolygonCollider2D 范围
- `WaterSurfaceEffect` 的 1px 触发器在 Scene 视图中显示为**青色细线**
### 关卡搭建建议
```
Layer 命名约定:
Tilemap_Liquid_Water ← 水体图块(仅视觉,无碰撞)
Tilemap_Liquid_Surface ← 水面装饰图块(涟漪动画)
LiquidZone 组件挂在独立 GameObject 上(非 Tilemap),
PolygonCollider2D 手动覆盖液体区域轮廓,精度 ≥ 2 顶点/格。
```
---
*文档版本 1.0 · 2025*