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 行为树与游戏 AI 决策
  2. Defold 材质与着色器语言详解
  3. Defold HTML5 导出与 Web 性能优化