前言:游戏上线后,我们常常还会需要更新,如新增玩法,活动等,这种动态的更新资源我们称为游戏的热更新。热更新一般只适用于脚本语言,因为脚本不需要编译,是一种解释性语言,而如C++语言是很难热更新的,其代码只要有改动就需要重新链接编译(接口统一,用动态库可以实现,不过太不灵活了)。 
本章将讲讲用Cocos-lua引擎怎么实现热更新,其实Cocos自带也封装了热更新模块(AssetsManager, AssetsManagerEx),不过我没用自带的那套,自己封装了一套,其基本思路原理是一致的。

热更新基本思路

  1. 登入游戏先向服务端请求当前游戏版本号信息,与本地版本号比较,如果相同则说明没有资源需要更新直接进入游戏,而如果不相同,则说明有资源需要更新进入第2步。

  2. 向服务端请求当前所有资源的列表(资源名+MD5),与本地资源列表比较,找出需要更新的资源。

  3. 根据找出的需要更新资源,向服务端请求下载下来。(目前发现更新资源很多时,一个个循环向服务端请求可能中途会出错,所以最好是以zip包的形式一次性发送过来,客服端只请求一次)

热更新注意点

  • 1,程序加载某个文件原理:首先一个程序加载本地硬盘某一文件最终加载的路径都是绝对全路径。而我们之所以还可以写相对路径也能找到对应的文件是因为还有一个搜索路径,搜索路径是用一个容器存储的,相对路径是这样得到全路径的 = 搜索路径(全路径) + 相对路径。就是只要加入到这个搜索路径中的路径,以后要加载这里面的文件就只需给文件名就可以了,前面的路径它会自动去搜索路径循环遍历查找。所以程序里我们一般不写绝对路径,而是把前面的全路径加入到搜索路径,之后只需写后面的相对路径就能查找到了。 
    2,手游安装到手机上后,其安装目录是只读属性,以后是不能修改的。所以热更新的资源是没法下载到以前目录的,那么就得自己创建一个手机上可读写的目录,并将资源更新到这个目录。接下来还一个问题就是更新目录与以前资源目录不一致了,那么游戏中是怎么优先使用更新目录里的资源的呢?其实只要将创建的可读写目录路径添加到搜索路径中并将其插入到最前面即可,代码里统一是绝对路径。 
    文件的操作我们使用cocos封装的FileUtils类,其中一些相关函数如:fullPathForFilename:返回全路径,cocos加载文件最后都是通过它转换到全路径加载的,addSearchPath:添加到搜索路径,getWritablePath:返回一个可读写的路径。下面是Lua代码:
--创建可写目录与设置搜索路径self.writeRootPath = cc.FileUtils:getInstance():getWritablePath() .. "_ClientGame2015_"  if not (cc.FileUtils:getInstance():isDirectoryExist(self.writeRootPath)) then         cc.FileUtils:getInstance():createDirectory(self.writeRootPath)    endlocal searchPaths = cc.FileUtils:getInstance():getSearchPaths() table.insert(searchPaths,1,self.writeRootPath .. '/')  table.insert(searchPaths,2,self.writeRootPath .. '/res/')table.insert(searchPaths,3,self.writeRootPath .. '/src/')cc.FileUtils:getInstance():setSearchPaths(searchPaths)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 我封装的这套热更新本地需要两个配置文件,一个记录版本信息与请求url,一个记录所有资源列表。这两个配置文件都是json格式,cocos自带json.lua解析库, json.decode(js):将js转lua表,json.encode(table):将lua表转js。配置表如下: 
     

  • 还发现一个lua io文件操作的坑,local fp = io.open(fullPath,’r’);这些操作在ios可以但android上却不支持。所以热更新文件读写还得我们c++自己封装再tolua使用(扩展FileUtils类)。然而,c++与lua传递字符串时又有一个坑,c,c++的字符串,如果是const char* 这种,那么遇到二进制的字节0,就认为结束。如果是std::string与lua这种,则有一个单独的变量来表示长度,遇到二进制的字节0也不会结束。而图片数据里面很可能会有很多0字节,那么lua与c++交互是不能直接接收完整的。解决办法是:

    c++这边接收字符串这样接收: 
    char *p = “abc\0def”; 
    size_t len = 7; 
    std::string str = std::string(p + len);

    lua这边修改tolua交互代码: 
    lua接收c++返回字符串:使用lua_pushlstring(),我看它是用的lua_pushstring()这个,接收不完整遇0结束。 
    c++接收lua返回字符串:使用lua_tolstring(),同上。

