Quick-Cocos2dx-- 资源热更新2

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

热更新基本思路

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

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

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

热更新注意点

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

我封装的这套热更新本地需要两个配置文件,一个记录版本信息与请求url,一个记录所有资源列表。这两个配置文件都是json格式,cocos自带json.lua解析库, json.decode(js):将js转lua表,json.encode(table):将lua表转js。配置表如下:
这里写图片描述
这里写图片描述

还发现一个lua io文件操作的坑,local fp = io.open(fullPath,’r’);这些操作在iZ喎�"http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vc7/J0tS1q2FuZHJvaWTJz8i0srvWp7PWoaPL+dLUyMi4/NDCzsS8/rbB0LS7ubXDztLDx2MrK9fUvLq34tew1Nl0b2x1Ycq508MowKnVuUZpbGVVdGlsc8DgKaGjyLu2+KOsYysr0+tsdWG0q7Xd19a3+7SuyrHT1tPQ0ru49r/To6xjo6xjKyu1xNfWt/u0rqOsyOe5+8rHY29uc3QgY2hhciog1eLW1qOsxMfDtNP2tb22/r341sa1xNfWvdowo6y+zcjPzqq94cr4oaPI57n7ysdzdGQ6OnN0cmluZ9PrbHVh1eLW1qOs1PLT0NK7uPa1pbbAtcSx5MG/wLSx7cq+s6S2yKOs0/a1vbb+vfjWxrXE19a92jDSsrK7u+G94cr4oaO2+M28xqzK/b7dwO/D5rrcv8nE3Lvh09C63LbgMNfWvdqjrMTHw7RsdWHT62MrK727u6XKx7K7xNzWsb3TvdPK1c3q1fu1xKGjveK+9rDst6jKx6O6PC9wPg0KPGJsb2NrcXVvdGU+DQoJPHA+Yysr1eKx373TytXX1rf7tK7V4tH5vdPK1aO6PGJyIC8+DQoJY2hhciAqcCA9ICZsZHF1bzthYmNkZWYmcmRxdW87OzxiciAvPg0KCXNpemVfdCBsZW4gPSA3OzxiciAvPg0KCXN0ZDo6c3RyaW5nIHN0ciA9IHN0ZDo6c3RyaW5nKHAgKyBsZW4pOzwvcD4NCgk8cD5sdWHV4rHf0N64xHRvbHVhvbu7pbT6wuujujxiciAvPg0KCWx1Yb3TytVjKyu3tbvY19a3+7Suo7rKudPDbHVhX3B1c2hsc3RyaW5nKCmjrM7Sv7TL/MrH08O1xGx1YV9wdXNoc3RyaW5nKCnV4rj2o6y908rVsrvN6tX70/YwveHK+KGjPGJyIC8+DQoJYysrvdPK1Wx1Ybe1u9jX1rf7tK6jusq508NsdWFfdG9sc3RyaW5nKCkszazJz6GjPC9wPg0KPC9ibG9ja3F1b3RlPg0KPGgyIGlkPQ=="热更新源代码">热更新源代码

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

逻辑层:

?
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
<code class = "hljs" livecodeserver= "" >
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 view
end
 
 
function 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
end
 
 
function UpdateLogicLayer:onEnter()
 
end
 
 
function UpdateLogicLayer:onExit()
 
