跳转到正文

存档系统概述

Fink Framework 的存档系统以 SaveManager 为统一入口,为游戏提供强类型数据保存、读取、槽位管理、全局存档、备份恢复和自动存档能力。

当前实现将职责拆成三层:

组件职责
SaveManager管理槽位/全局作用域、串行队列、异步 API、恢复顺序和自动存档
SaveSchema校验存档类型、创建默认实例、处理成员重命名兼容
SaveFileStore序列化容器、临时文件、回读校验、原子替换、备份轮换和文件解析

运行时命名空间为 FinkFramework.Runtime.SaveSaveManager 是不继承 MonoBehaviour 的普通 C# 单例,不需要在场景中挂载对象;但它的构造和 Unity 资源配置读取仍应在 Unity 主线程完成。

项目配置入口:

text
Edit → Project Settings → Fink Framework → Save System

1. 系统能力

能力当前行为
强类型存档通过 SaveAsync<T> / LoadAsync<T> 使用明确的根数据类型
JSON 与 BinaryJSON 保存为可读裸 JSON;Binary 使用 FSV1 容器和 Odin Payload
单槽位与多槽位单槽位固定使用 Slot 1;多槽位支持选择、创建、枚举和删除
全局存档独立于玩家槽位,拥有自己的主档、备份和历史链
快照保存保存调用在第一次异步等待前完成 Schema 校验和序列化,冻结本次数据
串行文件队列同一主文件的保存、加载、删除和回档按顺序执行;不同目标互不阻塞
安全提交临时文件写入、刷新、回读校验后才替换主档
自动恢复主档失败后依次尝试即时备份和编号历史备份
历史回档将选中的历史文件读取后按正常保存流程提交为新一代主档
Schema 演进新增成员使用默认值,删除成员自动忽略,重命名可声明旧名称
自动存档主线程抓取数据,文件保存复用普通保存队列

2. 一次保存发生了什么

text
游戏对象 / 运行时状态
        ↓ 业务层 Capture 为纯数据对象
SaveManager.SaveAsync<T>()
        ↓ Schema 校验
        ↓ 序列化并冻结快照
等待同一目标的保存队列
        ↓ 后台写入临时文件并 Flush
        ↓ 回读临时文件并校验
        ↓ 轮换即时备份和可选历史备份
        ↓ 原子替换主档
SaveResult

SaveAsync 不会把业务对象本身直接交给后台线程长期读取。Schema 校验和序列化在方法第一次让出线程前完成,因此调用方随后修改原对象,不会影响已经进入队列的这一笔保存。

文件进入原子替换阶段后不会响应中途取消,以避免出现半提交的主档;取消主要作用于排队等待和提交前工作。


3. 加载与自动恢复

加载一个目标时,系统按以下顺序寻找可用数据:

text
主档
  ↓ 失败
即时备份 _bak
  ↓ 失败
编号历史备份 _bak1、_bak2……
  ↓ 全部失败
返回失败结果

如果目标文件和所有备份都不存在,系统会创建当前版本的默认实例:

  • StatusFileNotFound
  • SourceDefault
  • Data 为当前类型的新实例;
  • LoadResult<T>.Succeeded 仍为 true

因此首次启动可以直接使用 result.Data;如果需要区分首次启动、主档损坏或从备份恢复,应检查 StatusSourceUsedDefaultRecovered

如果主档存在但主档、即时备份和历史备份全部读取失败,系统不会静默伪造一份看似成功的数据,而是返回具体失败状态及最后一次读取异常。


4. 槽位数据与全局数据

槽位存档

槽位数据适合保存:

  • 玩家进度;
  • 关卡和任务状态;
  • 角色、背包和装备;
  • 与某个存档槽绑定的游戏设置。

slotId 参数的 API 使用 CurrentSlotId。单槽位模式下它固定为 1;多槽位模式下可通过 SelectSlot 修改。

全局存档

全局存档不受当前槽位影响,适合保存:

  • 音量、画质和辅助功能设置;
  • 图鉴、全局解锁和账号级进度;
  • 最近使用的槽位或启动偏好。

槽位和全局数据使用独立的目标锁、主档、即时备份和历史备份链。


5. 当前文件布局

存档根目录为:

text
Application.persistentDataPath/FinkFramework_Save

以默认 Binary 后缀 .sav 为例,当前布局为:

text
FinkFramework_Save/
├─ global_save.sav
├─ global_save_bak.sav
├─ global_save_bak1.sav
└─ slot/
   ├─ slot_01.sav
   ├─ slot_01_bak.sav
   ├─ slot_01_bak1.sav
   └─ slot_02.sav

文件含义:

  • global_save / slot_01:当前主档;
  • _bak:最近一次覆盖主档前保留的即时备份,只用于自动恢复;
  • _bak1_bak2:可枚举、可手动回档的编号历史备份。

加载、槽位枚举、历史枚举和删除仍兼容旧版 Slots/Slot_N/main.saveGlobal/global.save 等目录结构,但新保存会使用当前扁平布局。


6. JSON 与 Binary

项目JSONBinary
文件内容DataUtil 生成的 UTF-8 裸 JSONFSV1 容器 + Odin Binary Payload
默认后缀.json.sav
人工可读性高,可直接检查不面向人工编辑
AES始终关闭复用全局 AES 配置,可选
GZip始终关闭可通过存档配置启用
元数据主要依赖 JSON 解析和文件时间类型、格式、代数、时间、标志和 SHA-256 校验

JSON 模式为了保持可读性,不套用 FSV1 外层容器,也不会启用压缩或加密。Binary 模式会保存容器头、数据类型、提交代数、UTC 时间、压缩/加密标志和 Payload 校验值。

开发和调试阶段可以选择 JSON 方便检查;正式单机项目通常根据体积、完整性校验和内容隐藏需求选择 Binary。AES 只能提高直接读取门槛,客户端内置密钥不能替代服务端校验或防作弊机制。


7. 业务层应该负责什么

存档系统负责文件和数据生命周期,业务层仍需要负责:

  • 将当前游戏状态转换为独立的纯数据对象;
  • 设计稳定的存档根类型和默认值;
  • 不把 GameObject、组件或其他 UnityEngine.Object 引用写入存档;
  • 根据 LoadResult<T> 的来源决定是否提示玩家或记录恢复日志;
  • 在删除槽位前进行 UI 二次确认;
  • 在格式、加密密码或数据结构变更时规划迁移方案。

8. 下一步

  • 存档系统配置:格式、Binary 后缀、槽位、历史、压缩和 AES 配置;
  • 基础使用:定义数据、保存加载、全局数据、槽位、回档和自动存档;
  • 运行时 API:结果类型、状态枚举、Schema 规则和完整方法签名。