热更新源代码

分为逻辑层与UI层,UI层是异步加载的,所以不能把这个模块当场景切换,用addChild添加到已有场景上就是。

逻辑层:


require('common.json')
local UpdateLogicLayer = class("UpdateLogicLayer", cc.Node)function UpdateLogicLayer:create(callback)local view = UpdateLogicLayer.new()local function onNodeEvent(eventType)if eventType == "enter" thenview:onEnter()elseif eventType == "exit" thenview:onExit()endendview:registerScriptHandler(onNodeEvent)view:init(callback)return view
endfunction UpdateLogicLayer:ctor()self.writeRootPath = nil               --手机可写路径self.manifest = nil                       --配置表信息(json->table)self.resConfigInfo = nil                --资源列表(json->table)self.updateResTable = nil            --需要更新资源表self.updateResProgress =1          --更新进度self.updateResPath = nil              --当前更新资源路径self.EventType = {None    = 0,                 --初始化状态StartGame  = 1,           --开始游戏StartUpdate = 2,          --开始更新AssetsProgress = 3,     --资源更新中AssetsFinish = 4,         --资源更新完成}self.callback = nil                             --外部回调self.status = self.EventType.None
endfunction UpdateLogicLayer:onEnter()endfunction UpdateLogicLayer:onExit()endfunction UpdateLogicLayer:init(callback)self.callback = callback--创建可写目录与设置搜索路径self.writeRootPath = cc.FileUtils:getInstance():getWritablePath() .. "_ClientGame2015_"  if not (cc.FileUtils:getInstance():isDirectoryExist(self.writeRootPath)) then         cc.FileUtils:getInstance():createDirectory(self.writeRootPath)    endlocal searchPaths = cc.FileUtils:getInstance():getSearchPaths() table.insert(searchPaths,1,self.writeRootPath .. '/')  table.insert(searchPaths,2,self.writeRootPath .. '/res/')table.insert(searchPaths,3,self.writeRootPath .. '/src/')cc.FileUtils:getInstance():setSearchPaths(searchPaths)--配置信息初始化local fullPath = cc.FileUtils:getInstance():fullPathForFilename('project.manifest') local fp = io.open(fullPath,'r')if fp thenlocal js = fp:read('*a')io.close(fp)self.manifest = json.decode(js)else    print('project.manifest read error!')end--版本比较self:cmpVersions()end--版本比较
function UpdateLogicLayer:cmpVersions()  --Postlocal xhr = cc.XMLHttpRequest:new()     xhr.responseType = 4   --json类型xhr:open("POST", self.manifest.versionUrl)  local function onReadyStateChange() if xhr.readyState == 4 and (xhr.status >= 200 and xhr.status < 207) thenlocal localversion = self.manifest.versionself.manifest = json.decode(xhr.response)if self.manifest.version == localversion then--开始游戏self.status = self.EventType.StartGameself:noticeEvent()print('11开始游戏啊啊啊啊啊啊啊啊啊啊啊啊啊啊!!!!!!')else--查找需要更新的资源并下载self.status = self.EventType.StartUpdateself:noticeEvent()self:findUpdateRes() endelse print("cmpVersions = xhr.readyState is:", xhr.readyState, "xhr.status is: ",xhr.status)endendxhr:registerScriptHandler(onReadyStateChange) xhr:send()
end--查找更新资源
function UpdateLogicLayer:findUpdateRes()local xhr = cc.XMLHttpRequest:new()   xhr.responseType = 4    xhr:open("POST", self.manifest.tableResUrl)  local function onReadyStateChange()   if xhr.readyState == 4 and (xhr.status >= 200 and xhr.status < 207) thenself.resConfigInfo = json.decode(xhr.response) self.updateResTable = self:findUpdateResTable()self:downloadRes() elseprint("findUpdateRes = xhr.readyState is:", xhr.readyState, "xhr.status is: ",xhr.status)endendxhr:registerScriptHandler(onReadyStateChange)   xhr:send('filename=/res_config.lua')
end--查找需要更新资源表(更新与新增,没考虑删除)
function UpdateLogicLayer:findUpdateResTable()local clientResTable = nillocal serverResTable = self.resConfigInfolocal fullPath = cc.FileUtils:getInstance():fullPathForFilename('resConfig.json') local fp = io.open(fullPath,'r')if fp thenlocal js = fp:read('*a')fp:close(fp)clientResTable = json.decode(js)elseprint('resConfig.json read error!')endlocal addResTable = {}local isUpdate = true  if clientResTable and serverResTable thenfor key1, var1 in ipairs(serverResTable) doisUpdate = truefor key2, var2 in ipairs(clientResTable) doif var2.name == var1.name thenif var2.md5 == var1.md5 thenisUpdate = falseendbreakendendif isUpdate == true thentable.insert(addResTable,var1.name)endendelseprint('local configFile error!(res_config_local or res_config_server)')endreturn addResTable
end--下载更新资源
function UpdateLogicLayer:downloadRes()local fileName = self.updateResTable[self.updateResProgress] if fileName thenlocal xhr = cc.XMLHttpRequest:new()    xhr:open("POST", self.manifest.downloadResUrl)  local function onReadyStateChange()  if xhr.readyState == 4 and (xhr.status >= 200 and xhr.status < 207) thenself:localWriteRes(fileName,xhr.response)elseprint("downloadRes = xhr.readyState is:", xhr.readyState, "xhr.status is: ",xhr.status)endendxhr:registerScriptHandler(onReadyStateChange)   xhr:send('filename=' .. fileName) else--资源更新完成local fp = io.open(self.writeRootPath .. '/res/project.manifest', 'w')  if fp then local js = json.encode(self.manifest)fp:write(js)io.close(fp)endlocal fp = io.open(self.writeRootPath .. '/res/resConfig.json', 'w')if fp then local js = json.encode(self.resConfigInfo)fp:write(js)io.close(fp)end--更新完成开始游戏self.status = self.EventType.AssetsFinishself:noticeEvent()print('22开始游戏啊啊啊啊啊啊啊啊啊啊啊啊啊啊!!!!!!')endend--资源本地写入
function UpdateLogicLayer:localWriteRes(resName, resData)local lenthTable = {}local tempResName = resNamelocal maxLength = string.len(tempResName)local tag = string.find(tempResName,'/')while tag doif tag ~= 1 thentable.insert(lenthTable,tag) endtempResName = string.sub(tempResName,tag + 1,maxLength)tag = string.find(tempResName,'/')endlocal sub = 0for key, var in ipairs(lenthTable) dosub = sub + var endif sub ~= 0 thenlocal temp = string.sub(resName,1,sub + 1)local pathName = self.writeRootPath .. temp if not (cc.FileUtils:getInstance():isDirectoryExist(pathName)) then         cc.FileUtils:getInstance():createDirectory(pathName)   endend  self.updateResPath = self.writeRootPath .. resNamelocal fp = io.open(self.updateResPath, 'w')if fp thenfp:write(resData)io.close(fp)self.status = self.EventType.AssetsProgressself:noticeEvent()print("countRes = ", self.updateResProgress,"nameRes =  ",resName)self.updateResProgress = self.updateResProgress + 1self:downloadRes() elseprint('downloadRes write error!!')end
endfunction UpdateLogicLayer:noticeEvent()if self.callback thenself.callback(self,self.status)elseprint('callback is nil')end
endreturn UpdateLogicLayer
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275

