您的位置:首页 > 移动开发 > Cocos引擎

关于cocos2dx客户端程序的自动更新解决方案

2016-06-27 10:44 671 查看
转载自:帘卷西风的专栏(http://blog.csdn.net/ljxfblog)

随着手机游戏的不断发展,游戏包也越来越大,手机网络游戏已经超过100M了,对于玩家来说,如果每次更新都要重新下载,那简直是灾难。而且如果上IOS平台,每次重新发包都要审核,劳神费力。所以当前的主流手游都开始提供自动更新的功能,在不改动C++代码的前提下,使用lua或者js进行业务逻辑开发,然后自动更新脚本和资源,方便玩家也方便研发者。

以前做端游的时候,自动更新是一个大工程,不仅要能更新资源和脚本,还要更新dll文件等,后期甚至要支持P2P,手游目前基本上都使用http方式。cocos2dx也提供了一个基础功能类AssetsManager,但是不太完善,只支持单包下载,版本控制基本没有。因此我决定在AssetsManager的基础上扩展一下这个功能。

先明确一下需求,自动更新需要做些什么?鉴于手游打包的方式,我们需要能够实现多版本增量更新游戏资源和脚本。明确设计思路,首先,服务器端,我们要要有一个版本计划,每一个版本和上一个版本之间的变化内容,打成一个zip包,并为之分配一个版本,然后将所有版本的信息放到http服务器上。然后,客户端程序启动的时候我们都需要读取服务器所有的版本信息,并与客户端版本进行比较,大于本地版本的都是需要下载的内容,将下载信息缓存起来,然后依次下载并解压,然后再正式进入游戏。

好了,我们先设计一下版本信息的格式吧!大家可以看看。

view
sourceprint?

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)


我们现在开始改造AssetsManager,首先定义下载任务的结构。

view
sourceprint?

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里面。

view
sourceprint?

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.
}


其次,改造void downloadAndUncompress(),把版本队里里面的任务取出来,下载解压,然后写本地版本号,直到版本队列为空。

view
sourceprint?

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.
}


再次,对lua进行支持,原来的方案是写了一个脚本代理类,但是写lua的中间代码比较麻烦,我采用了比较简单的方式,通常自动更新是全局的,所以自动更新的信息,我通过调用lua全局函数方式来处理。

view
sourceprint?

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.
}


最后把UpdateEngine使用PKG方式暴露给lua使用,这个lua文件是app里面调用的第一个lua文件,里面没有任何游戏内容相关,游戏内容都从main.lua开始加载,达到更新完毕后在加载其他lua文件的目的。

view
sourceprint?

1.
class
UpdateEngine
:
public
Node


2.
{


3.
public
:


4.
static
UpdateEngine*
create(
const
char
*
versionFileUrl,
const
char
*
storagePath);


5.


6.
virtual
void
update();


7.
};


好了,主要代码和思路以及给出来了,现在我们看看如何使用吧!

view
sourceprint?

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__)


最后说明一点,需要把下载解压的目录加到文件搜索的最前面,保证cocos2dx优先加载解压的lua文件和资源。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: