Files
ichni_Official/docs/offline-content-unlock-system.md

134 lines
7.2 KiB
Markdown
Raw 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.
# Offline 内容解锁系统
> **适用项目**`ichni Official`
> **状态**Core-003 已完成并经 Unity 运行时测试通过
> **最后更新**2026-07-18
本文是章节、歌曲和未来内容使用 Key 解锁机制的唯一使用说明。修改解锁规则、编写 Yarn 解锁命令或排查“内容为什么锁定”时,应先阅读本文。
## 1. 目标与边界
首发版本为纯 Offline。内容解锁记录在玩家本地 ES3 存档中,用于控制章节和歌曲是否可进入;它不是付费凭证、云端权益或反作弊机制。
解锁状态独立于下列数据域:
- 歌曲成绩Accuracy、Max Combo、FC/AP、Chart Revision。
- 剧情记录:故事树 Block、对话选项、Yarn 变量。
- 选曲缓存:本次运行中每章节最后选中的歌曲和难度。
当前不实现 Payment Unlock。未来若接入商店、DLC 或在线 entitlement应由对应模块向本系统授予同样的 Key不得在 UI 或歌曲数据中重新建立支付专用判断。
## 2. 主要代码与职责
| 位置 | 职责 |
|---|---|
| `Assets/Scripts/Saving/UnlockSaveModule.cs` | Key 存档、Schema、规则求值、章节/歌曲统一授权。 |
| `Assets/Scripts/Saving/GameSaveManager.cs` | 启动时创建并加载 `UnlockSaveModule`。 |
| `Assets/Scripts/Menu/ChapterSelection/ChapterSelectionUnit.cs` | 为章节和歌曲配置 `UnlockRequirement`。 |
| `Assets/Scripts/NewStorySystem/YarnFunctions/StoryTreeCommands.cs` | 提供 `grant_unlock` 与兼容的 `unlock_song` Yarn 命令。 |
| `Assets/Scripts/UI/ChapterSelection/ChapterSelectionUI.cs` | 阻止锁定章节进入剧情和选曲。 |
| `Assets/Scripts/UI/SongSelection/PlaySongUI.cs``SongSelectionTab.cs` | 阻止标准 Play 与快速点击绕过锁定。 |
## 3. Key 命名规则
Key 是稳定的内部 ID不是玩家可见文本也不是歌曲标题、章节标题或 Yarn 节点标题。
- 只能包含小写英文字母、数字和下划线。
- 必须以小写字母开头,长度为 196。
- 推荐格式:`来源_章节_事件_状态`
- 示例:`story_ch0_prologue_completed``story_ch0_route_a_completed``chapter_ch1_available`
- 禁止使用点号、空格、连字符、中文/日文等本地化文本,或会频繁更名的显示名称。
不符合规则的 Key 无法被授予;运行时遇到配置错误时会保持锁定并只输出一次警告,避免错误配置意外开放内容。
## 4. 配置章节与歌曲
`ChapterSelectionUnit.unlockRequirement` 控制该章节的剧情入口和选曲入口。`SongItemData.unlockRequirement` 控制歌曲自身。
根节点为空时表示无条件开放。需要锁定时,在 Odin Inspector 的 `Unlock Requirement` 中选择下列节点:
| 节点 | 含义 | 使用示例 |
|---|---|---|
| `Key` | 玩家必须持有该 Key。 | 一首歌曲要求 `story_ch0_prologue_completed`。 |
| `All Of (AND)` | 所有子条件都满足才开放。 | 同时完成两个剧情分支。 |
| `Any Of (OR)` | 满足任意一个子条件即可开放。 | 完成路线 A 或路线 B。 |
真正开始歌曲时,章节规则和歌曲规则必须同时满足。即使歌曲本身无条件开放,只要所属章节锁定,仍不能进入。
`Key`、空的 `All Of` / `Any Of` 列表、或组合中的空子节点都被视为错误配置并失败关闭。
## 5. 从 Yarn 授予 Key
### 通用内容解锁
```yarn
<<grant_unlock story_ch0_prologue_completed>>
```
`grant_unlock` 立即写入本地存档,适用于章节、歌曲、教程和未来任意内容。它本身不显示提示;若需要文案,应在 Yarn 中额外使用 `show_message`
### 兼容的歌曲命令
```yarn
<<unlock_song story_ch0_song_02_available>>
```
`unlock_song` 同样授予 Key并在首次授予后保留旧有歌曲解锁提示行为。当前提示仍会显示英文和内部 Key此显示问题属于后续本地化任务不影响授权结果。
两个命令都具备幂等性:已拥有同一 Key 时不会重复保存或重复弹出歌曲解锁提示。
## 6. 存档、Schema 与清档
- 文件:`Application.persistentDataPath/GameSaves/UnlockKeys.json`
- ES3 数据 Key`UnlockKeys`
- 当前 SchemaUnlock Save v1
本系统保留了旧 `UnlockKeys.json` 的文件名与 ES3 Key。此前仅保存 `HashSet<string>`、但没有 Schema 的预发布文件会作为 v0 无损读取,并补写为 v1文件中不符合当前命名规则的 Key 会被删除。
若读取到非 v1、且不是旧 v0 的 Schema当前预发布策略会重置该文件。正式发布后若变更解锁存档结构必须编写显式迁移不能继续删档。
`StoryManager.ClearAllStorySave()` 会同时清空剧情树、Yarn 变量和全部内容解锁 Key便于测试完整剧情进度。它不会清除歌曲成绩。
## 7. 入口防护
所有现有可进入内容的入口均需遵守同一授权规则:
| 入口 | 防护行为 |
|---|---|
| 章节剧情入口 | 按钮禁用,并在点击回调中再次检查章节规则。 |
| 章节选曲入口 | 按钮禁用,并在点击回调中再次检查章节规则。 |
| 歌曲标准 Play | 通过 `UnlockSaveModule.CanEnterSong(...)` 作最终检查。 |
| 歌曲快速点击 | 使用同一最终检查,不能绕过标准 Play。 |
`SongSelectionTab.isLocked` 只用于锁图标和预览表现,不能作为安全判断的唯一来源。剧情刚授予 Key 或 UI 被其他逻辑刷新后,真正进入前仍会重新计算规则。
## 8. 内容作者工作流
1. 先定义需要代表的进度事实,并按规则起一个稳定 Key。
2. 在 Chapter 或 Song 的 `Unlock Requirement` 中引用这个 Key 或组合规则。
3. 在对应 Yarn 节点完成时添加 `grant_unlock` 或兼容的 `unlock_song`
4. 使用新档测试锁定状态,再完成一次剧情测试授予后的即时状态。
5. 重启游戏确认 Key 仍存在;清空剧情存档后确认内容重新锁定。
不要为了“解锁某首歌”而直接修改 UI、`isLocked``UnlockKeys.json`;所有授予必须经由 `UnlockSaveModule.GrantKey(...)` 或 Yarn 命令。
## 9. 未来扩展原则
- 新内容类型优先复用 `UnlockRequirement``UnlockSaveModule`
- 需要更复杂的组合时在现有规则树中增加节点,不要在各页面复制 if 判断。
- 商店、DLC、成就或云端 entitlement 将来只负责授予/撤销 Key章节和歌曲 UI 不应关心 Key 的来源。
- 如需玩家可见的“为何锁定”说明,应添加本地化提示 Key不要展示内部 Unlock Key。
- `UnlockSaveModule.UnlockStateChanged` 预留给未来需要在当前页面即时刷新解锁状态的 UI。
## 10. 回归测试清单
1. 无规则的章节和歌曲在新档可进入。
2. 配置 `Key` 后,新档中章节/歌曲锁定。
3. 锁定歌曲的标准 Play 和快速点击均无法进入。
4. 锁定章节的剧情入口和选曲入口均无法进入。
5. `grant_unlock` 后规则立即满足;返回菜单后显示正确。
6. 重启后 Key 仍存在。
7. `All Of` 只有全部 Key 存在时开放;`Any Of` 任一 Key 存在时开放。
8. 空 Key 或错误组合不会开放内容,并只产生一次可定位警告。
9. `ClearAllStorySave()` 后剧情进度与解锁 Key 均清除,歌曲成绩保留。