Defold 热更新与热重载:Live Update、版本管理与运营流程

系统讲解 Defold 的开发期热重载与生产期 Live Update 热更新:资源热更原理、manifest 版本管理、回滚策略与运营更新流程。

热更新能力是移动游戏运营的关键一环:不用重新上架 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.01.0.0无更新
1.0.01.1.0下载 1.1.0 资源增量
1.0.01.0.0(被篡改)校验失败 → 回退内置资源
1.0.01.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 跨平台发布,热更配置与发布构建是配套的,建议两篇一起实践。

继续阅读

探索更多技术文章

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

全部文章 返回首页

「defold」更多文章

  1. Defold 跨平台发布:iOS/Android/Web/桌面打包、签名与 Store 上架
  2. Defold 编辑器与资源管线:从场景搭建到包体瘦身
  3. Defold 物理引擎:碰撞体、回调、关节与性能优化