UI层:

--[[
说明:
1,本地需求配置文件:project.manifest,   resConfig.json   2,循环post请求,有时会出现闪退情况,最好改成只发一次zip压缩包形式3,目前只支持ios,lua io库文件操作在andriod上不行,文件操作c实现(注意lua与c++交互对于char *遇/0结束问题,需要改lua绑定代码)]]local UpdateLogicLayer = require('app.views.Assets.UpdateLogicLayer')
local SelectSerAddrLayer = require("app.views.Login.SelectSerAddrLayer")
local UpdateUILayer = class("UpdateUILayer", cc.Layer)function UpdateUILayer:create()local view = UpdateUILayer.new()local function onNodeEvent(eventType)if eventType == "enter" thenview:onEnter()elseif eventType == "exit" thenview:onExit()endendview:registerScriptHandler(onNodeEvent)view:init()return view
endfunction UpdateUILayer:ctor()endfunction UpdateUILayer:onEnter()endfunction UpdateUILayer:onExit()endfunction UpdateUILayer:init()local updateLogicLayer = UpdateLogicLayer:create(function(sender,eventType) self:onEventCallBack(sender,eventType) end)self:addChild(updateLogicLayer)endfunction UpdateUILayer:onEventCallBack(sender,eventType)if eventType == sender.EventType.StartGame thenprint("startgame !!!")local view = SelectSerAddrLayer.new()self:addChild(view)elseif eventType == sender.EventType.StartUpdate thenprint("startupdate !!!")self:initAssetsUI()elseif eventType == sender.EventType.AssetsProgress thenprint("assetsprogress !!!")self:updateAssetsProgress(sender.updateResPath,sender.updateResTable,sender.updateResProgress)elseif eventType == sender.EventType.AssetsFinish thenprint("assetsfinish !!!")self:updateAssetsFinish(sender.writeRootPath)end
end--UI界面初始化
function UpdateUILayer:initAssetsUI()local assetsLayer = cc.CSLoader:createNode("csb/assetsUpdate_layer.csb") local visibleSize = cc.Director:getInstance():getVisibleSize()assetsLayer:setAnchorPoint(cc.p(0.5,0.5))assetsLayer:setPosition(visibleSize.width/2,visibleSize.height/2)self:addChild(assetsLayer) self.rootPanel = assetsLayer:getChildByName("Panel_root")self.widgetTable = {LoadingBar_1 = ccui.Helper:seekWidgetByName(self.rootPanel ,"LoadingBar_1"),Text_loadProgress = ccui.Helper:seekWidgetByName(self.rootPanel ,"Text_loadProgress"),Text_loadResPath = ccui.Helper:seekWidgetByName(self.rootPanel ,"Text_loadResPath"),Image_tag = ccui.Helper:seekWidgetByName(self.rootPanel ,"Image_tag"),}self.widgetTable.Image_tag:setVisible(false)self.widgetTable.LoadingBar_1:setPercent(1)self.widgetTable.Text_loadProgress:setString('0%')self.widgetTable.Text_loadResPath:setString('准备更新...')
end--资源更新完成
function UpdateUILayer:updateAssetsFinish(writePaht)self.widgetTable.Text_loadResPath:setString('资源更新完成...')self.widgetTable.Text_loadProgress:setString('100%')self:runAction(cc.Sequence:create(cc.DelayTime:create(1), cc.CallFunc:create(function() local view = SelectSerAddrLayer.new()self:addChild(view)end)))
end--资源更新中
function UpdateUILayer:updateAssetsProgress(resPath, updateResTable, updateResProgress)self.widgetTable.Text_loadResPath:setString(resPath)local percentMaxNum = #updateResTablelocal percentNum = math.floor((updateResProgress / percentMaxNum) * 100)self.widgetTable.LoadingBar_1:setPercent(percentNum) self.widgetTable.Text_loadProgress:setString(percentNum .. '%')
endreturn UpdateUILayer

