
前言:游戏上线后,我们常常还会需要更新,如新增玩法,活动等,这种动态的更新资源我们称为游戏的热更新。热更新一般只适用于脚本语言,因为脚本不需要编译,是一种解释性语言,而如C++语言是很难热更新的,其代码只要有改动就需要重新链接编译(接口统一,用动态库可以实现,不过太不灵活了)。
本章将讲讲用Cocos-lua引擎怎么实现热更新,其实Cocos自带也封装了热更新模块(AssetsManager,AssetsManagerEx),不过我没用自带的那套,自己封装了一套,其基本思路原理是一致的。
登入游戏先向服务端请求当前游戏版本号信息,与本地版本号比较,如果相同则说明没有资源需要更新直接进入游戏,而如果不相同,则说明有资源需要更新进入第2步。
向服务端请求当前所有资源的列表(资源名+MD5),与本地资源列表比较,找出需要更新的资源。
根据找出的需要更新资源,向服务端请求下载下来。(目前发现更新资源很多时,一个个循环向服务端请求可能中途会出错,所以最好是以zip包的形式一次性发送过来,客服端只请求一次)
热更新注意点 1,程序加载某个文件原理:首先一个程序加载本地硬盘某一文件最终加载的路径都是绝对全路径。而我们之所以还可以写相对路径也能找到对应的文件是因为还有一个搜索路径,搜索路径是用一个容器存储的,相对路径是这样得到全路径的 = 搜索路径(全路径) + 相对路径。就是只要加入到这个搜索路径中的路径,以后要加载这里面的文件就只需给文件名就可以了,前面的路径它会自动去搜索路径循环遍历查找。所以程序里我们一般不写绝对路径,而是把前面的全路径加入到搜索路径,之后只需写后面的相对路径就能查找到了。2,手游安装到手机上后,其安装目录是只读属性,以后是不能修改的。所以热更新的资源是没法下载到以前目录的,那么就得自己创建一个手机上可读写的目录,并将资源更新到这个目录。接下来还一个问题就是更新目录与以前资源目录不一致了,那么游戏中是怎么优先使用更新目录里的资源的呢?其实只要将创建的可读写目录路径添加到搜索路径中并将其插入到最前面即可,代码里统一是绝对路径。
文件的 *** 作我们使用cocos封装的fileUtils类,其中一些相关函数如:fullPathForfilename:返回全路径,cocos加载文件最后都是通过它转换到全路径加载的,addSearchPath:添加到搜索路径,getWritablePath:返回一个可读写的路径。下面是Lua代码:
--创建可写目录与设置搜索路径 self.writeRootPath = cc.fileUtils:getInstance():getWritablePath() .. "_ClIEntGame2015_" if not (cc.fileUtils:isDirectoryExist(self.writeRootPath)) then cc.fileUtils:createDirectory(self.writeRootPath) end local searchPaths = cc.fileUtils:getSearchPaths() table.insert(searchPaths,1,self.writeRootPath .. '/') table.insert(searchPaths,102);">2,0);">'/res/') table.insert(searchPaths,102);">3,0);">'/src/') cc.fileUtils: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交互代码:
@H_502_119@ 热更新源代码
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" then vIEw:onEnter() elseif eventType == "exit" then vIEw:onExit() end end vIEw:registerScriptHandler(onNodeEvent) vIEw:init(callback) return vIEwendfunction 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 = --开始游戏 StartUpdate = --开始更新 AssetsProgress = --资源更新中 AssetsFinish = 4,0);">--资源更新完成 } self.callback = nil --外部回调 self.status = self.EventType.None function UpdateLogicLayer:onEnter()function UpdateLogicLayer:onExit()function UpdateLogicLayer:init(callback) self.callback = callback --创建可写目录与设置搜索路径 self.writeRootPath = cc.fileUtils:getInstance():getWritablePath() .. if not (cc.fileUtils:getInstance():isDirectoryExist(self.writeRootPath)) then cc.fileUtils:getInstance():createDirectory(self.writeRootPath) end local searchPaths = cc.fileUtils:getInstance():getSearchPaths() table.insert(searchPaths,self.writeRootPath .. '/src/') cc.fileUtils:getInstance():setSearchPaths(searchPaths) --配置信息初始化 local fullPath = cc.fileUtils:getInstance():fullPathForfilename('project.manifest') local fp = io.open(fullPath,'r') if fp then local 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() --Post local xhr = cc.XMLhttpRequest:new() xhr.responseType = 4 --Json类型 xhr:open("POST",self.manifest.versionUrl) function onReadyStateChange() if xhr.readyState == 4 and (xhr.status >= 200 and xhr.status < 207) then local localversion = self.manifest.version self.manifest = Json.decode(xhr.response) if self.manifest.version == localversion then --开始游戏 self.status = self.EventType.StartGame self:noticeEvent() print('11开始游戏啊啊啊啊啊啊啊啊啊啊啊啊啊啊!!!!!!') else --查找需要更新的资源并下载 self.status = self.EventType.StartUpdate self:noticeEvent() self:findUpdateRes() end else print("cmpVersions = xhr.readyState is:",xhr.readyState,0);">"xhr.status is: ",xhr.status) end xhr:registerScriptHandler(onReadyStateChange) xhr:send() --查找更新资源function UpdateLogicLayer:findUpdateRes() new() xhr.responseType = 4 xhr:then self.resConfigInfo = Json.decode(xhr.response) self.updateRestable = self:findUpdateRestable() self:downloadRes() else print("findUpdateRes = xhr.readyState is:",136);">end xhr:registerScriptHandler(onReadyStateChange) xhr:send('filename=/res_config.lua') --查找需要更新资源表(更新与新增,没考虑删除)function UpdateLogicLayer:findUpdateRestable() local clIEntRestable = nil local serverRestable = self.resConfigInfo 'resConfig.Json') '*a') fp:close(fp) clIEntRestable = Json.decode(Js) else print('resConfig.Json read error!') end local addRestable = {} local isUpdate = true if clIEntRestable and serverRestable then for key1,var1 in ipairs(serverRestable) do isUpdate = true for key2,var2 in ipairs(clIEntRestable) do if var2.name == var1.name then if var2.md5 == var1.md5 then isUpdate = false end break end end if isUpdate == true then table.insert(addRestable,var1.name) end end 'local configfile error!(res_config_local or res_config_server)') end return addRestable--下载更新资源function UpdateLogicLayer:downloadRes() local filename = self.updateRestable[self.updateResProgress] if filename new() xhr:function onReadyStateChange() then self:localWriteRes(filename,xhr.response) else print("downloadRes = xhr.readyState is:",xhr.status) end xhr:registerScriptHandler(onReadyStateChange) xhr:'filename=' .. filename) else --资源更新完成 open(self.writeRootPath .. '/res/project.manifest',0);">'w') then local Js = Json.encode(self.manifest) fp:write(Js) io.close(fp) end '/res/resConfig.Json',0);">'w') local Js = Json.encode(self.resConfigInfo) fp:end --更新完成开始游戏 self.status = self.EventType.AssetsFinish self:noticeEvent() print('22开始游戏啊啊啊啊啊啊啊啊啊啊啊啊啊啊!!!!!!') end--资源本地写入function UpdateLogicLayer:localWriteRes(resname,resData) local lenthtable = {} local tempResname = resname local maxLength = string.len(tempResname) local tag = string.find(tempResname,0);">'/') while tag do if tag ~= 1 then table.insert(lenthtable,tag) end tempResname = string.sub(tempResname,tag + '/') local sub = 0 for key,var in ipairs(lenthtable) do sub = sub + var end if sub ~= 0 local temp = string.sub(resname,sub + 1) local pathname = self.writeRootPath .. temp if not (cc.fileUtils:getInstance():isDirectoryExist(pathname)) then cc.fileUtils:getInstance():createDirectory(pathname) end self.updateResPath = self.writeRootPath .. resname open(self.updateResPath,0);">'w') then fp:write(resData) io.close(fp) self.status = self.EventType.AssetsProgress self:noticeEvent() print("countRes = ",self.updateResProgress,0);">"nameRes = ",resname) self.updateResProgress = self.updateResProgress + 1 self:downloadRes() 'downloadRes write error!!') endfunction UpdateLogicLayer:noticeEvent() if self.callback then self.callback(self,self.status) 'callback is nil') endreturn UpdateLogicLayer1 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 275UI层:
--[[ 说明: 1,本地需求配置文件:project.manifest,resConfig.Json 2,循环post请求,有时会出现闪退情况,最好改成只发一次zip压缩包形式 3,目前只支持ios,lua io库文件 *** 作在andriod上不行,文件 *** 作c实现(注意lua与c++交互对于char *遇/0结束问题,需要改lua绑定代码) ]]local UpdateLogicLayer = 'app.vIEws.Assets.UpdateLogicLayer')local SelectSerAddrLayer = "app.vIEws.Login.SelectSerAddrLayer")local UpdateUILayer = class("UpdateUILayer",cc.Layer)function UpdateUILayer:create() local vIEw = UpdateUILayer.new() local function onNodeEvent(eventType) then vIEw:onEnter() elseif eventType == then vIEw:onExit() end end vIEw:registerScriptHandler(onNodeEvent) vIEw:init() return vIEwendfunction UpdateUILayer:ctor()function UpdateUILayer:onEnterfunction UpdateUILayer:onExitfunction UpdateUILayer:initlocal updateLogicLayer = UpdateLogicLayer:create(function(sender,eventType) self:onEventCallBack(sender,eventType) end) self:addChild(updateLogicLayer)function UpdateUILayer:onEventCallBackif eventType == sender.EventType.StartGame print("startgame !!!") local vIEw = SelectSerAddrLayer.new() self:addChild(vIEw) elseif eventType == sender.EventType.StartUpdate "startupdate !!!") self:initAssetsUI() elseif eventType == sender.EventType.AssetsProgress "assetsprogress !!!") self:updateAssetsProgress(sender.updateResPath,sender.updateRestable,sender.updateResProgress) elseif eventType == sender.EventType.AssetsFinish "assetsfinish !!!") self:updateAssetsFinish(sender.writeRootPath) endend--UI界面初始化function UpdateUILayer:initAssetsUIlocal assetsLayer = cc.csloader:createNode("csb/assetsUpdate_layer.csb") local visibleSize = cc.Director:getInstance():getVisibleSize() assetsLayer:setAnchorPoint(cc.p(0.5,102);">0.5)) assetsLayer:setposition(visibleSize.wIDth/2) self:addChild(assetsLayer) self.rootPanel = assetsLayer:getChildByname("Panel_root") self.Widgettable = { Loadingbar_1 = ccui.Helper:seekWidgetByname(self.rootPanel,0);">"Loadingbar_1"),Text_loadProgress = ccui.Helper:seekWidgetByname(self.rootPanel,0);">"Text_loadProgress"),Text_loadResPath = ccui.Helper:seekWidgetByname(self.rootPanel,0);">"Text_loadResPath"),Image_tag = ccui.Helper:seekWidgetByname(self.rootPanel,0);">"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('准备更新...')--资源更新完成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(() local vIEw = SelectSerAddrLayer.new() self:addChild(vIEw) end) ))--资源更新中function UpdateUILayer:updateAssetsProgress(resPath,updateRestable,updateResProgress) self.Widgettable.Text_loadResPath:setString(resPath) local percentMaxnum = #updateRestable local percentNum = math.floor((updateResProgress / percentMaxnum) * 100) self.Widgettable.Loadingbar_1:setPercent(percentNum) self.Widgettable.Text_loadProgress:setString(percentNum .. '%')endreturn UpdateUILayer总结以上是内存溢出为你收集整理的Cocos2dx-- 资源热更新全部内容,希望文章能够帮你解决Cocos2dx-- 资源热更新所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)