Cocos2d-x Lua 热更新机制

什么是热更新呢

游戏上线后,玩家下载第一个版本,在运营过程中,如果需要更换UI显示或修改游戏逻辑,这个时候如果不使用热更新,就需要重新打包,然后让玩家重新下载,这样既浪费流量和时间,最重要的是用户的体验非常不好。热更新是在不重新下载客户端的情况下,更新游戏的内容,热更新一般应用在手游中。

热更新也叫不停机更新,是在游戏服务器运行期间对游戏进行更新,实现不停机修正bug、修改游戏数据等操作。

热更新通常指的是客户端的资源更新,客户端在启动后访问更新API,根据更新API的反馈下载更新资源,然后使用新的资源启动客户端,或直接使用新资源不重启客户端。此种方式可跳过AppStore的审核,避免用户频繁下载、安装、覆盖产品包。使用热更新的方式可快速修复产品bug并增加新功能。

热更新一般适用于脚本语言,因为脚本无需编译,是一种解释性语言。C++是很难热更新的,其代码只要有改动就需要重新链接编译,虽然统一接口用动态库也可以实现,不过欠缺灵活。Lua相对于C++开发的优点之一是代码可在运行时加载,基于此不仅可以在编码期间更新,也可以在版本发布后更新代码。

Cocos自身也封装了热更新的模块AssetsManagerAssetsManagerEx

AssetsManager采用的是升级包的管理方式,首先进行版本号对比,然后根据URL获取对应的升级包,解压升级包,设置资源加载路径,通过加载writepath目录下最新文件的方式来实现更新。问题是当涉及跳版本更新,或只有一个文件被改动时,用户就要下载前面全部的升级内容,升级包会越来越大。

AssetsManagerExAssetsManager的加强版,不同的是不再使用升级包的方式,而是采用单个文件拉取的方式。首先获取本地更新配置,之后与服务器的更新配置比对,得出差异文件,之后单个拉取差异文件。当本地版本大于服务器版本时,会清理掉本地更新缓存。AssetsManagerEx也有尚未解决的问题,例如多个更新序列无法并行,只能顺序启动。另外版本后期随着项目庞大配置文件几乎包含了所有的文件信息,对比文件时间的耗时会越来越长。

基本思路

  1. 登录游戏前先向服务端请求当前游戏版本号信息,与并本地版本号比对。若相同则说明无资源需更新可直接进入登录。若不同则说明有资源需更新。
  2. 客户端向服务端请求当前所有资源的列表(资源名+md5),并与本地资源列表比对,找出需要更新的资源。
  3. 更具找出需要更新的资源,客户端向服务端请求并下载。

大版本号

安装包的大版本号是指C++部分的版本号,若有变动此版本号才会动。用来提示用户去APPStore下载新的版本。其他的版本号,只是一个显示版本号,可以根据游戏内容来区分。

安装包内部带有一个文件列表的Lua文件,之所以使用Lua文件,是为了在Lua中使用dofile方便读取。而files中列出了所有包内的文件。

local flist = {
  core = 1  -- 大版本号
  version = "1.0.1" -- 显示版本号
  update_md5 = "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
  framework_md5 = "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
  -- 安装包内文件,path是相对于res的路径,带完整目录和文件后缀。
  files = {
    {path="ui/imgs/close_btn.png", md5="xxxxxxxxxxxx", size="30"},
    {path="ui/imgs/tips.png", md5="xxxxxxxxxxxx", size="20"}
  }
}
return flist

资源服务器上也有一份同样的资料列表,服务器和安装包中的结构如下:

  • res/flist 资源列表
  • res/update.bin update模块的打包
  • res/framework.bin quick-framework的打包
  • res/game.bin 游戏逻辑的打包
  • res/... 其他游戏资源

热更新流程

  1. 从服务器获取版本列表flist
  2. 检查updatemd5值是否有更新,若有则下载update.bin重新载入,并退出main,退出前注意清除对某些的引用。再次重新进入 。
  3. 检查frameworkmd5值是否有更新,若有则下载framework.bin,并提示用户重新启动。
  4. 读取本地安装目录的版本列表文件flist
  5. 比对服务器版本列表与本地版本里中的大版本号,若不一致则提示用户去APPStore下载。
  6. 读取upd目录的版本列表文件flist,若存在或flist中存放的core与安装目录列表不一致,表示用户安装了新版本,则清除整个upd目录,并将本地安装目录的flist内容写入upd目录。
  7. 比对服务器列表与本地列表中version,若版本相同则认为数据无需更新,若版本不同则与服务器的flist对行md5比对,得到需要更新的文件。
  8. 遍历需要更新的文件列表,若upd存在则校验其md5值,若md5值与服务器相同,则从待更新列表中移除,其目的是为了应对上一次更新过程中,玩家中途退出的情况。
  9. 逐个更新文件,每个文件更新完毕后,再次校验其md5值,若md5码校验失败,则重新下载此文件。
  10. 待所有文件更新完毕,重写upd文件中的flist,最后进入游戏。
热更新模块流程

入口文件

对于热更新,游戏执行后首先执行main.lua,然后调用launcher模块代码,launcher根据版本决定下一步的逻辑。

-- main.lua
function __G__TRACKBACK__(errmsg)
  print("LUA ERROR: "..tostring(errmsg).."\n")
  print(debug.traceback("", 2))
end

-- 清除文件缓存避免无法加载新资源
local fileUtils = cc.FileUtils:getInstance()
fileUtils:setPopupNotify(false) -- 文件加载失败后禁止弹出消息框
fileUtils:purgeCachedEntries() --清除搜索文件缓存,避免无法加载新的资源。

-- 热更新模块
cc.LuaLoadChunksFromZIP("code/launcher.zip")
package.loaded["launcher.launcher"] = nil
require("launchere.launcher")

热更新模块

热更新launcher模块,先请求服务器的launcher模块文件,如果本地launcher模块文件和服务器不同则替换新的模块并重新加载。

模块和require机制

Lua内部提供require()用来实现模块的加载,其主要功能:

  • registry["_LOADED"]表中判断该模块是否已经加载过,若已加载过则返回,避免重复加载。
  • 依次调用注册的loader来加载模块
  • 将加载过的模块赋值给register["_LOADED"]
  • register["_LOADED"]表实际对应的是package.loaded

实现Lua代码热更新,其实也就是需要重新加载某模块,因此想办法让Lua认为之前从未加载过模块。Lua中的require()会阻止多次加载相同的模块,当需要更新系统时,要卸载掉相应的模块。并把全局表中对应模块表置为nill,同时把数据记录在专用的全局表下,并用local去引用它。初始化数据时,应首先检查是否已被初始化过,以保证数据不被更新过程重置。

-- require()增强版,动态更新模块代码,解决已经引用模块的地方不会得到更新。
function reload(module)
  -- 解决已经引用模块的不会得到更新
  local ori_module = _G[module]

  -- 判断是否曾经加载过此模块
  if package.loaded[module] then
  end

  -- 将该模块原来在表中注册的值清空
  package.loaded[module] = nil
  -- 再次调用require进行模块加载和注册
  require(module)
  
  -- 将引用该模块的地方的值也做对应更新
  local new_module = _G[module]
  for k,v in pairs(new_module) do
    ori_module[k] = v
  end
  package.loaded[module] = ori_module
end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容