5------Cocos2dx-- 资源热更新(lua)

来源:互联网 发布:淘宝什么利润大 编辑:程序博客网 时间:2024/05/17 07:09

前言:游戏上线后,我们常常还会需要更新,如新增玩法,活动等,这种动态的更新资源我们称为游戏的热更新。热更新一般只适用于脚本语言,因为脚本不需要编译,是一种解释性语言,而如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
<codeclass="hljs"ruby="">--创建可写目录与设置搜索路径
    self.writeRootPath = cc.FileUtils:getInstance():getWritablePath() .. _ClientGame2015_ 
    ifnot (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’);这些操作更新源代码.


分为逻辑层与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
<codeclass="hljs"livecodeserver="">
require('common.json')
local UpdateLogicLayer = class(UpdateLogicLayer, cc.Node)
 
function UpdateLogicLayer:create(callback)
    local view = UpdateLogicLayer.new()
    local function onNodeEvent(eventType)
        ifeventType == enter then
            view:onEnter()
        elseif eventType == exit then
            view:onExit()
        end
    end
    view:registerScriptHandler(onNodeEvent)
    view:init(callback)
    returnview
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_ 
    ifnot (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')
    iffp 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()
        ifxhr.readyState == 4and (xhr.status >= 200and xhr.status < 207) then
            local localversion = self.manifest.version
            self.manifest = json.decode(xhr.response)
            ifself.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()  
        ifxhr.readyState == 4and (xhr.status >= 200and 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')
    iffp 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 
 
    ifclientResTable and serverResTable then
        forkey1, var1 in ipairs(serverResTable) do
            isUpdate = true
            forkey2, var2 in ipairs(clientResTable) do
                ifvar2.name == var1.name then
                    ifvar2.md5 == var1.md5 then
                        isUpdate = false
                    end
                    break
                end
            end
            ifisUpdate == truethen
                table.insert(addResTable,var1.name)
            end
        end
 
    else
        print('local configFile error!(res_config_local or res_config_server)')
    end
 
    returnaddResTable
end
 
 
--下载更新资源
function UpdateLogicLayer:downloadRes()
    local fileName = self.updateResTable[self.updateResProgress]
    iffileName then
        local xhr = cc.XMLHttpRequest:new()   
        xhr:open(POST, self.manifest.downloadResUrl) 
 
        local function onReadyStateChange() 
            ifxhr.readyState == 4and (xhr.status >= 200and 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'
        iffp then
            local js = json.encode(self.manifest)
            fp:write(js)
            io.close(fp)
        end
 
        local fp = io.open(self.writeRootPath .. '/res/resConfig.json','w')
        iffp 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,'/')
    whiletagdo
        iftag ~= 1then
            table.insert(lenthTable,tag)
        end
        tempResName = string.sub(tempResName,tag + 1,maxLength)
        tag = string.find(tempResName,'/')
    end
 
    local sub = 0
    forkey, var in ipairs(lenthTable) do
        sub = sub + var
    end
    ifsub ~= 0then
        local temp = string.sub(resName,1,sub + 1)
        local pathName = self.writeRootPath .. temp
        ifnot (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')
    iffp 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()
    ifself.callback then
        self.callback(self,self.status)
    else
        print('callback is nil')
    end
end
 
 
returnUpdateLogicLayer
 
 
</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
<codeclass="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)
        ifeventType == enter then
            view:onEnter()
        elseif eventType == exit then
            view:onExit()
        end
    end
    view:registerScriptHandler(onNodeEvent)
    view:init()
    returnview
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)
    ifeventType == 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
 
 
returnUpdateUILayer
 
 
</code>

这里写图片描述


--------------------------------------------------------------------------------------------------------------------------------

原文链接:http://www.2cto.com/kf/201510/445687.html
       著作权归作者所有,转载请联系作者获得授权。


0 0
原创粉丝点击