Defold 游戏存档与序列化:数据建模、加密校验与版本迁移

系统覆盖 Defold 游戏存档系统:存档数据建模(存档状态/进度/设置分层)、序列化方案(JSON/msgpack/二进制)对比、Defold 文件系统(sys.save/load)与存储、存档加密与完整性校验(HMAC/版本戳)、存档版本迁移(字段演进/向后兼容)、自动存档与检查点、以及云存档与跨设备同步的工程实践。

引言

游戏存档是玩家「时间与情感的容器」——坏一个存档可能流失一个玩家。本文把 Defold 存档系统讲透:先讲存档数据建模(哪些该存、哪些不该存、怎么分层),再对比序列化方案(JSON vs msgpack vs 二进制)在 Defold 里的取舍,接着讲 Defold 的文件系统与读写(sys.save/load、跨平台存储路径),然后处理存档的加密与完整性校验(HMAC、版本戳、防篡改),再讲版本迁移(字段演进、向后兼容、救档),最后覆盖自动存档/检查点与云存档跨设备同步。

前置:/defold-script-system-lua/(Lua 数据与消息)、/defold-hot-reload-updates/(版本与资源管理)。数据序列化通用知识见 杂项专题。


目录


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 序列化与数据格式
  • 游戏开发专题 — 存档与玩家体验设计

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「defold」更多文章

  1. Defold Tilemap 碰撞与关卡设计
  2. Defold 性能分析与调试工具链
  3. Defold Collections、Factories 与动态实例化