162 lines
11 KiB
Markdown
162 lines
11 KiB
Markdown
# 存档、剧情进度与选曲缓存:当前状态说明
|
||
|
||
> **适用项目**:`ichni Official`
|
||
> **最后更新**:2026-07-18
|
||
> **状态**:已完成 Core-002 的存档与选曲缓存收尾,以及 Core-003 的统一 Offline 内容解锁模块。
|
||
|
||
本文记录当前已落地的运行时存档机制、数据边界、字段语义和维护规则。它不是面向玩家的说明;以后修改存档、谱面、剧情或选曲逻辑时,应先阅读本文。
|
||
|
||
## 1. 发布范围与基本原则
|
||
|
||
- 首发平台为 Windows、Android、iOS;当前目标允许为纯 Offline 版本。
|
||
- Chapter 0 是首发最低内容线;Chapter 1 以实际完成度为准。
|
||
- 首发本地化目标为英文、中文、日文、韩文、越南文。
|
||
- 游戏记录和剧情记录刻意分离:成绩变化不应破坏剧情进度,剧情变量变化也不应改写成绩。
|
||
- 当前仍处于正式发布前。因此两套存档的 Schema 均为 `v1`,出现不兼容旧测试存档时允许删除并重建;正式发布后必须改为显式迁移,不能沿用此策略。
|
||
|
||
## 2. 存档总览
|
||
|
||
| 数据域 | 主要职责 | ES3 文件 | 当前 Schema | 启动加载顺序 |
|
||
|---|---|---|---|---|
|
||
| 游戏记录 | 歌曲完成状态、每个谱面的成绩、Chart Revision | `GameSaves/SongSaves.json` | Song Save v1 | 先加载 |
|
||
| 内容解锁 | 已授予的章节/歌曲/教程等内容访问 Key | `GameSaves/UnlockKeys.json` | Unlock Save v1 | 游戏记录后、菜单显示前加载 |
|
||
| 剧情记录 | 各章节剧情树、选项状态、全局 Yarn 变量 | `StorySaves/*.json`(由 `StorySaveModule` 管理) | Story Save v1 | 游戏记录后加载 |
|
||
| 选曲缓存 | 当前运行期间各章节最后选择的歌曲与难度 | 仅内存 | 不适用 | 按需创建 |
|
||
|
||
`GameSaveManager.Start()` 先初始化 `SongSaveModule` 并加载游戏记录、剧情解锁 Key,再初始化 `StorySaveModule`。菜单和章节 UI 因而可在首次展示前读取成绩和解锁状态。
|
||
|
||
## 3. 游戏记录:歌曲与谱面成绩
|
||
|
||
主要实现位于 `Assets/Scripts/Saving/GameSaveManager.cs`。
|
||
|
||
### 3.1 保存时机
|
||
|
||
一次正常游玩结束后,运行时的 `PlayingRecorder` 会把该局结果交给 `SongSaveModule.RecordPlayResult(...)`。该方法负责:
|
||
|
||
1. 确认 `songName`、`saveDifficultyId` 和本局判定数据有效;
|
||
2. 取得或创建对应 `SongStatusSave` 和 `BeatmapSave`;
|
||
3. 根据当前 `Chart Revision` 判断是否需要先重置该谱面的可比成绩;
|
||
4. 更新最高 Accuracy、最高 Max Combo,以及“曾达成”的 FC/AP;
|
||
5. 将修改后的完整游戏记录和 Song Save Schema 一起写回 ES3。
|
||
|
||
若本局没有任何有效判定(`totalCount <= 0`),不会生成成绩记录,以免异常退出污染数据。
|
||
|
||
### 3.2 字段与含义
|
||
|
||
| 字段 | 所属层级 | 含义与更新规则 |
|
||
|---|---|---|
|
||
| `SongStatusSave.isCompleted` | 歌曲 | 任意一个有效游玩结束后置为 `true`;不会因为较差成绩回退。 |
|
||
| `SongStatusSave.additionalInfo` | 歌曲 | 预留信息;当前保持非空字符串,未承载游戏规则。 |
|
||
| `BeatmapSave.accuracy` | 谱面 | 当前 `Chart Revision` 下的最高 Accuracy。 |
|
||
| `BeatmapSave.maxCombo` | 谱面 | 当前 `Chart Revision` 下的最高 Max Combo。 |
|
||
| `BeatmapSave.isFullCombo` | 谱面 | Sticky 标记:本 Revision 内曾 FC 即保持 `true`。 |
|
||
| `BeatmapSave.isAllPerfect` | 谱面 | Sticky 标记:本 Revision 内曾 AP 即保持 `true`。 |
|
||
| `BeatmapSave.chartRevision` | 谱面 | 成绩所对应的谱面版本;与配置不一致时仅重置该谱面的可比成绩。 |
|
||
|
||
### 3.3 稳定 ID:`saveDifficultyId`
|
||
|
||
谱面成绩的字典 Key 是 `DifficultyData.saveDifficultyId`,而不是难度在 UI 列表中的位置。这样调整难度显示顺序、临时隐藏某难度或为某曲新增难度,都不会把旧成绩错误地归属到另一张谱面。
|
||
|
||
`difficultyListIndex` 只用于选曲 UI 的当前列表位置,不能用作存档 ID。每个需要保存成绩的难度应拥有非负、稳定且在同一歌曲内唯一的 `saveDifficultyId`。
|
||
|
||
### 3.4 `Chart Revision`
|
||
|
||
`Chart Revision` 是谱面内容版本号,不是整个存档的版本号。
|
||
|
||
- 仅改名称、封面、UI 排序等不影响判定内容的修改:不应递增。
|
||
- 改动音符、判定时机、总物量或足以使成绩不可比的规则:应递增。
|
||
- Revision 不一致时:只清空该难度的 Accuracy、Max Combo、FC、AP;歌曲完成状态、其它难度成绩、剧情存档和解锁 Key 均保留。
|
||
|
||
## 4. Schema Version
|
||
|
||
`Schema Version` 描述的是**存档数据结构**,不是游戏版本,也不是 `Chart Revision`。
|
||
|
||
当前 Song Save 和 Story Save 都为 `v1`。加载到不存在 Version 或 Version 不等于当前版本的数据时,预发布策略会删除该域的旧文件并按 v1 重建。这一策略方便当前开发阶段删档验证,但正式发布后必须替换为“读取旧 Version → 逐步迁移 → 写入新 Version”的流程。
|
||
|
||
改变以下内容时通常需要升级对应 Schema:
|
||
|
||
- 已持久化字段改名、删除或改变类型;
|
||
- 字典 Key 的含义改变;
|
||
- 文件或 ES3 Key 的布局改变;
|
||
- 新结构无法安全读取旧结构。
|
||
|
||
仅新增可安全默认初始化的字段时,是否升级 Schema 需按实际反序列化兼容性判断;不要为了“看起来更新了”而机械递增。
|
||
|
||
## 5. 剧情存档
|
||
|
||
剧情存档由 `Assets/Scripts/NewStorySystem/Save/StorySaveModule.cs` 负责。它独立保存章节剧情树、选项/节点状态和全局 Yarn 变量,并有自己的 Story Save Schema v1。
|
||
|
||
剧情存档不得承担歌曲 Accuracy、Combo、FC/AP 等游玩数据;反过来,游戏记录也不得复制 Yarn 变量或剧情节点状态。剧情通过 Yarn 命令授予内容解锁 Key,由独立 `UnlockSaveModule` 决定章节和歌曲是否开放。
|
||
|
||
## 6. Offline 内容解锁(Core-003)
|
||
|
||
主要实现位于 `Assets/Scripts/Saving/UnlockSaveModule.cs`。它是第三个独立存档域:既不是歌曲成绩,也不是剧情树。文件仍使用既有 `GameSaves/UnlockKeys.json`,会把旧的无 Schema 预发布 Key 集合无损补写为 Unlock Save v1。
|
||
|
||
### 6.1 Key 命名与授予
|
||
|
||
- Key 只能使用小写英文字母、数字和下划线,必须以字母开头,长度为 1–96。
|
||
- 推荐按“来源_章节_事件_状态”命名,例如 `story_ch0_prologue_completed`。
|
||
- 不得使用点号、空格、连字符、玩家可见标题、翻译文本或会频繁修改的 Yarn 标题。
|
||
- Yarn 使用 `<<grant_unlock unlock_key>>` 授予通用 Key;既有 `<<unlock_song unlock_key>>` 保持兼容,并在首次授予时继续排队歌曲提示。
|
||
|
||
### 6.2 内容规则
|
||
|
||
章节与歌曲均持有 `UnlockRequirement`。根节点为空表示无条件开放;配置了规则时可使用:
|
||
|
||
- `Key`:玩家必须持有指定 Key。
|
||
- `All Of`:所有子条件满足才开放。
|
||
- `Any Of`:任一子条件满足即可开放。
|
||
|
||
空的 `Key`、空的组合列表或空子节点属于配置错误,会保持锁定并仅在 Console 输出一次警告,避免错误配置意外放行。
|
||
|
||
真正进入歌曲时,当前章节和歌曲自身的规则必须同时满足。标准 Play、快速点击当前歌曲、以及未来的其它直达歌曲入口都必须调用 `UnlockSaveModule.CanEnterSong(...)`。
|
||
|
||
首发纯 Offline 不保留 Payment Unlock 运行时代码。未来若增加商店或其它 entitlement,只能向 `UnlockSaveModule` 授予 Key,不能在 UI 中新增独立支付判断。
|
||
|
||
## 7. 选曲与难度缓存
|
||
|
||
选曲缓存由 `Assets/Scripts/Menu/MenuInformationRecorder.cs` 管理。
|
||
|
||
- 它只存在于内存,重启游戏后不会恢复;这是当前已确认的设计。
|
||
- 缓存单位是章节:记录该章节最后一次选择的歌曲及 `difficultyListIndex`。
|
||
- 玩家重新进入同一次运行中的同一章节时,UI 会优先恢复该选择。
|
||
- 若缓存歌曲已不在章节内、难度索引失效、歌曲没有可用难度,系统会安全回退,不会把空难度交给进入游戏流程。
|
||
|
||
### 6.1 跨歌曲的难度回退规则
|
||
|
||
当玩家从一首歌拖动到另一首歌,而目标歌曲没有当前难度时,系统会选择目标歌曲中“更接近”的可用难度;距离相同则优先较低的列表序号。例如当前选择难度索引为 `2`,目标歌曲只有 `0`、`1`、`3` 可用时选择 `1`。
|
||
|
||
这是一项 UI 选择策略,不会改变 `saveDifficultyId` 或既有成绩归属。
|
||
|
||
## 8. 已加入的运行时防御
|
||
|
||
- 选曲初始化会处理缺失/过期缓存、空歌曲列表和无可用难度的歌曲。
|
||
- 标准 Play 按钮会在歌曲、难度、选中 Tab 缺失或歌曲锁定时拒绝进入。
|
||
- 快速进入路径已防止把空歌曲或空难度传入 `InformationTransistor`,并与标准 Play 共用 `CanEnterSong(...)` 的最终解锁检查。
|
||
- 成绩存档加载后会与当前章节/歌曲/难度定义对账:为新增歌曲和难度创建空记录,并按 Revision 检查已有记录。
|
||
- 已删除的难度记录暂不自动清除;它们不参与当前 UI 和完成度统计,保留可避免内容恢复时丢失历史成绩。
|
||
|
||
## 9. 当前已知边界与待办
|
||
|
||
1. **存档损坏处理**。尚未实现损坏文件恢复、备份或用户提示;已按当前决定延后。
|
||
2. **联网/云同步**。尚未实现;未来加入时应作为独立模块,不能直接把网络状态混入本地成绩、剧情文件或解锁 Key 文件。
|
||
3. **解锁提示本地化**。当前 `ShowUnlockMessage` 仍直接生成英文文本并显示 Key。后续本地化阶段应改为根据内容定义取得已本地化的标题与名称。
|
||
|
||
## 10. 维护与验证清单
|
||
|
||
修改相关逻辑后,至少手动验证:
|
||
|
||
1. 新档首次启动:歌曲记录、剧情记录和解锁 Key 文件能创建。
|
||
2. 完成一次谱面后退出并重启:Accuracy、Max Combo、FC/AP、歌曲完成状态仍正确。
|
||
3. 用较差成绩重复游玩:最高记录和 Sticky FC/AP 不回退。
|
||
4. 提高某谱面的 `Chart Revision` 后重启:仅该难度的可比成绩清空。
|
||
5. 调整难度显示顺序后:成绩仍对应原 `saveDifficultyId`。
|
||
6. 在同一运行中离开并重新进入章节:歌曲/难度恢复;目标歌曲没有同难度时按“最近、同距取低”规则回退。
|
||
7. 对无可用难度:标准 Play 与快速进入都不能开始游戏;对锁定章节或歌曲:剧情入口、选曲入口、标准 Play 和快速进入均无法绕过。
|
||
|
||
最近一次静态 C# 验证:`dotnet build Assembly-CSharp.csproj --no-restore -v:minimal /m:1 /clp:ErrorsOnly`,结果为 **0 errors**。工程现有警告未在本轮逐项审计。
|
||
|
||
## 11. 修改责任边界
|
||
|
||
本系统当前仅涉及 `ichni Official`。`ichniCreatorStudio` 未引用 `SongStatusSave`、`BeatmapSave`、`SongSaveModule` 或 `DifficultyData` 的这套运行时存档链路,因此本轮没有同步修改 Creator。若未来共享序列化类型、编辑器预览或导入流程明确引用这些类型,再重新评估跨项目同步。
|