转自:帘卷西风的专栏(http://blog.csdn.net/ljxfblog)
随着手机游戏的不断发展,游戏包也越来越大,手机网络游戏已经超过100M了,对于玩家来说,如果每次更新都要重新下载,那简直是灾难。而且如果上IOS平台,每次重新发包都要审核,劳神费力。所以当前的主流手游都开始提供自动更新的功能,在不改动C++代码的前提下,使用lua或者js进行业务逻辑开发,然后自动更新脚本和资源,方便玩家也方便研发者。
以前做端游的时候,自动更新是一个大工程,不仅要能更新资源和脚本,还要更新dll文件等,后期甚至要支持P2P,手游目前基本上都使用http方式。cocos2dx也提供了一个基础功能类AssetsManager,但是不太完善,只支持单包下载,版本控制基本没有。因此我决定在AssetsManager的基础上扩展一下这个功能。
先明确一下需求,自动更新需要做些什么?鉴于手游打包的方式,我们需要能够实现多版本增量更新游戏资源和脚本。明确设计思路,首先,服务器端,我们要要有一个版本计划,每一个版本和上一个版本之间的变化内容,打成一个zip包,并为之分配一个版本,然后将所有版本的信息放到http服务器上。然后,客户端程序启动的时候我们都需要读取服务器所有的版本信息,并与客户端版本进行比较,大于本地版本的都是需要下载的内容,将下载信息缓存起来,然后依次下载并解压,然后再正式进入游戏。
好了,我们先设计一下版本信息的格式吧!大家可以看看。
- http://203.195.148.180:8080/ts_update/11001scene.zip
- //格式为:文件包目录(http://203.195.148.180:8080/ts_update/)总版本数量(1)
- //版本号1(1001)版本文件1(scene.zip)...版本号n(1001)版本文件n(scene.zip)
- structUpdateItem
- {
- intversion;
- std::stringzipPath;
- std::stringzipUrl;
- UpdateItem(intv,std::stringp,std::stringu):version(v),zipPath(p),zipUrl(u){}
- };
- std::deque<UpdateItem>_versionUrls;
然后改造bool checkUpdate(),这里把服务器的版本内容解析出来,放到一个队列_versionUrls里面。
- boolUpdateEngine::checkUpdate()
- {
- if(_versionFileUrl.size()==0)returnfalse;
- _curl=curl_easy_init();
- if(!_curl)
- {
- CCLOG("cannotinitcurl");
- returnfalse;
- }
- _version.clear();
- CURLcoderes;
- curl_easy_setopt(_curl,CURLOPT_URL,_versionFileUrl.c_str());
- curl_easy_setopt(_curl,CURLOPT_SSL_VERIFYPEER,0L);
- curl_easy_setopt(_curl,CURLOPT_WRITEFUNCTION,getVersionCode);
- curl_easy_setopt(_curl,CURLOPT_WRITEDATA,&_version);
- if(_connectionTimeout)curl_easy_setopt(_curl,CURLOPT_CONNECTTIMEOUT,_connectionTimeout);
- curl_easy_setopt(_curl,CURLOPT_NOSIGNAL,1L);
- curl_easy_setopt(_curl,CURLOPT_LOW_SPEED_LIMIT,LOW_SPEED_LIMIT);
- curl_easy_setopt(_curl,CURLOPT_LOW_SPEED_TIME,LOW_SPEED_TIME);
- res=curl_easy_perform(_curl);
- if(res!=0)
- {
- Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,this]{
- if(this->_delegate)
- this->_delegate->onError(ErrorCode::NETWORK);
- });
- CCLOG("cannotgetversionfilecontent,errorcodeis%d",res);
- returnfalse;
- }
- intlocalVer=getVersion();
- StringBufferbuff(_version);
- intversion;
- shortversionCnt;
- stringversionUrl,pathUrl;
- buff>>pathUrl>>versionCnt;
- for(shorti=0;i<versionCnt;++i)
- {
- buff>>version>>versionUrl;
- if(version>localVer)
- {
- _versionUrls.push_back(UpdateItem(version,pathUrl,versionUrl));
- }
- }
- if(_versionUrls.size()<=0)
- {
- Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,this]{
- if(this->_delegate)
- this->_delegate->onError(ErrorCode::NO_NEW_VERSION);
- });
- CCLOG("thereisnotnewversion");
- returnfalse;
- }
- CCLOG("thereis%dnewversion!",_versionUrls.size());
- //设置下载目录,不存在则创建目录
- _downloadPath=FileUtils::getInstance()->getWritablePath();
- _downloadPath+="download_temp/";
- createDirectory(_downloadPath.c_str());
- returntrue;
- }
- voidUpdateEngine::downloadAndUncompress()
- {
- while(_versionUrls.size()>0)
- {
- //取出当前第一个需要下载的url
- UpdateItemitem=_versionUrls.front();
- _packageUrl=item.zipPath+item.zipUrl;
- chardownVersion[32];
- sprintf(downVersion,"%d",item.version);
- _version=downVersion;
- //通知文件下载
- std::stringzipUrl=item.zipUrl;
- Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,this,zipUrl]{
- if(this->_delegate)
- this->_delegate->onDownload(zipUrl);
- });
- //开始下载,下载失败退出
- if(!downLoad())
- {
- Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,this]{
- if(this->_delegate)
- this->_delegate->onError(ErrorCode::UNDOWNED);
- });
- break;
- }
- //通知文件压缩
- Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,this,zipUrl]{
- if(this->_delegate)
- this->_delegate->onUncompress(zipUrl);
- });
- //解压下载的zip文件
- stringoutFileName=_downloadPath+TEMP_PACKAGE_FILE_NAME;
- if(!uncompress(outFileName))
- {
- Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,this]{
- if(this->_delegate)
- this->_delegate->onError(ErrorCode::UNCOMPRESS);
- });
- break;
- }
- //解压成功,任务出队列,写本地版本号
- _versionUrls.pop_front();
- Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,this]{
- //写本地版本号
- UserDefault::getInstance()->setStringForKey("localVersion",_version);
- UserDefault::getInstance()->flush();
- //删除本次下载的文件
- stringzipfileName=this->_downloadPath+TEMP_PACKAGE_FILE_NAME;
- if(remove(zipfileName.c_str())!=0)
- {
- CCLOG("cannotremovedownloadedzipfile%s",zipfileName.c_str());
- }
- //如果更新任务已经完成,通知更新成功
- if(_versionUrls.size()<=0&&this->_delegate)
- this->_delegate->onSuccess();
- });
- }
- curl_easy_cleanup(_curl);
- _isDownloading=false;
- }
- voidUpdateEngineDelegate::onError(ErrorCodeerrorCode)
- {
- autoengine=LuaEngine::getInstance();
- lua_State*pluaState=engine->getLuaStack()->getLuaState();
- staticLuaFunctor<Type_Null,int>selfonError(pluaState,"UpdateLayer.onError");
- if(!selfonError(LUA_NOREF,nil,errorCode))
- {
- log("UpdateLayer.onErrorfailed!Because:%s",selfonError.getLastError());
- }
- }
- voidUpdateEngineDelegate::onProgress(intpercent,inttype/*=1*/)
- {
- autoengine=LuaEngine::getInstance();
- lua_State*pluaState=engine->getLuaStack()->getLuaState();
- staticLuaFunctor<Type_Null,int,int>selfonProgress(pluaState,"UpdateLayer.onProgress");
- if(!selfonProgress(LUA_NOREF,nil,percent,type))
- {
- log("UpdateLayer.onProgressfailed!Because:%s",selfonProgress.getLastError());
- }
- }
- voidUpdateEngineDelegate::onSuccess()
- {
- autoengine=LuaEngine::getInstance();
- lua_State*pluaState=engine->getLuaStack()->getLuaState();
- staticLuaFunctor<Type_Null>selfonSuccess(pluaState,"UpdateLayer.onSuccess");
- if(!selfonSuccess(LUA_NOREF,nil))
- {
- log("UpdateLayer.onSuccessfailed!Because:%s",selfonSuccess.getLastError());
- }
- }
- voidUpdateEngineDelegate::onDownload(stringpackUrl)
- {
- autoengine=LuaEngine::getInstance();
- lua_State*pluaState=engine->getLuaStack()->getLuaState();
- staticLuaFunctor<Type_Null,string>selfonDownload(pluaState,"UpdateLayer.onDownload");
- if(!selfonDownload(LUA_NOREF,nil,packUrl))
- {
- log("UpdateLayer.onDownloadfailed!Because:%s",selfonDownload.getLastError());
- }
- }
- voidUpdateEngineDelegate::onUncompress(stringpackUrl)
- {
- autoengine=LuaEngine::getInstance();
- lua_State*pluaState=engine->getLuaStack()->getLuaState();
- staticLuaFunctor<Type_Null,string>selfonUncompress(pluaState,"UpdateLayer.onUncompress");
- if(!selfonUncompress(LUA_NOREF,nil,packUrl))
- {
- log("UpdateLayer.onUncompressfailed!Because:%s",selfonUncompress.getLastError());
- }
- }
- classUpdateEngine:publicNode
- {
- public:
- staticUpdateEngine*create(constchar*versionFileUrl,constchar*storagePath);
- virtualvoidupdate();
- };
- --update.lua
- require"Cocos2d"
- localtimer_local=nil
- --自动更新界面
- UpdateLayer={}
- localfunctionshowUpdate()
- iftimer_localthen
- cc.Director:getInstance():getScheduler():unscheduleScriptEntry(timer_local)
- timer_local=nil
- end
- locallayer=cc.Layer:create()
- localsceneGame=cc.Scene:create()
- localwinSize=cc.Director:getInstance():getWinSize()
- localbg_list=
- {
- "update/loading_bg_1.jpg",
- "update/loading_bg_2.jpg",
- "update/loading_bg_3.jpg",
- }
- localimageName=bg_list[math.random(3)]
- localbgSprite=cc.Sprite:create(imageName)
- bgSprite:setPosition(cc.p(winSize.width/2,winSize.height/2))
- layer:addChild(bgSprite)
- --进度条背景
- localloadingbg=cc.Sprite:create("update/loading_bd.png")
- loadingbg:setPosition(cc.p(winSize.width/2,winSize.height/2-40))
- layer:addChild(loadingbg)
- --进度条
- UpdateLayer._loadingBar=ccui.LoadingBar:create("update/loading.png",0)
- UpdateLayer._loadingBar:setSize(cc.size(880,20))
- UpdateLayer._loadingBar:setPosition(cc.p(winSize.width/2,winSize.height/2-40))
- layer:addChild(UpdateLayer._loadingBar)
- --提示信息
- UpdateLayer._labelNotice=cc.LabelTTF:create("","res/fonts/DFYuanW7-GB2312.ttf",25)
- UpdateLayer._labelNotice:setPosition(cc.p(winSize.width/2,winSize.height/2))
- layer:addChild(UpdateLayer._labelNotice)
- --动画切换场景
- sceneGame:addChild(layer)
- localtransScene=cc.TransitionFade:create(1.5,sceneGame,cc.c3b(0,0,0))
- cc.Director:getInstance():replaceScene(transScene)
- --初始化更新引擎
- localpath=cc.FileUtils:getInstance():getWritablePath().."temp/"
- UpdateLayer._updateEngine=UpdateEngine:create("http://203.195.148.180:8080/ts_update/version",path)
- UpdateLayer._updateEngine:retain()
- --启动定时器等待界面动画完成后开始更新
- localfunctionstartUpdate()
- UpdateLayer._loadingBar:setPercent(1)
- UpdateLayer._updateEngine:update()
- cc.Director:getInstance():getScheduler():unscheduleScriptEntry(timer_local)
- timer_local=nil
- end
- UpdateLayer._loadingBar:setPercent(0)
- UpdateLayer._labelNotice:setString(strg2u("正在检查新版本,请稍等"))
- timer_local=cc.Director:getInstance():getScheduler():scheduleScriptFunc(startUpdate,1.5,false)
- end
- --显示提示界面
- localfunctionshowNotice()
- iftimer_localthen
- cc.Director:getInstance():getScheduler():unscheduleScriptEntry(timer_local)
- timer_local=nil
- end
- locallayer=cc.Layer:create()
- localsceneGame=cc.Scene:create()
- localwinSize=cc.Director:getInstance():getWinSize()
- localnotice=cc.Sprite:create("update/notice.png")
- notice:setPosition(cc.p(winSize.width/2,winSize.height/2));
- layer:addChild(notice)
- sceneGame:addChild(layer)
- localtransScene=cc.TransitionFade:create(1.5,sceneGame,cc.c3b(0,0,0))
- cc.Director:getInstance():replaceScene(transScene)
- timer_local=cc.Director:getInstance():getScheduler():scheduleScriptFunc(showUpdate,2.6,false)
- end
- --显示logo界面
- localfunctionshowLogo()
- localsceneGame=cc.Scene:create()
- localwinSize=cc.Director:getInstance():getWinSize()
- locallayer=cc.LayerColor:create(cc.c4b(128,128,128,255),winSize.width,winSize.height)
- locallogo1=cc.Sprite:create("update/logo1.png")
- locallogo2=cc.Sprite:create("update/logo2.png")
- locallogo3=cc.Sprite:create("update/logo3.png")
- logo3:setPosition(cc.p(winSize.width/2,winSize.height/2))
- logo2:setPosition(cc.p(winSize.width-logo2:getContentSize().width/2,logo2:getContentSize().height/2))
- logo1:setPosition(cc.p(winSize.width-logo1:getContentSize().width/2,logo2:getContentSize().height+logo1:getContentSize().height/2))
- layer:addChild(logo1)
- layer:addChild(logo2)
- layer:addChild(logo3)
- sceneGame:addChild(layer)
- cc.Director:getInstance():runWithScene(sceneGame)
- timer_local=cc.Director:getInstance():getScheduler():scheduleScriptFunc(showNotice,1,false)
- end
- --更新主函数
- functionupdate()
- collectgarbage("collect")
- --avoidmemoryleak
- collectgarbage("setpause",100)
- collectgarbage("setstepmul",5000)
- math.randomseed(os.time())
- math.random(os.time())
- math.random(os.time())
- math.random(os.time())
- --显示logoo界面
- showLogo()
- end
- --c++更新信息回调
- localErrorCode=
- {
- NETWORK=0,
- CREATE_FILE=1,
- NO_NEW_VERSION=2,
- UNDOWNED=3,
- UNCOMPRESS=4,
- }
- localfunctionfinishUpdate()
- UpdateLayer.percent=0
- localfunctionaddPercent()
- ifUpdateLayer.percent<200then
- UpdateLayer.percent=UpdateLayer.percent+2
- ifUpdateLayer.percent<100then
- UpdateLayer._loadingBar:setPercent(UpdateLayer.percent)
- elseifUpdateLayer.percent<=100then
- UpdateLayer._loadingBar:setPercent(UpdateLayer.percent)
- UpdateLayer._labelNotice:setString(strg2u("当前版本已经最新,无需更新"))
- elseifUpdateLayer.percent>=200then
- cc.Director:getInstance():getScheduler():unscheduleScriptEntry(timer_local)
- timer_local=nil
- --进入游戏界面
- UpdateLayer=nil
- require"src.main"
- end
- end
- end
- timer_local=cc.Director:getInstance():getScheduler():scheduleScriptFunc(addPercent,0.05,false)
- end
- functionUpdateLayer.onError(errorCode)
- iferrorCode==ErrorCode.NO_NEW_VERSIONthen
- finishUpdate()
- elseiferrorCode==ErrorCode.NETWORKthen
- UpdateLayer._labelNotice:setString(strg2u("获取服务器版本失败,请检查您的网络"))
- elseiferrorCode==ErrorCode.UNDOWNEDthen
- UpdateLayer._labelNotice:setString(strg2u("下载文件失败,请检查您的网络"))
- elseiferrorCode==ErrorCode.UNCOMPRESSthen
- UpdateLayer._labelNotice:setString(strg2u("解压文件失败,请关闭程序重新更新"))
- end
- end
- functionUpdateLayer.onProgress(percent)
- localprogress=string.format("正在下载文件:%s(%d%%)",UpdateLayer._downfile,percent)
- print(strg2u(progress))
- UpdateLayer._labelNotice:setString(strg2u(progress))
- UpdateLayer._loadingBar:setPercent(percent)
- end
- functionUpdateLayer.onSuccess()
- UpdateLayer._labelNotice:setString(strg2u("自动更新完毕"))
- localfunctionupdateSuccess()
- cc.Director:getInstance():getScheduler():unscheduleScriptEntry(timer_local)
- timer_local=nil
- --进入游戏界面
- UpdateLayer=nil
- require"src.main"
- end
- timer_local=cc.Director:getInstance():getScheduler():scheduleScriptFunc(updateSuccess,2,false)
- end
- functionUpdateLayer.onDownload(str)
- UpdateLayer._downfile=str
- localdownfile=string.format("正在下载文件:%s(0%%)",str)
- print(strg2u(downfile))
- UpdateLayer._labelNotice:setString(strg2u(downfile))
- end
- functionUpdateLayer.onUncompress(str)
- localuncompress=string.format("正在解压文件:%s",str)
- print(strg2u(uncompress))
- UpdateLayer._labelNotice:setString(strg2u(uncompress))
- end
- --forCCLuaEnginetraceback
- function__G__TRACKBACK__(msg)
- print("----------------------------------------")
- print("LUAERROR:"..tostring(msg).."\n")
- print(debug.traceback())
- print("----------------------------------------")
- end
- xpcall(update,__G__TRACKBACK__)