Files
zeling_v2/Docs/Design/40_LiquidSwimSystem.md
2026-05-08 11:04:00 +08:00

359 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 40 · 液态区域与游泳系统Liquid & Swim System
> **命名空间** `BaseGames.World.Liquid`
> **所属文档集** [← 返回索引](./README.md) · [总览](./00_Overview.md)
> **依赖** `BaseGames.Player`PlayerController · AbilitiesSaveData· `BaseGames.Core.Events`
> **关联** 08_WorldSystem §6HazardZone· 03_PlayerSystem §4PlayerState 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
{
/// <summary>
/// 挂在液态区域的根 GameObject 上。
/// 子物件 [Surface] 挂载水面特效触发器;子物件 [Body] 挂载主区域碰撞体。
/// </summary>
[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 PolygonCollider2DIsTrigger
→ LiquidZone.OnTriggerEnter2D(Player)
├─ 播放 _splashEnterFeedbackMMF_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 退出 LiquidZoneOnTriggerExit2D
→ LiquidZone 发布 _onPlayerExited 频道
PlayerController:
→ 恢复正常 Gravity Scale从 LiquidPhysicsConfig.underwaterGravityScale 恢复)
→ FSM 切换至 FallState继承当前 Rigidbody2D.velocity
→ 播放 _splashExitFeedback
```
---
## 6. 酸液/熔岩特化InstantKill 液体)
酸液和熔岩本质上是 **视觉增强的 HazardZone**,实际致死逻辑复用 `HazardZoneInstantKill`
```
[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.250.35 | 越小越"飘"Hollow Knight 水感约 0.3 |
| `swimSpeed` | 4.55.5 | 略低于地面奔跑速度(约 70%|
| `surfaceJumpForce` | 1014 | 跃出水面感觉应比普通跳跃稍低 |
| `drownTime` | 2.53.5 | 太短会惩罚探索;太长失去压迫感 |
---
## 8. 水下视觉效果
### 水下后处理 Profile
进入 `LiquidType.Water` 时,`LiquidZone` 通过 SO 事件频道触发 Post-processing Volume 切换:
```
_onPlayerEntered 广播
→ UnderwaterPostProcessingController 监听
→ 激活 Volume_UnderwaterColor Adjustment青绿色 Filter + 亮度 -0.3
→ 激活 ChromaticAberrationintensity = 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<LiquidZone> OnEventRaised;
public void Raise(LiquidZone zone) => OnEventRaised?.Invoke(zone);
}
```
---
## 11. 编辑器友好设计
### Custom InspectorLiquidZone
```
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*