存档是玩家「心血的载体」——等级、装备、剧情进度、通关记录,一旦存档损坏或丢失,玩家的投入感会瞬间崩塌。很多团队把存档当「随便序列化一下存个文件」,结果遇到版本更新后旧存档读不了、存档被修改器等灾难。本文剥开存档系统外壳,聚焦四个核心模块:存档数据结构(存什么、怎么组织)、序列化格式(怎么变成可存文件)、版本迁移与兼容(更新后旧档怎么办)、校验与云存档(安全与跨端),并用 Unity、Godot 与自研序列化三重视角对照。
建议先读 游戏模块划分和语言选型,理解存档在「数据层」的位置。
1. 存档数据结构:存什么、怎么组织
1.1 存档的分层
存档不是「把所有状态塞一个文件」,而是按生命周期分层:
├── 会话数据(Session):当前对局状态(可丢,局内)
├── 玩家数据(Profile):等级/装备/金币(必须持久)
└── 配置数据(Settings):音量/键位(本机偏好)
| 层 | 数据 | 丢失影响 | 存哪 |
|---|---|---|---|
| 会话 | 当前血量/位置 | 回检查点 | 内存 + 检查点 |
| 玩家 | 等级/装备/金币 | 灾难 | 持久存档 |
| 配置 | 音量/键位 | 可接受 | 本机 |
1.2 存档结构设计
{
"schemaVersion": 3, // 存档格式版本(关键!)
"player": {
"id": "u-1024",
"level": 42,
"gold": 8500,
"inventory": [{ "id": "sword", "count": 1 }]
},
"progress": {
"questId": "q17",
"unlockedAreas": ["forest", "cave"]
},
"settings": { "volume": 0.8, "lang": "zh" },
"savedAt": "2026-09-28T10:00:00Z"
}
设计铁律:
├── schemaVersion 必须放最顶层(迁移全靠它)
├── 数据按「玩家 / 进度 / 设置」分块(部分可局部刷新)
└── id 用稳定标识(不要用「第 3 件」这种位置索引)
记忆:存档结构的灵魂是 schemaVersion + 分块。版本号是「怎么迁移」的钥匙,分块是「局部更新」的基础。
2. 序列化格式:怎么变成可存文件
2.1 三种格式对比
| 格式 | 优点 | 缺点 | 适用 |
|---|---|---|---|
| JSON | 可读、易调试、生态广 | 体积大、慢 | 配置、小存档 |
| Binary | 快、小 | 不可读、难调试 | 大存档、性能敏感 |
| Protobuf | 快、小、版本友好 | 需 .proto、生成代码 | 网络 + 存档统一 |
JSON:{"gold":8500,"level":42} (可读,但冗余)
Binary:8B int + 4B float + ... (紧凑,但不可读)
Protobuf:字段号编码 + 变长整数 (快且版本友好)
2.2 选型决策
选 JSON 当原型 → 规模大了再迁 Binary/Protobuf
├── 原型期:JSON 可读性救了你 100 次调试
├── 发布期:存档大的游戏用 Binary/Protobuf
└── 混合:核心数据 Binary + 配置 JSON
心法:序列化格式别一开始就上 Protobuf。JSON 的原型红利巨大,等「性能真的需要」再迁,别为不存在的性能问题提前买单。
3. 版本迁移与兼容:更新后旧档怎么办
3.1 迁移链
游戏更新 → 存档 schemaVersion 变高 → 旧存档需迁移
迁移链:v1 → v2 → v3(逐版本升,不跳)
├── 每个版本一个迁移函数
└── 读档时:version < 当前 → 依次跑迁移
// 迁移链:低版本逐级升到当前
SaveData Migrate(SaveData data) {
while (data.schemaVersion < CURRENT) {
switch (data.schemaVersion) {
case 1: data = V1ToV2(data); break; // 加字段/改名
case 2: data = V2ToV3(data); break; // 改结构
}
}
return data;
}
3.2 兼容策略
| 策略 | 做法 | 场景 |
|---|---|---|
| 逐版本迁移 | v1→v2→v3 | 常规更新 |
| 字段加默认值 | 新字段缺失给默认 | 加字段 |
| 降级读取 | 高版本档旧客户端读不了 | 前向兼容(难) |
| 备份回滚 | 迁移前备份 | 迁移失败兜底 |
迁移失败的兜底:
├── 迁移前先备份(迁移前存 .bak)
├── 迁移失败 → 回退备份 + 提示玩家
└── 绝不原地覆盖:写新文件,确认成功再删旧的
铁律:迁移永远「先备份、写新、成功才删旧」。存档是不可再生资产,一次失败的原地覆盖迁移可能毁掉玩家全部进度。
4. 校验与防篡改:存档可信吗
4.1 校验完整性
存档损坏(断电/异常退出)检测:
├── 校验和(CRC/Hash):读档时验证,损坏则用备份
├── 魔法头(Magic):识别「这是不是我们的存档」
└── 冗余备份:主档 + 备份档交替写
写档流程:
写临时文件 → 写校验和 → fsync → 原子改名成正式存档
读档流程:
验魔法头 → 验校验和 → 不通过则用备份档
4.2 防篡改(单机)
单机存档防「存档修改器」:
├── 关键数值哈希签名(服务端密钥,客户端不可逆)
├── 服务端权威数据(段位/进度存服务端)
└── 客户端存档只存「本地装饰数据」
| 数据 | 权威方 | 防篡改 |
|---|---|---|
| 金币/等级 | 服务端 | 服务端存 |
| 本地设置 | 客户端 | 无需 |
| 剧情进度 | 服务端 | 服务端存 |
心法:防篡改的最优解不是「把存档加密得没人能改」,而是「把重要的数据放服务端」。客户端存档永远可被破解,服务端权威才是真防线(也接反作弊)。
5. 自动存档与云存档
5.1 自动存档时机
自动存档时机:
├── 关键节点:完成任务、升级、进入新区域
├── 安全点:战斗结束、对话结束
└── 退出时:应用切后台/退出前
防频繁写:同一时刻只允许一个写档在途(合并)
5.2 云存档同步
云存档 = 本地存档 + 服务端备份 + 多端同步
├── 上传:本地写档成功后异步上传
├── 下载:启动时拉服务端最新版
├── 冲突:本地 vs 云端(选新版/手动选)
└── 端衔接:跨平台继续(接扩展篇跨平台)
记忆:自动存档要「关键点 + 防重入」,云存档要「上传/下载/冲突合并」。存档不只存本地,跨端随行是现代游戏的基本盘。
6. 三引擎对照
| 维度 | Unity | Godot | 自研 |
|---|---|---|---|
| JSON | JsonUtility/Newtonsoft | JSON.stringify | 自己写 |
| 二进制 | BinaryWriter | FileAccess + PackedByteArray | 自研 |
| 存档路径 | Application.persistentDataPath | user:// | 平台路径 |
| 云存档 | 需接服务 | 需接服务 | 自己接 |
记忆:引擎只给「序列化 + 路径」的底座,存档的「结构/版本/校验/云同步」都是自己设计的。这部分是通用工程,引擎原生帮不上太多。
7. 最佳实践与总结
存档系统决策清单:
- 先分层:会话/玩家/配置分开,别全塞一个文件。
- schemaVersion 必留:版本号是迁移的钥匙,永远在顶层。
- JSON 起步、性能再迁:原型用 JSON,大了再 Binary/Protobuf。
- 迁移备份兜底:先备份、写新、成功才删旧。
- 服务端权威:重要的进度/段位放服务端,客户端存档防不了篡改。
- 自动存档防重入:关键点存 + 同刻只一个写档。
自研存档最小骨架推荐阅读顺序:数据结构 → JSON 序列化 → 版本迁移 → 校验备份 → 云同步。每完成一层,用一个「等级+装备」的 demo 验证「改代码后旧档还能读」。
存档没有银弹:JSON 的调试红利、Binary 的性能、服务端权威的安全,各有取舍。但版本迁移、备份兜底、服务端权威这三件事不分引擎必须做对——它们是玩家进度的「保险丝」。
相关阅读:游戏引擎架构 讲解数据层在引擎里的位置;游戏服务端高级进阶知识储备 讲解服务端权威与云端存储。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。