Cocos2dx-- 资源热更新相关推荐

  1. 【步兵 cocos2dx】热更新(下)

    [步兵 cocos2dx]热更新(下) By EOS. 上一篇把基本的理论都讲了一遍,接下来讲一下代码部分. (ps:上下两篇讲完刚好,如果再来个上中下...岂不成三集篇了 =.=?) 热更否? 我这 ...

  2. 【Unity3D】基于AssetBundle实现资源热更新

    1 前言 Unity3D 本地资源一般放在 Resources 目录下,但是 Resouces 文件夹的大小不能超过 2G,使用 AssetBundle 管理资源可以解决 Resources 文件夹受 ...

  3. Addressable资源热更新疑问

    1)Addressable资源热更新疑问 ​2)如何解决远处网格线会花的问题 3)关于着色器中某些特殊图片的用途 4)Lightmap在内存中有重复加载 5)Unity Job System问题 这是 ...

  4. 关于uni-app的资源热更新!!!

    先看官方文档,对uni-app 的资源热更新有一个大致的了解: 整包升级 在线资源热更新 app方法 本文主要讲述,实现资源热更新时客户端需要进行的操作: 可以在***app.vue***的***on ...

  5. cocos2dx lua 热更新

    文章新地址 原理   每次登陆游戏利用cocos的assetManager从服务器拉去当前最新的两个文件. 一个是version.mainifest,一个project.mainifest. 这两个文 ...

  6. 【步兵 cocos2dx】热更新(上)

    [步兵 cocos2dx]lua的热更新 By EOS. 之前写好的热更,基本可以在项目中使用,接下来拿出来跟大家分享一下. 话不多说,直接进入正题...总感觉两行长度差太多不舒服,现在好了. lua ...

  7. cocos2dx版本热更新梳理

    文章转载自:http://blog.csdn.net/itol925/article/details/45968039 cocos热更新实现了项目资源和脚本文件的动态更新.当工程有新的改动时,用户无需 ...

  8. 《天涯明月刀》游戏资源热更新解决方案

    天刀在韩国化的过程中,韩方对天刀的游戏内商城功能提出了一系列的适应他们本土运营习惯的商业化改造需求,其中最重要的基础功能修改就是需要商城能够支持不停服修改商品内容或上下架商品,包含新增未事先打进版本配 ...

  9. GameFramework篇:StarForce资源热更新讲解(一:基本流程)

    准备工作: StarForce dev/Update分支 https://github.com/EllanJiang/StarForce/tree/dev/Update 注意下载子库         ...

  10. [unity3d]手游资源热更新策略探讨

    原地址:http://blog.csdn.net/dingxiaowei2013/article/details/20079683 我们学习了如何将资源进行打包.这次就可以用上场了,我们来探讨一下手游 ...

