热更新能力是移动游戏运营的关键一环:不用重新上架 App,就能修复 Bug、投放新内容。Defold 提供两套相关但不同的能力——开发期热重载(Live Reload) 与 生产期资源热更(Live Update)。(Defold hot reload docs) 本文分别讲解其原理、配置与运营实践。
前置建议:理解资源引用与打包机制(见 编辑器与资源管线),并结合 跨平台发布 了解发布产物结构。
一、开发期热重载:Live Reload
1. 热重载是什么
Defold 编辑器在运行时可直接修改脚本、GUI、图集等资源并立即生效,无需重新编译启动。触发方式:
Cmd + R(macOS)/Ctrl + R(Windows/Linux)重载脚本- 编辑器中修改场景/图集后,焦点切回引擎窗口即自动应用
这对迭代调试价值极大:改一行 Lua、调一个坐标,即刻看到效果。
2. 热重载的机制
脚本热重载会触发 on_reload 回调,随后重新执行 init:
function on_reload(self)
-- 热重载时保持关键状态:把 init 里会覆盖的数据备份回来
local saved_hp = self.hp
-- ...重新 init 后
self.hp = saved_hp
end
由于 init 会重新执行,热重载通常重置脚本状态。需要「改完还想看现场」的场景,务必在 on_reload 中恢复关键变量。
3. 热重载的边界
- 资源(图片、音频)改路径/删除后,需要重建相应组件
require的模块被修改时,若模块有状态缓存,需要额外处理- 场景结构(Collection)大改时,建议重启试玩以保证一致性
4. 多设备热重载
编辑器支持 File → Live Reload 将更新推送到连接中的真机/模拟器,方便真机调试 UI 与性能。配合 game.project 中开启的调试端口即可。
二、Live Update:资源热更原理
1. 为什么需要 Live Update
移动平台的包体审核周期长(尤其 iOS),把「可变更内容」从安装包中剥离出来、运行时从服务器下载,是行业标准做法。Defold 的 Live Update(曾用名 Hot Reload)允许:
- 更新 Lua 脚本、图集、GUI、Tilemap 等全部资源
- 客户端启动时按版本拉取新资源,无需重新审核
- 原生扩展(.dylib/.so)除外——涉及代码层面改动用原生扩展不支持热更
2. 工作流程总览
构建期:
bob --build --archive ... # 生成 app.arcd
bob --build-resources ... # 生成基础资源 + 可热更资源清单
客户端启动:
检查本地 manifest vs 服务器 manifest
差异部分走 HTTPS 下载 → 写入本地归档
挂载新归档 → 加载最新资源
3. 关键概念:Archive 与 Manifest
- Archive(归档):打包好的资源文件(
.arcd),内含引擎启动所需的最小资源集 - Manifest(清单):记录资源清单与校验和的元数据文件,Live Update 据此判断哪些资源需要更新
game.project 开启热更:
[liveupdate]
enabled = 1
private_key = /path/private.der -- 签名用,发布后妥善保管
4. 客户端加载流程代码
初始化时加载并挂载远程归档:
function init(self)
-- 尝试挂载已下载的热更资源
self.state = "mount"
end
function update(self, dt)
if self.state == "mount" then
local ok = resource.mount_archive("/liveupdate/remote.arcd")
if ok then
self.state = "done"
else
self.state = "fetch" -- 本地无归档,去下载
end
elseif self.state == "fetch" then
http.request(self, "https://cdn.example.com/game/liveupdate.arcd", "GET", function(self, id, response)
if response.status == 200 then
-- 写入本地,供下次启动挂载
resource.store_archive(response.body)
resource.mount_archive("/liveupdate/remote.arcd")
end
self.state = "done"
end)
end
end
三、版本管理与回滚
1. Manifest 版本控制
每个发布版本都有唯一 manifest。构建时可用 --variant 或版本号区分。建议:
- 构建产物按版本号/时间戳命名归档:
liveupdate_v103.arcd - 服务器保留「当前版本 + 上一版本」两份,兼容未及时更新的客户端
- manifest 校验失败(校验和不符)时客户端回退到内置资源,保证可玩
2. 兼容性矩阵
| 客户端版本 | 服务器版本 | 行为 |
|---|---|---|
| 1.0.0 | 1.0.0 | 无更新 |
| 1.0.0 | 1.1.0 | 下载 1.1.0 资源增量 |
| 1.0.0 | 1.0.0(被篡改) | 校验失败 → 回退内置资源 |
| 1.0.0 | 1.0.0(部分缺失) | 下载缺失资源,断点续传 |
3. 回滚策略
运营上「更新出问题」是常态,回滚要快速:
- 服务器侧回滚:把 CDN 上
liveupdate指向旧版本归档,客户端下次启动比对 manifest 发现差异即重新下载 - 强制回滚:需要「回到上一个版本」时,服务器推送旧 manifest,客户端按旧清单重下
- 灰黑名单:对有问题的设备指纹/版本号返回旧包,精细控制灰度面
4. 签名与安全
Live Update 归档默认用私钥签名,客户端用内置公钥验签,防止中间人篡改:
private_key只在构建机使用,绝不可放进客户端- 定期轮换密钥,旧归档用旧密钥验证
- HTTPS 传输 + 签名双重保障,参见 网络与安全专题
四、运营更新流程
1. 从提交到生效的完整链路
典型运营流程:
开发分支改动 → CI 构建出 liveupdate.arcd
→ 上传到 CDN(带版本号与 manifest)
→ 运营后台更新「当前版本」指向
→ 客户端启动拉取 manifest → 差异下载 → 挂载生效
借助 DevOps 专题 的流水线,可把这套链路自动化:提交 tag 即触发构建与上传。
2. 更新时机与体验
- 启动时静默检查更新,进入游戏后再异步下载(不阻塞首屏)
- 大资源包显示下载进度条,避免用户误以为卡死
- 下载完成前用旧内容占位,完成后平滑切换
-- 显示下载进度
http.request(self, url, "GET", function(self, id, response)
-- 假设服务端支持 Content-Length 与分块
local total = tonumber(response.headers["Content-Length"] or 0)
gui.set_text(gui.get_node("progress"), string.format("%d%%", response.progress * 100))
end)
3. 内容增量 vs 全量
早期实现常全量替换归档,简单但流量大。进阶做法:
- 整包热更:一次下载整个 liveupdate 归档,简单可靠,适合内容不大
- 增量热更:按 manifest 只下载变更资源,省流量但需要服务端做差异计算
- 资源版本化命名(
atlas_20260927.atlas)便于增量匹配与缓存失效
4. 构建命令与产物清单
构建热更产物的常用 bob 命令:
# 基础构建:生成完整归档 app.arcd
java -jar bob.jar --archive --build --bundle-output build/ios
# 生成可热更资源清单
java -jar bob.jar --build-resources build/liveupdate
# 产物
# build/liveupdate/liveupdate.arcd -- 热更归档
# build/liveupdate/manifest.json -- 资源清单与校验
# build/liveupdate/private.der 相关 -- 签名材料(仅构建机保留)
将 liveupdate.arcd 与 manifest.json 一起上传 CDN,路径约定建议:
https://cdn.example.com/game/<channel>/<version>/liveupdate.arcd
https://cdn.example.com/game/<channel>/<version>/manifest.json
五、灰度发布与渠道管理
1. 灰度(Canary)发布
新版本不直接全量放量,而是分批次暴露,观察崩溃率与留存:
第一批 5% → 监控 crash / 报错率
第二批 20% → 对比活跃与付费数据
第三批 100% → 全量
实现方式:服务器对不同客户端版本号/设备指纹返回不同 manifest 指向,即「同一套客户端,不同更新状态」。
2. 渠道与版本隔离
多商店(Google Play、App Store、TapTap 等)或多渠道(联运)需要隔离更新:
- 每个渠道独立
channel目录,避免相互覆盖 game.project中通过构建变体注入渠道号,客户端启动携带渠道标识请求对应 manifest- 渠道专属内容(皮肤、礼包)只在该渠道的归档中下发
-- 客户端上报渠道,请求对应 manifest
local channel = sys.get_config("channel", "default")
local url = "https://cdn.example.com/game/" .. channel .. "/latest.json"
3. 版本强制升级策略
当热更无法覆盖变更(原生代码升级、数据结构不兼容)时,需要商店重新发布:
- 服务器下发
force_update = true,客户端弹出「请更新到最新版本」阻塞页 - 非强制更新则提示「有新版本,是否立即更新」
- 建议在
game.project记录version,启动时与服务端min_version比对
function on_message(self, message_id, message, sender)
if message_id == hash("version_check") then
if message.force_update and message.min_version > sys.get_config("project.version", "0.0.0") then
gui.set_enabled(gui.get_node("force_update_dialog"), true)
end
end
end
4. A/B 测试接入
利用热更快速迭代内容,可结合 A/B 测试:
- 服务器把「实验组配置」打包进某分支归档,只对实验组设备下发
- 客户端上报分组,运营后台对比两组转化率后全量或回滚
- 配置字段(数值、文案、开关)尽量数据驱动,放在 Lua 模块中便于热更调整
六、服务器端与 CDN 实践
1. CDN 与缓存策略
Live Update 对 CDN 的要求:
- 归档文件大、请求频率集中在启动瞬间,CDN 必须支持大文件与高并发
- manifest 文件小、需要「最新」,设置短 TTL 或每次请求回源校验
- 归档按内容寻址(文件名含哈希),可长缓存,天然防重复下载
manifest.json → Cache-Control: no-cache
liveupdate.arcd → Cache-Control: public, max-age=31536000, immutable
2. 服务器接口设计
最少只需两个接口:
GET /game/<channel>/latest.json→ 返回{ version, manifest_url, force_update, min_version }GET /game/<channel>/<version>/liveupdate.arcd→ 返回归档流
客户端启动流程对应:
function init(self)
http.request(self, "https://api.example.com/game/latest.json", "GET", on_version_response)
end
function on_version_response(self, id, response)
local data = json.decode(response.body)
if data.version == sys.get_config("project.version") then
return -- 版本一致,无需更新
end
self.remote_url = data.manifest_url
check_and_download(self) -- 下载并挂载
end
3. 失败重试与断点续传
移动网络不稳定,必须做重试与续传:
- 记录已下载字节,下次请求带
Range头续传 - 下载失败按指数退避重试(1s/2s/4s…),最多 N 次后暂停到下次启动
- 归档写入临时文件,校验通过后
rename为正式文件,避免半写文件被挂载
local retry = 0
function schedule_retry(self, url)
retry = retry + 1
local delay = math.min(2 ^ retry, 30)
timer.delay(delay, false, function()
http.request(self, url, "GET", on_download_response)
end)
end
4. 监控与告警
运营体系必须能「看到」更新状态:
- 上报每次拉取/下载/挂载的成功率与耗时
- 监控各版本客户端占比,异常突降说明下载失败或强制升级失效
- 告警:CDN 5xx、manifest 返回异常、崩溃率上升
5. 安全加固
- 归档签名 + HTTPS 双保险(见上文签名一节)
- 服务端对渠道、版本做白名单,防止非授权客户端拉取
- 敏感配置(奖励数值、掉落表)不要明文下发,热更内容同样要做反作弊校验
七、热更边界与注意事项
1. 哪些能热更,哪些不能
| 内容 | 是否可热更 | 说明 |
|---|---|---|
| Lua 脚本、GUI、图集、Tilemap | 是 | Live Update 覆盖全部资源 |
| 原生扩展(.dylib/.so/.aar) | 否 | 涉及代码与系统权限,需走商店更新 |
| 引擎内核版本 | 否 | 依赖 runtime,需重新打包 |
| 启动引导资源 | 需谨慎 | 首屏资源热更风险高,建议保留内置 |
2. 热更资源的加载时机
热更归档挂载后,新资源才能被 factory.create / gui.get_node 使用。若游戏主循环中对象已按旧资源创建,切换需重新创建或重启场景:
function on_update_applied(self)
msg.post("/game_proxy", "unload")
msg.post("/game_proxy", "load") -- 重载场景,应用新资源
end
3. 常见坑
- manifest 过期:客户端拿到旧 manifest 会反复请求,给 CDN 加合理缓存头
- 归档损坏:下载中断导致归档不完整,校验和失败 → 触发重下并清理损坏文件
- 多版本共存:老客户端连新服务器,注意资源删除导致的引用缺失,保留向后兼容资源
4. 测试热更
- 本地起静态服务器(
python -m http.server)模拟 CDN,跑通「首次无归档 → 下载 → 挂载」全流程 - 故意篡改归档校验热更失败路径的回滚
- 弱网/断点测试:用代理工具限制带宽,验证分块下载与重试
八、总结
热重载是开发期的「加速器」,Live Update 是运营期的「弹药库」。两者共享一个理念——把资源当作可替换的数据,而不是固化的包体。掌握 manifest、归档挂载、签名校验与版本矩阵,就能为 Defold 游戏搭建一套可靠的更新体系。
发布环节的最终落地在 Defold 跨平台发布,热更配置与发布构建是配套的,建议两篇一起实践。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。