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,454 @@
# 14 · 进程系统区域组织·能力门·Boss 进程)
> **命名空间** `BaseGames.Progression`
> **所属文档集** [← 返回索引](./README.md) · [总览](./00_Overview.md)
> **依赖** `BaseGames.Core.Events` · `BaseGames.World`SaveSystem· `BaseGames.Player`(能力查询)
---
## 目录
1. [系统总览](#1-系统总览)
2. [区域Region划分](#2-区域region划分)
3. [能力门AbilityGate](#3-能力门abilitygate)
4. [进程锁ProgressLock](#4-进程锁progresslock)
5. [Boss 进程管理](#5-boss-进程管理)
6. [HP 容器升级流程](#6-hp-容器升级流程)
7. [收集品进度追踪](#7-收集品进度追踪)
8. [SaveData 字段扩展](#8-savedata-字段扩展)
9. [区域解锁与地图联动](#9-区域解锁与地图联动)
10. [事件频道](#10-事件频道)
11. [编辑器友好设计](#11-编辑器友好设计)
---
## 1. 系统总览
进程系统解决"类银河恶魔城游戏如何组织超大地图进程"的核心问题:
```
进程系统职责:
├─ 区域划分 → RegionDefinitionSO 定义区域元数据
├─ 能力门 → AbilityGate 阻挡没有对应能力的玩家
├─ 进程锁 → ProgressLock 阻挡未满足条件的通路
├─ Boss 进程 → Boss 击败解锁通往新区域的大门
├─ HP 容器升级 → 拾取后 MaxHP++ 并触发 UI 演出
└─ 收集品追踪 → 完成度百分比(成就/结局条件)
```
---
## 2. 区域Region划分
### 区域枚举
游戏世界分为以下区域,按开放顺序排列:
| 区域 ID | 中文名 | 特点 | Boss | 开放条件 |
|---------|--------|------|------|---------|
| `Forest` | 扎根森林 | 初始区域,基础敌人 | 蛛网守卫 | 无(起始区域)|
| `Cave` | 腐蚀洞穴 | 黑暗区域,大量陷阱 | 蚀骨蠕虫 | 击败蛛网守卫 |
| `Ruins` | 坍塌废墟 | 需要冲刺能力 | 废墟遗骑士 | 获得冲刺能力 |
| `Abyss` | 深渊裂隙 | 需要双跳,垂直地图 | 深渊之喉 | 击败废墟遗骑士 |
| `Core` | 核心熔炉 | 终局区域 | 最终Boss | 击败深渊之喉 |
### RegionDefinitionSO
每个区域一个 SO 资产,集中管理区域元数据:
```csharp
[CreateAssetMenu(menuName = "Progression/RegionDefinition")]
public class RegionDefinitionSO : ScriptableObject
{
public string regionId; // 如 "Cave"(与 AudioZone 的 regionId 一致)
public string displayName; // 如 "腐蚀洞穴"
public Color mapColor; // 地图 UI 上该区域的颜色标识
public Sprite mapIconSprite; // P1地图图标
[Header("解锁条件")]
public string requiredBossDefeated; // 空字符串 = 无条件
public AbilityType requiredAbility; // None = 无要求
[Header("关联房间")]
public string[] roomSceneNames; // 该区域包含的所有场景名
public string bossSceneName; // Boss 房间场景名
public string entrySceneName; // 从外部进入该区域的第一个房间
}
```
资产路径:`Assets/ScriptableObjects/Progression/Regions/`
---
## 3. 能力门AbilityGate
`AbilityGate` 阻止未解锁对应能力的玩家通过,是 Metroidvania 空间门控的核心组件:
```csharp
public class AbilityGate : MonoBehaviour
{
[SerializeField] AbilityType _requiredAbility; // 需要的能力类型
[SerializeField] GameObject _blockingObject; // 阻挡碰撞体(如悬崖、矮墙、荆棘)
[SerializeField] GameObject _hintUI; // 提示 UI如能力图标 + "???"
void Start()
{
// 零耦合:从注入的 SaveData 查询,而非通过 SaveManager.Instance
bool hasAbility = _saveData != null
? _saveData.HasAbility(_requiredAbility)
: false;
_blockingObject.SetActive(!hasAbility);
_hintUI.SetActive(!hasAbility);
}
// _saveData 由 GameInitializer 在场景加载时注入
SaveData _saveData;
// 监听能力解锁事件,实时更新(玩家在已进入的区域解锁能力时即时生效)
void OnEnable() => _onAbilityUnlocked.OnEventRaised += OnAbilityUnlocked;
void OnDisable() => _onAbilityUnlocked.OnEventRaised -= OnAbilityUnlocked;
void OnAbilityUnlocked(string abilityId)
{
if (abilityId != _requiredAbility.ToString()) return;
_blockingObject.SetActive(false);
// P1播放解锁动画如荆棘收缩、道路开通特效
}
}
```
### AbilityGate 类型对照表
| 能力 | 门控表现形式 | 场景示例 |
|------|------------|---------|
| `DoubleJump` | 过高的空中平台,单跳无法到达 | 无法单跳到达的洞穴上层 |
| `Dash` | 跨越无平台的水平间隙(距离 > 单跳能到) | 废墟区域宽沟壑 |
| `WallJump` | 两侧光滑高墙的竖井,无法借力 | 深渊垂直上升通道 |
| `Swim` | P1充满液体的通道无法通过 | 深渊底部的地下湖 |
---
## 4. 进程锁ProgressLock
`ProgressLock` 是单向/永久性阻挡,需满足特定条件(如击败特定 Boss 或持有特定道具)才能解锁:
```csharp
public class ProgressLock : MonoBehaviour
{
[Header("解锁条件")]
[SerializeField] string _requiredBossId; // 空 = 不检查Boss
[SerializeField] string _requiredItemId; // 空 = 不检查道具P1
[Header("物理表现")]
[SerializeField] GameObject _lockedVisuals; // 锁住状态的视觉(如大门、封印石)
[SerializeField] GameObject _unlockedVisuals; // 开启状态的视觉(可选,或直接禁用)
[SerializeField] Collider2D _blockCollider;
[Header("存档")]
[SerializeField] string _lockId; // 唯一 ID存档中记录开启状态
void Start()
{
bool isUnlocked = CheckUnlocked();
ApplyState(isUnlocked);
}
bool CheckUnlocked()
{
var save = SaveManager.Instance.Data;
if (!string.IsNullOrEmpty(_requiredBossId) && !save.defeatedBosses.Contains(_requiredBossId))
return false;
if (!string.IsNullOrEmpty(_requiredItemId) && !save.ownedItems.Contains(_requiredItemId))
return false;
return save.unlockedProgressLocks.Contains(_lockId);
}
void ApplyState(bool unlocked)
{
_blockCollider.enabled = !unlocked;
_lockedVisuals.SetActive(!unlocked);
if (_unlockedVisuals != null)
_unlockedVisuals.SetActive(unlocked);
}
}
```
### 典型用途
| 场景 | lockId 示例 | 解锁条件 |
|------|------------|---------|
| Forest → Cave 的大门 | `Lock_Forest_ToCave` | 击败蛛网守卫bossId=`Boss_SpiderGuard`|
| 废墟入口封印石 | `Lock_Ruins_Entry` | 持有冲刺符文itemId=`Item_DashRune`|
| 深渊入口遗迹门 | `Lock_Abyss_Entry` | 击败废墟遗骑士 |
---
## 5. Boss 进程管理
Boss 击败信息集中存储在 `SaveData.defeatedBosses` 字符串集合中,由各 `ProgressLock``RegionDefinitionSO` 查询。
### BossProgressTracker轻量辅助组件
挂载在 Boss 房间的 `BossTrigger` 同一对象上:
```csharp
public class BossProgressTracker : MonoBehaviour
{
[SerializeField] string _bossId; // 如 "Boss_SpiderGuard"
[SerializeField] string[] _unlocksProgressLockIds; // 击败后解锁哪些 ProgressLock
void OnEnable() => _onBossDefeated.OnEventRaised += OnBossDefeated;
void OnDisable() => _onBossDefeated.OnEventRaised -= OnBossDefeated;
void OnBossDefeated(string bossId)
{
if (bossId != _bossId) return;
// 1. 通过事件频道通知 SaveSystem 记录(零耦合)
_onBossDefeatedForSave.Raise(new BossDefeatedPayload
{
bossId = bossId,
progressLockIds = _unlocksProgressLockIds,
});
// 2. 广播区域解锁事件(地图 UI 更新)
_onRegionUnlocked.Raise(bossId);
}
}
```
---
## 6. HP 容器升级流程
HP 容器Heart Container是永久 MaxHP 增量物件,拾取后触发完整 UI 演出:
```csharp
public class HPContainerPickup : MonoBehaviour
{
[SerializeField] string _collectibleId; // 存档用唯一 ID
void OnTriggerEnter2D(Collider2D other)
{
if (!other.CompareTag("Player")) return;
// 通过注入的 SaveData 引用检查,而非 SaveManager.Instance
if (_saveData != null && _saveData.collectedItems.Contains(_collectibleId)) return;
StartCoroutine(PickupSequence(other.transform));
}
SaveData _saveData; // 由 GameInitializer 在 Awake 时注入
IEnumerator PickupSequence(Transform player)
{
// 1. 禁用玩家输入(进入短暂无敌+输入锁定状态)
_inputReader.EnableGameplayInput(false);
// 2. 隐藏本物件
gameObject.SetActive(false);
// 3. 播放获取特效(中央放大的心形光效,全屏暗化)
// Feel MMF_Player: Flash + Scale + Particles
// 4. 等待特效完成0.8s
yield return new WaitForSeconds(0.8f);
// 5. MaxHP + 2每个容器增加 2 HP
// 零耦合:通过事件频道通知 SaveSystem携带新 MaxHP 值
_onMaxHPContainerPickedUp.Raise(new HPContainerPayload { collectibleId = _collectibleId });
// SaveSystem 收到事件后执行data.maxHP += 2; data.currentHP = data.maxHP; data.collectedItems.Add(id); Save();
// 6. 广播给 UI 系统更新显示SaveSystem 修改完数据后再广播真实值,这里仅预估触发动画)
_onMaxHPChanged.Raise(_saveData != null ? _saveData.maxHP : 0);
_onHPChanged.Raise(_saveData != null ? _saveData.maxHP : 0);
// 8. 恢复输入
yield return new WaitForSeconds(0.5f);
_inputReader.EnableGameplayInput(true);
}
}
```
**UI 演出细节**
- 全屏轻微暗化Canvas_Overlay 半透明黑色遮罩淡入淡出)
- HPContainer 新增的心形图标从中央飞入到 HUD 左上角
- Soul 槽短暂闪烁
- 音效SFX_HPContainer_Pickup神圣感上升音效
---
## 7. 收集品进度追踪
```csharp
// SaveData 扩展字段(见 §8
// collectionProgress 记录各类收集品的拾取状态
public static class ProgressCalculator
{
// 完成度百分比(用于主菜单存档槽显示 / P2 成就)
public static float GetCompletionPercent(SaveData data, RegionDefinitionSO[] allRegions)
{
int total = /* 所有可收集物件总数,硬编码在 GameConstantsSO 中 */;
int collected = data.collectedItems.Count
+ data.defeatedBosses.Count * 5 // Boss 击败权重更高
+ data.visitedRooms.Count; // P1房间探索率
return Mathf.Clamp01((float)collected / total) * 100f;
}
}
```
---
## 8. SaveData 字段扩展
在 [08_WorldSystem.md §5](./08_WorldSystem.md) 的 SaveData JSON Schema 基础上,进程系统新增字段:
```json
{
"player": {
"maxHP": 6,
"unlockedAbilities": ["Dash", "DoubleJump"],
"ownedItems": ["Item_DashRune"]
},
"progression": {
"defeatedBosses": ["Boss_SpiderGuard"],
"unlockedProgressLocks": ["Lock_Forest_ToCave"],
"unlockedFastTravelIds": ["FT_Forest_01", "FT_Cave_01"],
"visitedRooms": ["Room_Forest_01", "Room_Forest_02"],
"collectedItems": ["HC_Forest_01", "HC_Cave_01"]
}
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| `maxHP` | `int` | 当前最大 HP初始值 6每个容器 +2|
| `unlockedAbilities` | `HashSet<string>` | 已解锁的能力 ID 集合 |
| `defeatedBosses` | `HashSet<string>` | 已击败 Boss 的 bossId 集合 |
| `unlockedProgressLocks` | `HashSet<string>` | 已解锁的 ProgressLock.lockId 集合 |
| `unlockedFastTravelIds` | `HashSet<string>` | 已激活的 Bench 传送点 ID 集合 |
| `visitedRooms` | `HashSet<string>` | P1已访问房间的场景名用于地图显示 |
| `collectedItems` | `HashSet<string>` | 所有可收集物件的唯一 IDHP容器/能力道具等)|
> **性能规范**:所有需要 `.Contains()` 查询的集合字段均使用 `HashSet<string>`O(1) 查询),禁止使用 `string[]` 或 `List<string>`O(n) 遍历。JSON 序列化使用 `Newtonsoft.Json``JsonConvert.DeserializeObject<SaveData>`Newtonsoft 原生支持 `HashSet<T>` 的序列化/反序列化,无需额外转换。
### C# SaveData 结构体(部分)
```csharp
[Serializable]
public class SaveData
{
// 玩家状态
public int maxHP = 6;
public int geoCount = 0;
public int soulCount = 0;
// 进程集合 — 全部 HashSet<string> 保证 O(1) Contains
public HashSet<string> unlockedAbilities = new();
public HashSet<string> defeatedBosses = new();
public HashSet<string> unlockedProgressLocks = new();
public HashSet<string> unlockedFastTravelIds = new();
public HashSet<string> visitedRooms = new();
public HashSet<string> collectedItems = new();
public HashSet<string> exploredRooms = new(); // 地图探索16_MapSystem
// 辅助方法
public bool HasAbility(AbilityType ability)
=> unlockedAbilities.Contains(ability.ToString());
public bool HasDefeatedBoss(string bossId)
=> defeatedBosses.Contains(bossId);
}
```
---
## 9. 区域解锁与地图联动
`OnRegionUnlocked` 事件触发时地图系统P1更新对应区域的显示状态
```
区域解锁流程:
BossProgressTracker.OnBossDefeated()
→ SaveData.defeatedBosses.Add(bossId)
→ OnRegionUnlocked.Raise(bossId)
├─ 相邻 ProgressLock: 检测条件满足 → ApplyState(unlocked=true)(门开启)
├─ P1 MapManager: 将下一区域标记为"可探索"(地图上显示区域轮廓)
└─ P1 UIManager: 短暂显示"新区域解锁"提示(屏幕上方滑入 ToastNotification
```
---
## 10. 事件频道
新增频道(`Assets/ScriptableObjects/Events/Progression/`
| 资产名 | 类型 | 触发时机 |
|--------|------|---------|
| `OnAbilityUnlocked.asset` | `StringEventChannelSO` | 玩家获得新能力,传递 abilityId |
| `OnBossDefeated.asset` | `StringEventChannelSO` | Boss 击败,传递 bossId由 BossBase 触发)|
| `OnRegionUnlocked.asset` | `StringEventChannelSO` | Boss 击败后,传递解锁的区域 ID |
| `OnMaxHPChanged.asset` | `IntEventChannelSO` | MaxHP 增加HP容器拾取后HPContainer UI 响应 |
| `OnProgressLockOpened.asset` | `StringEventChannelSO` | ProgressLock 开启,传递 lockIdP1地图动效|
> `OnAbilityUnlocked.asset` 与 [08_WorldSystem.md §8](./08_WorldSystem.md) 的 `AbilityUnlock` 组件共用同一频道。
---
## 11. 编辑器友好设计
- `AbilityGate` Gizmo在 Scene View 显示对应能力图标(叠加在阻挡碰撞体上方),红色=阻挡中,绿色=已解锁
- `ProgressLock` Custom Inspector显示当前 lockId、解锁条件、运行时是否满足绿色勾选 / 红色叉)
- `RegionDefinitionSO` Custom Inspector列出该区域所有房间场景并标注是否存在路径是否有效
- EditorWindow `ProgressionChecker`:扫描全部场景,列出所有 AbilityGate / ProgressLock检测是否有孤立lockId 未被任何 BossProgressTracker 引用的进程锁UI Toolkit `ListView` + `MultiColumnListView``CreateGUI()` 实现)
---
## 12. 序列越级防护矩阵Sequence Break Prevention
> 配合 [49_AntiSoftlockSystem.md](./49_AntiSoftlockSystem.md) 使用。本节定义各区域的越级风险等级与应对策略。
**越级Sequence Break**定义:玩家在未获得预期能力的情况下,提前进入设计意图为后续区域的内容。
### 12.1 各区域越级风险矩阵
| 越级路径 | 所需绕过的 AbilityGate | 风险等级 | 处理策略 |
|---------|----------------------|---------|---------|
| Forest → Cave跳过 FlorestBoss| `AbilityGate_Cave_Entrance` (无要求) | 🟢 低 | 允许Cave 入口无能力要求)|
| Forest → Ruins跳过 Cave| `AbilityGate_Ruins_MainGate` (需 Dash) | 🟡 中 | Dash 门严格,绕行需精确跳跃(允许但罕见)|
| Cave → Abyss跳过 Ruins| `AbilityGate_Abyss_Bridge` (需 DoubleJump) | 🟡 中 | 允许序列越级速通路线HardAbilityGate显示越级警告 |
| 任意区域 → Core最终区域| `AbilityGate_Core_Gate` (需 Swim+Dash+DoubleJump) | 🔴 高 | 三能力全部严格验证,不允许物理绕过 |
| 获得 Swim 前进入液体深区 | `AbilityGate_DeepWater` (需 Swim) | 🟡 中 | 无 Swim = 液体中失血,玩家自然死亡重生(不软锁)|
| 获得 WallJump 前进入攀墙区 | `AbilityGate_VerticalShaft` (需 WallJump) | 🟢 低 | 可从底部进入,无能力时 SoftlockDetector 兜底 |
### 12.2 HardAbilityGate强制门规范
**普通 AbilityGate** vs **HardAbilityGate** 的区别:
| 属性 | AbilityGate | HardAbilityGate |
|------|------------|----------------|
| 物理阻挡 | 碰撞体阻挡 | 碰撞体阻挡 + Trigger 警告 |
| 绕过提示 | 无 | 显示"此路需要 [能力] 才能安全通过" |
| 序列越级标记 | 无 | `sequenceBreakRisk = true`(供 QA 分析用)|
| 应用场景 | 普通进程门 | 核心区域入口、不可逆操作前 |
```csharp
// HardAbilityGate 仅在 Core 区入口和高风险路径使用
// 其余区域用普通 AbilityGate保留速通路线可能性
[SerializeField] bool sequenceBreakRisk = false;
[SerializeField] string warningLocKey; // 若 sequenceBreakRisk = true显示此警告
```
### 12.3 序列越级分析工具
`EscapeGuaranteeValidator`(见 49_AntiSoftlockSystem §6在执行 BFS 分析时,同时标注所有可能的序列越级路径,输出报告供关卡设计师审查:
```
[越级路径报告]
✓ Forest → Ruins (via precision jump above AbilityGate_Ruins_SideGate)
风险: 低 | 已知速通路线 | SoftlockDetector: 覆盖
⚠ Cave_B3 → Abyss_Top (via wall clip at X=234, Y=89)
风险: 中 | 玩家可能卡在 Abyss 无 DoubleJump | SoftlockDetector: 覆盖
✗ Ruins → Core (无已知越级路径)
HardAbilityGate 三重验证
```