17 KiB
49 · 反软锁死系统(Anti-Softlock System)
命名空间
BaseGames.Progression
所属文档集 ← 返回索引 · 总览
依赖BaseGames.Core.Events·BaseGames.World(SaveManager、SceneLoader)·BaseGames.Player(能力查询)
关联 14_ProgressionSystem(能力门/进程锁)· 08_WorldSystem(房间切换)· 31_SaveDataSchema(存档)
目录
- 系统总览
- 软锁死分类
- 房间可达性矩阵
- Sequence Break 防护
- 运行时软锁死检测器
- 紧急传送机制
- 软锁死恢复 UI
- Escape Guarantee 验证工具
- SaveData 集成
- 测试矩阵
- 事件频道
- 编辑器友好设计
1. 系统总览
银河恶魔城(Metroidvania)品类特有的测试挑战:玩家可能凭借技巧或意外路线,在没有必要能力的情况下进入某区域,然后无法离开,导致游戏无法继续推进(软锁死)。
反软锁死系统职责:
├─ 房间可达性矩阵 → 设计时静态验证:每个房间的"逃离路径"是否始终存在
├─ Sequence Break防护 → 防止能力组合绕过能力门进入预期外区域
├─ 运行时检测器 → 检测玩家静止超时 / 卡在特定区域无法脱离
├─ 紧急传送机制 → 触发后安全传送玩家到最近存档点(不损失进度)
└─ 恢复 UI → 提示玩家当前情况并提供选项(非强制打断游戏流)
核心原则:
- 软锁死恢复不惩罚玩家——传送到上一存档点,不扣除 Geo/物品
- 系统检测是辅助性的,主要靠设计时静态分析彻底消除软锁死隐患
- 传送必须有玩家确认,不自动发生(防止误触破坏游戏体验)
2. 软锁死分类
2.1 类型一:能力锁死(Ability Trap)
玩家进入一个区域,该区域有单向出口(如只能向下跳入但无法跳出的深井)。进入后发现无法用当前能力离开。
设计规则:
- 凡有单向入口(只能跌落/仅靠冲刺进入)的区域,出口必须支持无能力通过,或在该区域内提供能力道具
- AbilityGate 只阻挡进入,不阻挡离开(出口方向无能力门)
2.2 类型二:进程锁死(Progress Trap)
玩家解锁了某 ProgressLock(如击败 Boss 后大门开启),进入新区域后在新区域 Boss 战中死亡过多次,Geo 全失,卡在新区域无法回购消耗品。
设计规则:
- 每个区域内有至少一个免费恢复点(灵泉台/免费商品),或进入 Boss 房前强制激活存档点
2.3 类型三:状态锁死(State Trap)
游戏状态出错(如 Boss 战异常中断、剧情触发器失灵)导致玩家被困在一个无门的空间。
解决方案:运行时软锁死检测器 + 紧急传送(见 §5、§6)
2.4 类型四:Sequence Break 后遗症
玩家绕过能力门提前进入高难度区域,在该区域内卡死(无法回头也无法前进)。
解决方案:Sequence Break 防护层(见 §4)
3. 房间可达性矩阵
3.1 EscapeInfo(每个房间必须标注的数据)
每个房间场景在设计时必须用 RoomEscapeInfoSO 标注其逃离条件:
/// <summary>
/// 每个房间必须挂载此 SO,记录"最低能力集合即可离开此房间"
/// 设计时由关卡设计师填写,编辑器工具自动验证可达性
/// </summary>
[CreateAssetMenu(menuName = "Progression/RoomEscapeInfo")]
public class RoomEscapeInfoSO : ScriptableObject
{
[Header("房间标识")]
public string sceneAddress; // 对应 Addressable 场景地址
[Header("逃离要求(AND 关系,满足其一路线即可)")]
public EscapeRoute[] escapeRoutes; // 多条逃离路线(满足任意一条即视为可逃离)
[Header("单向入口警告")]
public bool hasOneWayEntry; // 是否有单向进入点(如跌落入口)
[TextArea(1, 3)]
public string designerNotes; // 设计注意事项
}
[Serializable]
public class EscapeRoute
{
public string routeLabel; // 如 "向左回到 Forest_Main"
public string targetSceneAddress; // 逃离到达的目标房间
public AbilityType[] requiredAbilities; // 空 = 无需任何能力即可离开
}
3.2 区域逃离规则表(设计约束)
| 区域 | 房间 | 逃离条件 | 备注 |
|---|---|---|---|
| Forest | 全部 | 无能力 | 初始区域,永远可徒步离开 |
| Cave | 普通房间 | 无能力(单向跌落入口除外) | 跌落房间必须提供爬出方式 |
| Cave | Boss房间前厅 | 无能力 | Boss 房外必须可回头 |
| Ruins | 普通房间 | 无能力(冲刺可选捷径) | 捷径不阻止不会冲刺的玩家离开 |
| Ruins | 高处秘密房间 | 双跳 OR 无能力(通过台阶可下) | 只进不出的高台禁止存在 |
| Abyss | 普通房间 | 双跳 | 深渊区域合理要求双跳离开 |
| Core | 全部 | 已默认获得所有核心能力 | 终局区域无限制 |
规则:如果某房间的所有 EscapeRoute 中,requiredAbilities 的最低要求超过了玩家进入该房间时理论上能拥有的能力,则视为设计 BUG,编辑器工具会标红警告。
4. Sequence Break 防护
4.1 什么是 Sequence Break
玩家利用技巧(如精准跳跃、速通走位)绕过 AbilityGate 进入预期外的区域。这在社区速通中是正常现象,但在普通游玩中可能导致:
- 进入远超当前实力的区域被反复秒杀
- 在设计边界外卡死
4.2 防护策略
策略 A:双层门控
关键区域入口同时有 AbilityGate(能力检查)+ ProgressLock(Boss 进程检查)。即使玩家绕过能力检查,进程锁仍会物理阻挡。
高难度区域入口:
[AbilityGate: 双跳] ──→ [ProgressLock: 要求已击败Forest_Boss] ──→ 进入区域
策略 B:AbilityGate 增强版——SkillCapCheck
对于能用技巧绕过的能力门,增加技能力检查(跳跃输入次数限制 + 实际位移验证)。
/// <summary>
/// 增强型能力门:除了检查能力标志,还检测玩家当前是否"真的能做到"
/// 用于防止玩家用精准时机绕过只检查标志的 AbilityGate
/// </summary>
public class HardAbilityGate : AbilityGate
{
[Header("额外物理验证")]
[SerializeField] bool _requirePhysicalValidation = false;
// 在关卡测试期间用编辑器工具标记"此门已验证可能被绕过"
[SerializeField] bool _sequenceBreakRisk = false;
protected override bool EvaluateAccess()
{
if (!base.EvaluateAccess()) return false;
if (!_requirePhysicalValidation) return true;
// 对于需要物理验证的门:检查能力实际已激活(非仅标志为 true)
return _playerStats != null && _playerStats.IsAbilityActuallyUnlocked(_requiredAbility);
}
}
策略 C:Sequence Break 日志
当玩家进入标记了 sequenceBreakRisk = true 的区域时,记录遥测事件。用于后续分析玩家行为(见 55_AnalyticsTelemetrySystem)。
5. 运行时软锁死检测器
SoftlockDetector 挂载在 Persistent 场景,全局监听玩家状态:
namespace BaseGames.Progression
{
public class SoftlockDetector : MonoBehaviour
{
[Header("检测参数")]
[SerializeField] float _stuckTimeThreshold = 45f; // 45s 无实质移动 = 疑似卡死
[SerializeField] float _moveThreshold = 0.5f; // 移动阈值(单位:Unity unit)
[Header("依赖")]
[SerializeField] VoidEventChannelSO _onSoftlockSuspected;
[SerializeField] Transform _playerTransform; // 通过 GameInitializer 注入
Vector2 _lastRecordedPosition;
float _stuckTimer;
bool _isDetectorActive = true; // Boss 战/过场动画中临时禁用
void Update()
{
if (!_isDetectorActive || _playerTransform == null) return;
float moved = Vector2.Distance(_playerTransform.position, _lastRecordedPosition);
if (moved > _moveThreshold)
{
_lastRecordedPosition = _playerTransform.position;
_stuckTimer = 0f;
}
else
{
_stuckTimer += Time.deltaTime;
if (_stuckTimer >= _stuckTimeThreshold)
{
_stuckTimer = 0f; // 重置,避免反复触发
_onSoftlockSuspected.Raise();
}
}
}
// Boss 战开始时暂停检测(玩家可能长时间在小范围内战斗)
public void SetDetectorActive(bool active) => _isDetectorActive = active;
}
}
检测器禁用时机
| 场景 | 禁用原因 |
|---|---|
| Boss 战进行中 | 小范围高强度战斗会误触发 |
| 过场动画播放中 | 玩家无控制权 |
| 对话进行中 | 玩家站立不动属正常 |
| 存档点交互动画中 | 玩家静止属正常 |
| 加载画面 | 无玩家对象 |
6. 紧急传送机制
6.1 EscapeTeleporter
namespace BaseGames.Progression
{
/// <summary>
/// 紧急传送服务。由 SoftlockDetector 触发或玩家主动在设置菜单中激活。
/// </summary>
public class EscapeTeleporter : MonoBehaviour
{
[SerializeField] SceneLoaderSO _sceneLoader;
[SerializeField] VoidEventChannelSO _onSoftlockSuspected; // 订阅检测器事件
[SerializeField] SoftlockRecoveryUISO _recoveryUIConfig;
[SerializeField] SaveManager _saveManager; // 注入引用
void OnEnable() => _onSoftlockSuspected.OnEventRaised += OnSoftlockSuspected;
void OnDisable() => _onSoftlockSuspected.OnEventRaised -= OnSoftlockSuspected;
void OnSoftlockSuspected()
{
// 打开恢复 UI(玩家自主选择,不强制传送)
RecoveryUIManager.Instance.Show(_recoveryUIConfig, OnTeleportConfirmed, OnTeleportCancelled);
}
void OnTeleportConfirmed()
{
// 找到当前角色最近的已激活存档点
var nearestSavePoint = _saveManager.GetNearestActivatedSavePoint();
if (nearestSavePoint == null)
{
// Fallback:传送到区域入口存档点
nearestSavePoint = _saveManager.GetRegionEntryPoint();
}
// 传送:不触发死亡,不消耗 Geo/物品,保留当前 HP
_sceneLoader.TeleportToSavePoint(nearestSavePoint, preserveItems: true, preserveHP: true);
}
void OnTeleportCancelled()
{
// 玩家选择继续尝试,重置检测器计时
FindObjectOfType<SoftlockDetector>()?.ResetTimer();
}
}
}
6.2 传送安全规则
| 规则 | 说明 |
|---|---|
| 不触发死亡 | 传送不走死亡流程,不生成遗骸,不重置房间 |
| 不扣 Geo | 正常死亡会在遗骸处保留 Geo,紧急传送直接保留 |
| 不重置消耗品 | 灵泉次数、符文耐久等保持当前值 |
| 保留 Boss 进程 | 若 Boss 已处于濒死状态,传送后 Boss 仍保持受伤状态 |
| 传送冷却 60s | 防止玩家滥用紧急传送作为快速旅行手段 |
7. 软锁死恢复 UI
当 SoftlockDetector 触发时,弹出轻量级提示 UI(不打断游戏流,类似通知横幅):
┌─────────────────────────────────────────────────────┐
│ ⚠ 似乎陷入困境了? │
│ │
│ 可以传送到最近的存档点(不会损失道具或 Geo) │
│ │
│ [ 传送到存档点 ] [ 继续尝试 ] │
└─────────────────────────────────────────────────────┘
- 文案不使用"软锁死"等技术词汇
- 玩家选择"继续尝试"后,检测计时器重置为 20s(缩短,防止反复打扰)
- 如果玩家连续 3 次选择"继续尝试"后仍不动,则在暂停菜单中永久显示"传送到存档点"按钮直到玩家使用
暂停菜单中的手动传送入口
无论是否触发自动检测,玩家可在 Pause → 设置 → 辅助功能 中找到:
"传送到最近存档点"(含 30s 冷却)
8. Escape Guarantee 验证工具
8.1 编辑器窗口
Assets/Editor/EscapeGuaranteeValidator.cs — UI Toolkit EditorWindow
功能:
- 读取所有
RoomEscapeInfoSO,构建房间连通图 - 对每个房间执行逃离可达性 BFS(广度优先搜索):
- 起始状态:玩家在该房间,拥有进入时理论最低能力集
- 目标:能到达任意存档点
- 若某房间无法到达任何存档点 → 标红报错
- 若某房间逃离路线依赖未确认获取的能力 → 标黄警告
Escape Guarantee Validator
═══════════════════════════════════════════════════════════
[▶ 运行验证] [导出报告]
Forest_Main ✅ 可从 3 条路线逃离(最低要求:无)
Forest_SecretCave ✅ 可从 1 条路线逃离(最低要求:无)
Cave_Entrance ✅ 可从 2 条路线逃离(最低要求:无)
Cave_DropShaft_01 ⚠ 仅有 1 条逃离路线(双跳)— 单向入口!
Ruins_HighPlatform ❌ 无逃离路线(BUG:缺少 EscapeRoutes 配置)
═══════════════════════════════════════════════════════════
总计: 47 个房间 | 45 ✅ | 1 ⚠ | 1 ❌
8.2 集成到 CI 流程
# .github/workflows/design-validation.yml(示意)
- name: Escape Guarantee Check
run: unity -batchmode -runEditorTests -testFilter "EscapeGuaranteeTests"
# 若有 ❌ 房间,测试失败,阻止 PR 合并
9. SaveData 集成
在 SaveData 中新增字段,支持紧急传送的状态恢复:
public class WorldSaveData
{
// ...(现有字段)
// 紧急传送记录(调试用,不影响游戏逻辑)
[JsonProperty("escapeTeleportCount")]
public int EscapeTeleportCount { get; set; } = 0;
// 最近激活的存档点 ID(SoftlockDetector 需要此数据确定传送目的地)
[JsonProperty("lastSavePointId")]
public string LastSavePointId { get; set; }
}
10. 测试矩阵
| 测试场景 | 预期结果 | 优先级 |
|---|---|---|
| 跌入单向坑无法出 | 45s 后触发软锁死提示 | P0 |
| Boss 战中长时间僵局 | 检测器禁用,不触发提示 | P0 |
| 对话中长时间站立 | 检测器禁用,不触发提示 | P0 |
| 传送到存档点后物品正常保留 | 无物品损失 | P0 |
| 无存档点激活时传送 | Fallback 到区域入口点 | P1 |
| 速通玩家手动关闭软锁死提示 | 检测重置,下次触发时间延长 | P1 |
| 绕过 AbilityGate 进入 Cave | 记录 SequenceBreak 遥测事件 | P2 |
11. 事件频道
| 频道 SO | 类型 | 触发时机 |
|---|---|---|
OnSoftlockSuspected |
VoidEventChannelSO |
SoftlockDetector 检测到超时 |
OnEscapeTeleportUsed |
StringEventChannelSO |
玩家确认传送(传入目标存档点 ID) |
OnBossFightStarted |
VoidEventChannelSO |
订阅:暂停检测器 |
OnBossFightEnded |
VoidEventChannelSO |
订阅:恢复检测器 |
12. 编辑器友好设计
- SoftlockDetector Inspector:显示当前
_stuckTimer(只读进度条)、检测器开关状态 - AbilityGate Inspector:新增
[SequenceBreakRisk]勾选框(标红显示,提醒 QA 重点测试) - RoomEscapeInfoSO Inspector:每条 EscapeRoute 右侧显示绿/黄/红状态标签
- Escape Guarantee Validator:每次打开 Play Mode 自动后台验证,发现 ❌ 在控制台输出警告
本文档版本 1.0 · 2026-04 · 关联 14_ProgressionSystem / 08_WorldSystem / 31_SaveDataSchema