转载自:帘卷西风的专栏(http://blog.csdn.net/ljxfblog)
随着手机游戏的不断发展,游戏包也越来越大,手机网络游戏已经超过100M了,对于玩家来说,如果每次更新都要重新下载,那简直是灾难。而且如果上IOS平台,每次重新发包都要审核,劳神费力。所以当前的主流手游都开始提供自动更新的功能,在不改动C++代码的前提下,使用lua或者js进行业务逻辑开发,然后自动更新脚本和资源,方便玩家也方便研发者。
以前做端游的时候,自动更新是一个大工程,不仅要能更新资源和脚本,还要更新dll文件等,后期甚至要支持P2P,手游目前基本上都使用http方式。cocos2dx也提供了一个基础功能类AssetsManager,但是不太完善,只支持单包下载,版本控制基本没有。因此我决定在AssetsManager的基础上扩展一下这个功能。
先明确一下需求,自动更新需要做些什么?鉴于手游打包的方式,我们需要能够实现多版本增量更新游戏资源和脚本。明确设计思路,首先,服务器端,我们要要有一个版本计划,每一个版本和上一个版本之间的变化内容,打成一个zip包,并为之分配一个版本,然后将所有版本的信息放到http服务器上。然后,客户端程序启动的时候我们都需要读取服务器所有的版本信息,并与客户端版本进行比较,大于本地版本的都是需要下载的内容,将下载信息缓存起来,然后依次下载并解压,然后再正式进入游戏。
好了,我们先设计一下版本信息的格式吧!大家可以看看。
1.
http:
//203.195.148.180:8080/ts_update/ 1 1001 scene.zip
2.
//格式为:文件包目录(http://203.195.148.180:8080/ts_update/) 总版本数量(1)
3.
//版本号1(1001) 版本文件1(scene.zip) ... 版本号n(1001) 版本文件n(scene.zip)
01.
struct UpdateItem
02.
{
03.
int
version;
04.
std::string zipPath;
05.
std::string zipUrl;
06.
07.
UpdateItem(
int
v, std::string p, std::string u) : version(v), zipPath(p), zipUrl(u) {}
08.
};
09.
std::deque<UpdateItem> _versionUrls;
然后改造bool checkUpdate(),这里把服务器的版本内容解析出来,放到一个队列_versionUrls里面。
01.
bool UpdateEngine::checkUpdate()
02.
{
03.
if
(_versionFileUrl.size() ==
0
)
return
false
;
04.
05.
_curl = curl_easy_init();
06.
if
(!_curl)
07.
{
08.
CCLOG(
"can not init curl"
);
09.
return
false
;
10.
}
11.
_version.clear();
12.
13.
CURLcode res;
14.
curl_easy_setopt(_curl, CURLOPT_URL, _versionFileUrl.c_str());
15.
curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, 0L);
16.
curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, getVersionCode);
17.
curl_easy_setopt(_curl, CURLOPT_WRITEDATA, &_version);
18.
if
(_connectionTimeout) curl_easy_setopt(_curl, CURLOPT_CONNECTTIMEOUT, _connectionTimeout);
19.
curl_easy_setopt(_curl, CURLOPT_NOSIGNAL, 1L);
20.
curl_easy_setopt(_curl, CURLOPT_LOW_SPEED_LIMIT, LOW_SPEED_LIMIT);
21.
curl_easy_setopt(_curl, CURLOPT_LOW_SPEED_TIME, LOW_SPEED_TIME);
22.
res = curl_easy_perform(_curl);
23.
24.
if
(res !=
0
)
25.
{
26.
Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,
this
]{
27.
if
(
this
->_delegate)
28.
this
->_delegate->onError(ErrorCode::NETWORK);
29.
});
30.
CCLOG(
"can not get version file content, error code is %d"
, res);
31.
return
false
;
32.
}
33.
34.
int
localVer = getVersion();
35.
StringBuffer buff(_version);
36.
37.
int
version;
38.
short
versionCnt;
39.
string versionUrl, pathUrl;
40.
buff >> pathUrl >> versionCnt;
41.
for
(
short
i =
0
; i < versionCnt; ++i)
42.
{
43.
buff >> version >> versionUrl;
44.
if
(version > localVer)
45.
{
46.
_versionUrls.push_back(UpdateItem(version, pathUrl, versionUrl));
47.
}
48.
}
49.
if
(_versionUrls.size() <=
0
)
50.
{
51.
Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,
this
]{
52.
if
(
this
->_delegate)
53.
this
->_delegate->onError(ErrorCode::NO_NEW_VERSION);
54.
});
55.
CCLOG(
"there is not new version"
);
56.
return
false
;
57.
}
58.
CCLOG(
"there is %d new version!"
, _versionUrls.size());
59.
60.
//设置下载目录,不存在则创建目录
61.
_downloadPath = FileUtils::getInstance()->getWritablePath();
62.
_downloadPath +=
"download_temp/"
;
63.
createDirectory(_downloadPath.c_str());
64.
return
true
;
65.
}
01.
void
UpdateEngine::downloadAndUncompress()
02.
{
03.
while
(_versionUrls.size() >
0
)
04.
{
05.
//取出当前第一个需要下载的url
06.
UpdateItem item = _versionUrls.front();
07.
_packageUrl = item.zipPath + item.zipUrl;
08.
char
downVersion[
32
];
09.
sprintf(downVersion,
"%d"
, item.version);
10.
_version = downVersion;
11.
12.
//通知文件下载
13.
std::string zipUrl = item.zipUrl;
14.
Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,
this
, zipUrl]{
15.
if
(
this
->_delegate)
16.
this
->_delegate->onDownload(zipUrl);
17.
});
18.
19.
//开始下载,下载失败退出
20.
if
(!downLoad())
21.
{
22.
Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,
this
]{
23.
if
(
this
->_delegate)
24.
this
->_delegate->onError(ErrorCode::UNDOWNED);
25.
});
26.
break
;
27.
}
28.
29.
//通知文件压缩
30.
Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,
this
, zipUrl]{
31.
if
(
this
->_delegate)
32.
this
->_delegate->onUncompress(zipUrl);
33.
});
34.
35.
//解压下载的zip文件
36.
string outFileName = _downloadPath + TEMP_PACKAGE_FILE_NAME;
37.
if
(!uncompress(outFileName))
38.
{
39.
Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,
this
]{
40.
if
(
this
->_delegate)
41.
this
->_delegate->onError(ErrorCode::UNCOMPRESS);
42.
});
43.
break
;
44.
}
45.
//解压成功,任务出队列,写本地版本号
46.
_versionUrls.pop_front();
47.
Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,
this
]{
48.
//写本地版本号
49.
UserDefault::getInstance()->setStringForKey(
"localVersion"
, _version);
50.
UserDefault::getInstance()->flush();
51.
52.
//删除本次下载的文件
53.
string zipfileName =
this
->_downloadPath + TEMP_PACKAGE_FILE_NAME;
54.
if
(remove(zipfileName.c_str()) !=
0
)
55.
{
56.
CCLOG(
"can not remove downloaded zip file %s"
, zipfileName.c_str());
57.
}
58.
//如果更新任务已经完成,通知更新成功
59.
if
(_versionUrls.size() <=
0
&&
this
->_delegate)
60.
this
->_delegate->onSuccess();
61.
});
62.
}
63.
64.
curl_easy_cleanup(_curl);
65.
_isDownloading =
false
;
66.
}
01.
void
UpdateEngineDelegate::onError(ErrorCode errorCode)
02.
{
03.
auto engine = LuaEngine::getInstance();
04.
lua_State* pluaState = engine->getLuaStack()->getLuaState();
05.
static
LuaFunctor<Type_Null,
int
> selfonError(pluaState,
"UpdateLayer.onError"
);
06.
if
(!selfonError(LUA_NOREF, nil, errorCode))
07.
{
08.
log(
"UpdateLayer.onError failed! Because: %s"
, selfonError.getLastError());
09.
}
10.
}
11.
12.
void
UpdateEngineDelegate::onProgress(
int
percent,
int
type
/* = 1 */
)
13.
{
14.
auto engine = LuaEngine::getInstance();
15.
lua_State* pluaState = engine->getLuaStack()->getLuaState();
16.
static
LuaFunctor<Type_Null,
int
,
int
> selfonProgress(pluaState,
"UpdateLayer.onProgress"
);
17.
if
(!selfonProgress(LUA_NOREF, nil, percent, type))
18.
{
19.
log(
"UpdateLayer.onProgress failed! Because: %s"
, selfonProgress.getLastError());
20.
}
21.
}
22.
23.
void
UpdateEngineDelegate::onSuccess()
24.
{
25.
auto engine = LuaEngine::getInstance();
26.
lua_State* pluaState = engine->getLuaStack()->getLuaState();
27.
static
LuaFunctor<Type_Null> selfonSuccess(pluaState,
"UpdateLayer.onSuccess"
);
28.
if
(!selfonSuccess(LUA_NOREF, nil))
29.
{
30.
log(
"UpdateLayer.onSuccess failed! Because: %s"
, selfonSuccess.getLastError());
31.
}
32.
}
33.
34.
void
UpdateEngineDelegate::onDownload(string packUrl)
35.
{
36.
auto engine = LuaEngine::getInstance();
37.
lua_State* pluaState = engine->getLuaStack()->getLuaState();
38.
static
LuaFunctor<Type_Null, string> selfonDownload(pluaState,
"UpdateLayer.onDownload"
);
39.
if
(!selfonDownload(LUA_NOREF, nil, packUrl))
40.
{
41.
log(
"UpdateLayer.onDownload failed! Because: %s"
, selfonDownload.getLastError());
42.
}
43.
}
44.
45.
void
UpdateEngineDelegate::onUncompress(string packUrl)
46.
{
47.
auto engine = LuaEngine::getInstance();
48.
lua_State* pluaState = engine->getLuaStack()->getLuaState();
49.
static
LuaFunctor<Type_Null, string> selfonUncompress(pluaState,
"UpdateLayer.onUncompress"
);
50.
if
(!selfonUncompress(LUA_NOREF, nil, packUrl))
51.
{
52.
log(
"UpdateLayer.onUncompress failed! Because: %s"
, selfonUncompress.getLastError());
53.
}
54.
}
1.
class
UpdateEngine :
public
Node
2.
{
3.
public
:
4.
static
UpdateEngine* create(
const
char
* versionFileUrl,
const
char
* storagePath);
5.
6.
virtual
void
update();
7.
};
001.
--update.lua
002.
require
"Cocos2d"
003.
004.
local timer_local = nil
005.
006.
--自动更新界面
007.
UpdateLayer = {}
008.
local function showUpdate()
009.
if
timer_local then
010.
cc.Director:getInstance():getScheduler():unscheduleScriptEntry(timer_local)
011.
timer_local = nil
012.
end
013.
014.
local layer = cc.Layer:create()
015.
local sceneGame = cc.Scene:create()
016.
local winSize = cc.Director:getInstance():getWinSize()
017.
018.
local bg_list =
019.
{
020.
"update/loading_bg_1.jpg"
,
021.
"update/loading_bg_2.jpg"
,
022.
"update/loading_bg_3.jpg"
,
023.
}
024.
local imageName = bg_list[math.random(
3
)]
025.
local bgSprite = cc.Sprite:create(imageName)
026.
bgSprite:setPosition(cc.p(winSize.width /
2
, winSize.height /
2
))
027.
layer:addChild(bgSprite)
028.
029.
--进度条背景
030.
local loadingbg = cc.Sprite:create(
"update/loading_bd.png"
)
031.
loadingbg:setPosition(cc.p(winSize.width /
2
, winSize.height /
2
-
40
))
032.
layer:addChild(loadingbg)
033.
034.
--进度条
035.
UpdateLayer._loadingBar = ccui.LoadingBar:create(
"update/loading.png"
,
0
)
036.
UpdateLayer._loadingBar:setSize(cc.size(
880
,
20
))
037.
UpdateLayer._loadingBar:setPosition(cc.p(winSize.width /
2
, winSize.height /
2
-
40
))
038.
layer:addChild(UpdateLayer._loadingBar)
039.
040.
--提示信息
041.
UpdateLayer._labelNotice = cc.LabelTTF:create(
""
,
"res/fonts/DFYuanW7-GB2312.ttf"
,
25
)
042.
UpdateLayer._labelNotice:setPosition(cc.p(winSize.width /
2
, winSize.height /
2
))
043.
layer:addChild(UpdateLayer._labelNotice)
044.
045.
--动画切换场景
046.
sceneGame:addChild(layer)
047.
local transScene = cc.TransitionFade:create(
1.5
, sceneGame, cc.c3b(
0
,
0
,
0
))
048.
cc.Director:getInstance():replaceScene(transScene)
049.
050.
--初始化更新引擎
051.
local path = cc.FileUtils:getInstance():getWritablePath() ..
"temp/"
052.
UpdateLayer._updateEngine = UpdateEngine:create(
"http://203.195.148.180:8080/ts_update/version"
, path)
053.
UpdateLayer._updateEngine:retain()
054.
055.
--启动定时器等待界面动画完成后开始更新
056.
local function startUpdate()
057.
UpdateLayer._loadingBar:setPercent(
1
)
058.
UpdateLayer._updateEngine:update()
059.
cc.Director:getInstance():getScheduler():unscheduleScriptEntry(timer_local)
060.
timer_local = nil
061.
end
062.
UpdateLayer._loadingBar:setPercent(
0
)
063.
UpdateLayer._labelNotice:setString(strg2u(
"正在检查新版本,请稍等"
))
064.
timer_local = cc.Director:getInstance():getScheduler():scheduleScriptFunc(startUpdate,
1.5
,
false
)
065.
end
066.
067.
--显示提示界面
068.
local function showNotice()
069.
if
timer_local then
070.
cc.Director:getInstance():getScheduler():unscheduleScriptEntry(timer_local)
071.
timer_local = nil
072.
end
073.
local layer = cc.Layer:create()
074.
local sceneGame = cc.Scene:create()
075.
local winSize = cc.Director:getInstance():getWinSize()
076.
077.
local notice = cc.Sprite:create(
"update/notice.png"
)
078.
notice:setPosition(cc.p(winSize.width/
2
, winSize.height/
2
));
079.
080.
layer:addChild(notice)
081.
sceneGame:addChild(layer)
082.
083.
local transScene = cc.TransitionFade:create(
1.5
, sceneGame, cc.c3b(
0
,
0
,
0
))
084.
cc.Director:getInstance():replaceScene(transScene)
085.
086.
timer_local = cc.Director:getInstance():getScheduler():scheduleScriptFunc(showUpdate,
2.6
,
false
)
087.
end
088.
089.
--显示logo界面
090.
local function showLogo()
091.
local sceneGame = cc.Scene:create()
092.
local winSize = cc.Director:getInstance():getWinSize()
093.
local layer = cc.LayerColor:create(cc.c4b(
128
,
128
,
128
,
255
), winSize.width, winSize.height)
094.
095.
local logo1 = cc.Sprite:create(
"update/logo1.png"
)
096.
local logo2 = cc.Sprite:create(
"update/logo2.png"
)
097.
local logo3 = cc.Sprite:create(
"update/logo3.png"
)
098.
099.
logo3:setPosition(cc.p(winSize.width /
2
, winSize.height /
2
))
100.
logo2:setPosition(cc.p(winSize.width - logo2:getContentSize().width /
2
, logo2:getContentSize().height /
2
))
101.
logo1:setPosition(cc.p(winSize.width - logo1:getContentSize().width /
2
, logo2:getContentSize().height + logo1:getContentSize().height /
2
))
102.
103.
layer:addChild(logo1)
104.
layer:addChild(logo2)
105.
layer:addChild(logo3)
106.
107.
sceneGame:addChild(layer)
108.
cc.Director:getInstance():runWithScene(sceneGame)
109.
110.
timer_local = cc.Director:getInstance():getScheduler():scheduleScriptFunc(showNotice,
1
,
false
)
111.
end
112.
113.
--更新主函数
114.
function update()
115.
collectgarbage(
"collect"
)
116.
-- avoid memory leak
117.
collectgarbage(
"setpause"
,
100
)
118.
collectgarbage(
"setstepmul"
,
5000
)
119.
math.randomseed(os.time())
120.
math.random(os.time())
121.
math.random(os.time())
122.
math.random(os.time())
123.
124.
--显示logoo界面
125.
showLogo()
126.
end
127.
128.
--c++更新信息回调
129.
local ErrorCode =
130.
{
131.
NETWORK =
0
,
132.
CREATE_FILE =
1
,
133.
NO_NEW_VERSION =
2
,
134.
UNDOWNED =
3
,
135.
UNCOMPRESS =
4
,
136.
}
137.
138.
local function finishUpdate()
139.
UpdateLayer.percent =
0
140.
local function addPercent()
141.
if
UpdateLayer.percent <
200
then
142.
UpdateLayer.percent = UpdateLayer.percent +
2
143.
if
UpdateLayer.percent <
100
then
144.
UpdateLayer._loadingBar:setPercent(UpdateLayer.percent)
145.
elseif UpdateLayer.percent <=
100
then
146.
UpdateLayer._loadingBar:setPercent(UpdateLayer.percent)
147.
UpdateLayer._labelNotice:setString(strg2u(
"当前版本已经最新,无需更新"
))
148.
elseif UpdateLayer.percent >=
200
then
149.
cc.Director:getInstance():getScheduler():unscheduleScriptEntry(timer_local)
150.
timer_local = nil
151.
152.
--进入游戏界面
153.
UpdateLayer = nil
154.
require
"src.main"
155.
end
156.
end
157.
end
158.
timer_local = cc.Director:getInstance():getScheduler():scheduleScriptFunc(addPercent,
0.05
,
false
)
159.
end
160.
161.
function UpdateLayer.onError(errorCode)
162.
if
errorCode == ErrorCode.NO_NEW_VERSION then
163.
finishUpdate()
164.
elseif errorCode == ErrorCode.NETWORK then
165.
UpdateLayer._labelNotice:setString(strg2u(
"获取服务器版本失败,请检查您的网络"
))
166.
elseif errorCode == ErrorCode.UNDOWNED then
167.
UpdateLayer._labelNotice:setString(strg2u(
"下载文件失败,请检查您的网络"
))
168.
elseif errorCode == ErrorCode.UNCOMPRESS then
169.
UpdateLayer._labelNotice:setString(strg2u(
"解压文件失败,请关闭程序重新更新"
))
170.
end
171.
end
172.
173.
function UpdateLayer.onProgress(percent)
174.
local progress = string.format(
"正在下载文件:%s(%d%%)"
, UpdateLayer._downfile, percent)
175.
print(strg2u(progress))
176.
UpdateLayer._labelNotice:setString(strg2u(progress))
177.
UpdateLayer._loadingBar:setPercent(percent)
178.
end
179.
180.
function UpdateLayer.onSuccess()
181.
UpdateLayer._labelNotice:setString(strg2u(
"自动更新完毕"
))
182.
local function updateSuccess()
183.
cc.Director:getInstance():getScheduler():unscheduleScriptEntry(timer_local)
184.
timer_local = nil
185.
186.
--进入游戏界面
187.
UpdateLayer = nil
188.
require
"src.main"
189.
end
190.
timer_local = cc.Director:getInstance():getScheduler():scheduleScriptFunc(updateSuccess,
2
,
false
)
191.
end
192.
193.
function UpdateLayer.onDownload(str)
194.
UpdateLayer._downfile = str
195.
local downfile = string.format(
"正在下载文件:%s(0%%)"
, str)
196.
print(strg2u(downfile))
197.
UpdateLayer._labelNotice:setString(strg2u(downfile))
198.
end
199.
200.
function UpdateLayer.onUncompress(str)
201.
local uncompress = string.format(
"正在解压文件:%s"
, str)
202.
print(strg2u(uncompress))
203.
UpdateLayer._labelNotice:setString(strg2u(uncompress))
204.
end
205.
206.
--
for
CCLuaEngine traceback
207.
function __G__TRACKBACK__(msg)
208.
print(
"----------------------------------------"
)
209.
print(
"LUA ERROR: "
.. tostring(msg) .. "
210.
")
211.
print(debug.traceback())
212.
print(
"----------------------------------------"
)
213.
end
214.
215.
xpcall(update, __G__TRACKBACK__)