最新文章

  1. 【搜索专题】BFS中的多源BFS-双端队列BFS
  2. GraphQL和REST对比时需要注意些什么
  3. jsonp java后台_jsonp与Java后端
  4. [TPYBoard - Micropython] 五分种学会用TPYBoard - GPS 制作短信群发机
  5. C++初始编程及相关的问题总结
  6. mysql pkg_Solaris10下mysql的pkg安装方法
  7. C++_程序内存模型_内存四区_代码区_全局区_每种区域都存放什么样的变量---C++语言工作笔记028
  8. select coun等于0查出来不等于0_飞蚊症不需要治疗,是真的吗?
  9. Tesler去世丨你逃不过复制粘贴,同样也逃不过Tesler定律
  10. oracle 访问adf 慢,ORACLE ADF 问题总结
  11. WampServer图标黄色如何解决
  12. Python语音识别终极指南(收藏)
  13. ACR122U Android端应用开发入道指南
  14. [经典]PK:星际争霸 vs 魔兽争霸3 vs 红警
  15. 为什么阿里巴巴的企业Logo是它?
  16. Picture 线段树扫描线求轮廓线
  17. 动作捕捉系统用于苹果采摘机器人
  18. LitJson输出格式化Json字符串
  19. Mac之时间机器的使用
  20. redis如何将存储json和解析

热门文章

  1. python人民币转大写_python 人民币数字转大写中文
  2. 汽车计算机英语,架图你的行车电脑变成英文了怎么办??????
  3. 传统客服中心KPI指标汇总(非智能客服运营中心指标)
  4. 黑马培训的点滴(前端)
  5. 基于java的公链,第一个基于Java的BFT区块链 – Alienchain外星链号称以太坊Java版
  6. 查看工程里有多少行java代码
  7. 框架技术Vue --- 路由、后台管理系统页面
  8. java判断是否已数字结尾,正则表达式测试字符串是否以数字结尾
  9. CSDN写文章上传图片失败原因
  10. JVM 垃圾回收算法与ART CC回收器实现概述