引言
游戏存档是玩家「时间与情感的容器」——坏一个存档可能流失一个玩家。本文把 Defold 存档系统讲透:先讲存档数据建模(哪些该存、哪些不该存、怎么分层),再对比序列化方案(JSON vs msgpack vs 二进制)在 Defold 里的取舍,接着讲 Defold 的文件系统与读写(sys.save/load、跨平台存储路径),然后处理存档的加密与完整性校验(HMAC、版本戳、防篡改),再讲版本迁移(字段演进、向后兼容、救档),最后覆盖自动存档/检查点与云存档跨设备同步。
前置:/defold-script-system-lua/(Lua 数据与消息)、/defold-hot-reload-updates/(版本与资源管理)。数据序列化通用知识见 杂项专题。
目录
- 1. 存档是什么:玩家状态的容器
- 2. 存档数据建模:分层与边界
- 3. 序列化方案:JSON-msgpack-二进制
- 4. Defold 文件系统与存储路径
- 5. 加密与完整性校验
- 6. 版本迁移:字段演进与救档
- 7. 自动存档与检查点
- 8. 云存档与跨设备同步
- 9. 存档的常见陷阱
- 10. 速查表
- 延伸阅读
1. 存档是什么:玩家状态的容器
存档 = 把「玩家的游戏进度」序列化成可持久化的数据:
游戏运行时:玩家对象在内存(关卡、金币、技能、背包)
存档:把这些「关键状态」落盘 → 下次启动恢复
什么该存、什么不该存:
| 该存 | 不该存 |
|---|---|
| 关卡进度、存档点 | 纯临时状态(动画、特效) |
| 金币/等级/技能/装备 | 可推导数据(当前帧位置) |
| 解锁内容、成就 | 运行时缓存(敌人巡逻相位) |
| 设置(音量/画质) | 敏感凭证(绝不明文存) |
核心原则:存「语义状态」不存「瞬时状态」:
- 存「角色到了第 3 关」,不存「角色像素坐标 123,456」
- 存「拥有剑与护甲」,不存「当前装备渲染对象」
- 推导成本高的数据才存,可推导的不存
心智:存档存「玩家语义状态」——进度/资产/设置,不存瞬时与可推导状态,坏档的痛比什么都大。
2. 存档数据建模:分层与边界
好的存档模型是「结构化 + 可演进」的:
-- 存档结构(示意)
local save = {
version = 5, -- 存档格式版本
meta = { -- 元信息
created = 1700000000,
updated = 1700001000,
},
progress = { -- 进度层
level = 3,
checkpoints = {2, 5},
unlocked = {"map_1", "map_2"},
},
player = { -- 角色层
gold = 1250,
level = 8,
skills = {"sword_master", "dash"},
inventory = {items = {...}},
},
settings = { -- 设置层
volume = 0.8,
quality = "high",
language = "zh",
},
}
分层的价值:
- 各层独立演进(settings 层升级不影响 progress 层)
- 部分层可不同步(云存档只同步进度,不同步设置)
- 校验/加密按层区分(设置可明文、进度要校验)
数据建模注意:
- 用「稳定的键名」:改名 = 破坏老存档(用 version 迁移兜底)
- 数值用整数/字符串(浮点精度跨平台不稳)
- 布尔/枚举用固定字符串(别用数字魔法值)
- 大型数据(地图/自定义)→ 单独文件,不塞主存档
心智:存档建模 = version + meta + 分层(progress/player/settings)——分层独立演进、键名稳定、数值可移植,为「迁移」留好余地。
3. 序列化方案:JSON-msgpack-二进制
存档要「序列化」才能落盘——三方案在 Defold 的取舍:
| 方案 | 优点 | 缺点 | Defold 适用 |
|---|---|---|---|
| JSON | 可读、调试友好、生态成熟 | 体积大、Lua 无内置 | 中小型存档、调试期 |
| msgpack | 体积小、速度快、Lua 友好 | 二进制不可读 | 常规存档(推荐) |
| 二进制手写 | 最小体积 | 费时、易错 | 超大存档/性能敏感 |
Defold 序列化实践:
-- JSON(需要第三方库)
local json = require("modules.json")
local str = json.encode(save_data)
-- msgpack(Lua 生态成熟)
local msgpack = require("modules.msgpack")
local blob = msgpack.pack(save_data)
-- 注意:函数/协程/userdata 不可序列化
-- 存档里只放「纯数据」(table/字符串/数值/布尔)
选择决策:
- 存档小(<几十 KB)→ JSON 足够,调试最爽
- 存档大/频繁读写 → msgpack(体积速度双赢)
- 跨端(云端/网页端)→ 优先 JSON(通用解析)
- 存档要「可手工编辑」?→ 人类可读格式
Lua 序列化的坑:
- table 键必须是「字符串/数值」(否则序列化失败)
- 循环引用 → 序列化崩溃(存档数据别建环)
- 浮点数跨平台精度 → 存整数/字符串
- 数值索引的数组 vs 哈希表 → 序列化库要能区分
心智:存档序列化 = JSON 可读但大、msgpack 小又快、手写二进制最小——中小存档 JSON 调试爽、常规存档 msgpack、跨端 JSON;纯数据才能序列化,别放函数与环。
4. Defold 文件系统与存储路径
Defold 读写文件的核心 API:
-- 写存档(data 是二进制字符串)
sys.save("/save/slot1.data", data)
-- 读存档
local ok, data = sys.load("/save/slot1.data")
if ok then
local save = msgpack.unpack(data)
end
-- 删除
os.remove("/save/slot1.data") -- 或 sys 提供的方法
存储路径与跨平台:
- "/save/..." 是 Defold 的「用户数据」虚拟路径
→ 自动映射到各平台的安全目录
- 目录不存在要自己创建(io.mkdir 或 ensure)
- 路径规范:/save/slot1.data、/save/settings.json
多个存档槽位:
-- 槽位管理
local SLOTS = {"/save/slot1.data", "/save/slot2.data", "/save/slot3.data"}
function write_slot(self, idx, data)
sys.save(SLOTS[idx], data)
end
写入安全的工程细节:
- 原子写入:先写临时文件再改名(防中途崩溃坏档)
- 写盘完成确认(sys.save 同步/异步差异)
- 空间不足/写入失败 → 捕获并提示玩家
- 别在「游戏帧中」频繁大写入(卡顿)
心智:Defold 用 sys.save/load 走 “/save/…” 虚拟路径自动适配各平台——槽位多文件、原子写入防坏档、失败要兜底提示。
5. 加密与完整性校验
存档不加密 = 玩家可改(作弊/破坏);不校验 = 坏档无感知:
完整性校验(必做):
-- HMAC 校验:存档数据 + 秘密密钥 → 摘要
local hmac = require("modules.hmac")
local key = "app_secret_key" -- 藏于二进制(对抗玩家)
local data = msgpack.pack(save_body)
-- 写入
local checksum = hmac.hmac_sha256(key, data)
sys.save("/save/slot1.data", data .. checksum)
-- 读取
local loaded = sys.load("/save/slot1.data")
local body, sum = string.sub(loaded, 1, -33), string.sub(loaded, -32)
if hmac.hmac_sha256(key, body) ~= sum then
-- 存档被篡改或损坏 → 提示/丢弃/救档
else
local save = msgpack.unpack(body)
end
加密 vs 校验的边界:
- 完整性校验(HMAC):防篡改、防损坏 → 必须
- 加密(AES):防「读取明文」→ 一般存档不需要
(玩家总能用内存工具读出值,加密只防「文本编辑」)
- 真正要防的是「数值被改」→ 校验 > 加密
抗作弊的高级手段:
- 密钥不硬编码成明文(拆散/混淆)
- 服务器权威存档(数值只在服务端可信)
- 校验「数值合法性」(金币非负、等级在范围内)
心智:存档防护 = HMAC 完整性校验(必须) + 适度混淆(对抗文本修改)——防篡改靠校验而非加密,数值合法性也要校验,权威档在服务器。
6. 版本迁移:字段演进与救档
游戏版本升级后,老存档的格式要对齐——版本迁移是存档系统的「保险」:
-- 迁移管线:把老版本存档升级到当前版本
local migrations = {
-- [1] → [2]:金币字段改名 gold → currency
function(save)
save.currency = save.gold
save.gold = nil
return save
end,
-- [2] → [3]:新增技能树(默认值)
function(save)
save.skills = save.skills or {}
return save
end,
}
function load_save(raw)
local save = msgpack.unpack(raw)
local v = save.version or 1
for i = v, #migrations do
save = migrations[i](save) -- 逐步升级
end
save.version = #migrations -- 打上新版本
return save
end
迁移的工程原则:
- 迁移「只增不改」:老字段保留读取兼容,新字段给默认值
- 逐版本顺序迁移(不跳步)→ 逻辑清晰
- 迁移失败 → 保留原始存档(可回滚/救档)
- 测试矩阵:每个历史版本 → 迁移 → 当前版本 都能跑
救档策略:
- 损坏存档:尝试恢复部分数据(能救的救)
- 迁移失败:保留原档 + 提示「已备份」
- 备份方案:每次覆盖前存一份 .bak
- 坏档检测到 → 别直接删,先备份再处理
心智:版本迁移 = 逐版本迁移函数链「只增不改、给默认值」——迁移失败保留原档、覆盖前备份、每个历史版本都要测通,让玩家「老档不丢」。
7. 自动存档与检查点
存档时机与体验直接挂钩——「自动存档」的取舍:
自动存档点:
- 关卡过关 / 检查点到达
- 关键事件(Boss 战前、重大选择)
- 应用切后台 / 退出时
- 定时(每 N 分钟)
不要每帧存:频繁写盘卡顿 + 磨损存储
检查点 vs 全量存档:
- 检查点:轻量「重生点」数据(关卡内位置)
- 全量存档:完整进度(跨关卡)
- 组合:检查点快速恢复 + 全量存档定期落盘
-- 自动存档(事件触发)
function on_checkpoint(self, data)
self:write_slot(data) -- 检查点即时存
end
function on_message(self, message_id, ...)
if message_id == hash("app_pause") then -- 切后台
self:write_slot() -- 兜底存档
end
end
自动存档的体验:
- 存盘点图标/音效反馈(玩家知道「存过了」)
- 自动存档不阻塞(异步/后台写)
- 「死亡惩罚」平衡:检查点太远 = 挫败、太近 = 无压力
心智:自动存档 = 关键事件触发 + 检查点快速恢复 + 切后台兜底——别每帧存、存盘点要有反馈、死亡惩罚靠检查点距离调手感。
8. 云存档与跨设备同步
云存档(跨设备)是移动端标配——难点是「冲突与信任」:
云存档方案:
- 平台云存档:iCloud / Google Play Games(平台内建)
- 自建后端:服务器权威存档 + 客户端读写
- 混合:本地为主 + 云端同步(离线可用)
同步冲突处理:
冲突场景:设备 A、B 各玩一段 → 同步时谁的档有效?
策略:
1. 时间戳胜出:最新修改者覆盖(简单,但丢进度)
2. 分字段合并:进度层取最新、设置层按设备(复杂)
3. 服务器权威 + 版本号:冲突则玩家选(体验折衷)
跨设备同步要点:
- 本地「缓存 + 后台同步」:弱网可玩
- 同步前先「版本戳」比较,避免回退
- 同步后校验(HMAC)防串档
- 首次登录关联:老档上传 / 云端档下载 的选择 UI
多槽位的云同步:
- 云端多槽位 → 每槽独立版本戳
- 槽位上限(如 3 个)→ 超出提示清理
- 云端删除要二次确认(不可逆)
心智:云存档 = 本地缓存 + 后台同步 + 版本戳冲突处理——最新胜出最简单、分字段合并最精确、服务器权威最可信;同步前版本比较、同步后校验,多槽位独立管理。
9. 存档的常见陷阱
| 陷阱 | 现象 | 规避 |
|---|---|---|
| 存瞬时状态 | 读档后角色卡在墙里 | 只存语义状态 |
| 键名乱改 | 老档读出来全是空 | 稳定键名 + 迁移 |
| 浮点跨平台 | 金币 1.1 变 1.0999 | 存整数/字符串 |
| 不校验 | 篡改数值 / 坏档无感 | HMAC + 数值合法校验 |
| 写一半崩溃 | 存档文件损坏 | 原子写入 + .bak |
| 无版本迁移 | 升级后档全废 | version + 迁移链 |
| 每帧存盘 | 卡顿 + 磨损 | 事件触发 + 异步 |
| 云同步回退 | 新档被旧档覆盖 | 版本戳 + 冲突策略 |
10. 速查表
全篇速查:
| 主题 | 结论 |
|---|---|
| 建模 | 存语义状态、分层 progress/settings |
| 序列化 | JSON 调试 / msgpack 常规 / 二进制大档 |
| 读写 | sys.save/load、"/save/" 路径 |
| 安全 | HMAC 校验必须、加密看场景 |
| 迁移 | 逐版本函数链、只增不改 |
| 救档 | 覆盖前 .bak、迁移失败保留原档 |
| 自动存 | 关键事件 + 检查点 + 切后台兜底 |
| 云同步 | 本地缓存 + 版本戳 + 冲突策略 |
| 陷阱 | 瞬时状态/键名/浮点/不校验 |
| 体验 | 存盘点反馈、检查点距离调难度 |
一句话记忆:存档 = 玩家语义状态的容器——建模分层存语义不存瞬时,序列化 JSON 调试爽、msgpack 常规快、二进制留给大档;sys.save/load 走 “/save/” 路径、原子写入防坏档;HMAC 校验必做、数值合法也要查;版本迁移用「逐版本函数链只增不改」加 .bak 救档;自动存档靠关键事件 + 检查点 + 切后台兜底,云同步用本地缓存 + 版本戳 + 冲突策略——坏一个档流失一个玩家,存档的每一层都是玩家的「信任存款」。
延伸阅读
- /defold-script-system-lua/ — Lua 数据表与消息传递
- /defold-hot-reload-updates/ — 版本管理与资源热更
- /defold-multiplayer-sync/ — 状态同步与权威数据
- /defold-cross-platform-publish/ — 跨平台存储与发布
- 杂项专题 — JSON/YAML 序列化与数据格式
- 游戏开发专题 — 存档与玩家体验设计
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。