end
 
 
function 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)   
     end
     local 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 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) 
 
     local 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, xhr.status is: ,xhr.status)
         end
     end
     xhr: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 ) then
             self.resConfigInfo = json.decode(xhr.response)
             self.updateResTable = self:findUpdateResTable()
             self:downloadRes()
 
         else
             print(findUpdateRes = xhr.readyState is:, xhr.readyState, xhr.status is: ,xhr.status)
         end
     end
     xhr:registerScriptHandler(onReadyStateChange)  
     xhr:send( 'filename=/res_config.lua'
end
 
 
--查找需要更新资源表(更新与新增,没考虑删除)
function UpdateLogicLayer:findUpdateResTable()
     local clientResTable = nil
     local serverResTable = self.resConfigInfo
     local fullPath = cc.FileUtils:getInstance():fullPathForFilename( 'resConfig.json' )
     local fp = io.open(fullPath, 'r' )
     if fp then
         local js = fp:read( '*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
 
     else
         print( 'local configFile error!(res_config_local or res_config_server)' )
     end
 
     return addResTable
end
 
 
--下载更新资源
function UpdateLogicLayer:downloadRes()
     local fileName = self.updateResTable[self.updateResProgress]
     if fileName then
         local 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 ) then
                 self:localWriteRes(fileName,xhr.response)
             else
                 print(downloadRes = xhr.readyState is:, xhr.readyState, xhr.status is: ,xhr.status)
             end
         end
         xhr: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)
         end
 
         local 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.AssetsFinish
         self:noticeEvent()
         print( '22开始游戏啊啊啊啊啊啊啊啊啊啊啊啊啊啊!!!!!!' )
 
     end
 
end
 
 
--资源本地写入
function UpdateLogicLayer:localWriteRes(resName, resData)
     local lenthTable = {}
     local tempResName = resName
     local maxLength = string.len(tempResName)
     local tag = string.find(tempResName, '/' )
     while tag do
         if tag ~= 1 then
             table.insert(lenthTable,tag)
         end
         tempResName = string.sub(tempResName,tag + 1 ,maxLength)
         tag = string.find(tempResName, '/' )
     end
 
     local sub = 0
     for key, var in ipairs(lenthTable) do
         sub = sub + var
     end
     if sub ~= 0 then
         local 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)  
         end
     end 
 
     self.updateResPath = self.writeRootPath .. resName
     local fp = io.open(self.updateResPath, 'w' )
     if fp then
         fp:write(resData)
         io.close(fp)
 
         self.status = self.EventType.AssetsProgress
         self:noticeEvent()
         print(countRes = , self.updateResProgress,nameRes =  ,resName)
         self.updateResProgress = self.updateResProgress + 1
         self:downloadRes()
     else
         print( 'downloadRes write error!!' )
     end
end
 
 
function UpdateLogicLayer:noticeEvent()
     if self.callback then
         self.callback(self,self.status)
     else
         print( 'callback is nil' )
     end
end
 
 
return UpdateLogicLayer
 
 
</code>

UI层:

?
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
<code class = "hljs" lua= "" >--[[
说明:
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 then
             view:onEnter()
         elseif eventType == exit then
             view:onExit()
         end
     end
     view:registerScriptHandler(onNodeEvent)
     view:init()
     return view
end
 
 
function UpdateUILayer:ctor()
 
end
 
 
function UpdateUILayer:onEnter()
 
end
 
 
function UpdateUILayer:onExit()
 
end
 
 
function UpdateUILayer:init()
     local updateLogicLayer = UpdateLogicLayer:create(function(sender,eventType) self:onEventCallBack(sender,eventType) end)
     self:addChild(updateLogicLayer)
 
end
 
 
function UpdateUILayer:onEventCallBack(sender,eventType)
     if eventType == sender.EventType.StartGame then
         print(startgame !!!)
         local view = SelectSerAddrLayer. new ()
         self:addChild(view)
 
     elseif eventType == sender.EventType.StartUpdate then
         print(startupdate !!!)
         self:initAssetsUI()
 
     elseif eventType == sender.EventType.AssetsProgress then
         print(assetsprogress !!!)
         self:updateAssetsProgress(sender.updateResPath,sender.updateResTable,sender.updateResProgress)
 
     elseif eventType == sender.EventType.AssetsFinish then
         print(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 = #updateResTable
     local percentNum = math.floor((updateResProgress / percentMaxNum) * 100 )
     self.widgetTable.LoadingBar_1:setPercent(percentNum)
     self.widgetTable.Text_loadProgress:setString(percentNum .. '%' )
end
 
 
return UpdateUILayer
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值