`
445822357
  • 浏览: 735680 次
文章分类
社区版块
存档分类
最新评论

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

 
阅读更多

转自:帘卷西风的专栏(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/11001scene.zip
  2. //格式为:文件包目录(http://203.195.148.180:8080/ts_update/)总版本数量(1)
  3. //版本号1(1001)版本文件1(scene.zip)...版本号n(1001)版本文件n(scene.zip)
我们现在开始改造AssetsManager,首先定义下载任务的结构。

  1. structUpdateItem
  2. {
  3. intversion;
  4. std::stringzipPath;
  5. std::stringzipUrl;
  6. UpdateItem(intv,std::stringp,std::stringu):version(v),zipPath(p),zipUrl(u){}
  7. };
  8. std::deque<UpdateItem>_versionUrls;

然后改造bool checkUpdate(),这里把服务器的版本内容解析出来,放到一个队列_versionUrls里面。

  1. boolUpdateEngine::checkUpdate()
  2. {
  3. if(_versionFileUrl.size()==0)returnfalse;
  4. _curl=curl_easy_init();
  5. if(!_curl)
  6. {
  7. CCLOG("cannotinitcurl");
  8. returnfalse;
  9. }
  10. _version.clear();
  11. CURLcoderes;
  12. curl_easy_setopt(_curl,CURLOPT_URL,_versionFileUrl.c_str());
  13. curl_easy_setopt(_curl,CURLOPT_SSL_VERIFYPEER,0L);
  14. curl_easy_setopt(_curl,CURLOPT_WRITEFUNCTION,getVersionCode);
  15. curl_easy_setopt(_curl,CURLOPT_WRITEDATA,&_version);
  16. if(_connectionTimeout)curl_easy_setopt(_curl,CURLOPT_CONNECTTIMEOUT,_connectionTimeout);
  17. curl_easy_setopt(_curl,CURLOPT_NOSIGNAL,1L);
  18. curl_easy_setopt(_curl,CURLOPT_LOW_SPEED_LIMIT,LOW_SPEED_LIMIT);
  19. curl_easy_setopt(_curl,CURLOPT_LOW_SPEED_TIME,LOW_SPEED_TIME);
  20. res=curl_easy_perform(_curl);
  21. if(res!=0)
  22. {
  23. Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,this]{
  24. if(this->_delegate)
  25. this->_delegate->onError(ErrorCode::NETWORK);
  26. });
  27. CCLOG("cannotgetversionfilecontent,errorcodeis%d",res);
  28. returnfalse;
  29. }
  30. intlocalVer=getVersion();
  31. StringBufferbuff(_version);
  32. intversion;
  33. shortversionCnt;
  34. stringversionUrl,pathUrl;
  35. buff>>pathUrl>>versionCnt;
  36. for(shorti=0;i<versionCnt;++i)
  37. {
  38. buff>>version>>versionUrl;
  39. if(version>localVer)
  40. {
  41. _versionUrls.push_back(UpdateItem(version,pathUrl,versionUrl));
  42. }
  43. }
  44. if(_versionUrls.size()<=0)
  45. {
  46. Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,this]{
  47. if(this->_delegate)
  48. this->_delegate->onError(ErrorCode::NO_NEW_VERSION);
  49. });
  50. CCLOG("thereisnotnewversion");
  51. returnfalse;
  52. }
  53. CCLOG("thereis%dnewversion!",_versionUrls.size());
  54. //设置下载目录,不存在则创建目录
  55. _downloadPath=FileUtils::getInstance()->getWritablePath();
  56. _downloadPath+="download_temp/";
  57. createDirectory(_downloadPath.c_str());
  58. returntrue;
  59. }
其次,改造void downloadAndUncompress(),把版本队里里面的任务取出来,下载解压,然后写本地版本号,直到版本队列为空。
  1. voidUpdateEngine::downloadAndUncompress()
  2. {
  3. while(_versionUrls.size()>0)
  4. {
  5. //取出当前第一个需要下载的url
  6. UpdateItemitem=_versionUrls.front();
  7. _packageUrl=item.zipPath+item.zipUrl;
  8. chardownVersion[32];
  9. sprintf(downVersion,"%d",item.version);
  10. _version=downVersion;
  11. //通知文件下载
  12. std::stringzipUrl=item.zipUrl;
  13. Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,this,zipUrl]{
  14. if(this->_delegate)
  15. this->_delegate->onDownload(zipUrl);
  16. });
  17. //开始下载,下载失败退出
  18. if(!downLoad())
  19. {
  20. Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,this]{
  21. if(this->_delegate)
  22. this->_delegate->onError(ErrorCode::UNDOWNED);
  23. });
  24. break;
  25. }
  26. //通知文件压缩
  27. Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,this,zipUrl]{
  28. if(this->_delegate)
  29. this->_delegate->onUncompress(zipUrl);
  30. });
  31. //解压下载的zip文件
  32. stringoutFileName=_downloadPath+TEMP_PACKAGE_FILE_NAME;
  33. if(!uncompress(outFileName))
  34. {
  35. Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,this]{
  36. if(this->_delegate)
  37. this->_delegate->onError(ErrorCode::UNCOMPRESS);
  38. });
  39. break;
  40. }
  41. //解压成功,任务出队列,写本地版本号
  42. _versionUrls.pop_front();
  43. Director::getInstance()->getScheduler()->performFunctionInCocosThread([&,this]{
  44. //写本地版本号
  45. UserDefault::getInstance()->setStringForKey("localVersion",_version);
  46. UserDefault::getInstance()->flush();
  47. //删除本次下载的文件
  48. stringzipfileName=this->_downloadPath+TEMP_PACKAGE_FILE_NAME;
  49. if(remove(zipfileName.c_str())!=0)
  50. {
  51. CCLOG("cannotremovedownloadedzipfile%s",zipfileName.c_str());
  52. }
  53. //如果更新任务已经完成,通知更新成功
  54. if(_versionUrls.size()<=0&&this->_delegate)
  55. this->_delegate->onSuccess();
  56. });
  57. }
  58. curl_easy_cleanup(_curl);
  59. _isDownloading=false;
  60. }
再次,对lua进行支持,原来的方案是写了一个脚本代理类,但是写lua的中间代码比较麻烦,我采用了比较简单的方式,通常自动更新是全局的,所以自动更新的信息,我通过调用lua全局函数方式来处理。

  1. voidUpdateEngineDelegate::onError(ErrorCodeerrorCode)
  2. {
  3. autoengine=LuaEngine::getInstance();
  4. lua_State*pluaState=engine->getLuaStack()->getLuaState();
  5. staticLuaFunctor<Type_Null,int>selfonError(pluaState,"UpdateLayer.onError");
  6. if(!selfonError(LUA_NOREF,nil,errorCode))
  7. {
  8. log("UpdateLayer.onErrorfailed!Because:%s",selfonError.getLastError());
  9. }
  10. }
  11. voidUpdateEngineDelegate::onProgress(intpercent,inttype/*=1*/)
  12. {
  13. autoengine=LuaEngine::getInstance();
  14. lua_State*pluaState=engine->getLuaStack()->getLuaState();
  15. staticLuaFunctor<Type_Null,int,int>selfonProgress(pluaState,"UpdateLayer.onProgress");
  16. if(!selfonProgress(LUA_NOREF,nil,percent,type))
  17. {
  18. log("UpdateLayer.onProgressfailed!Because:%s",selfonProgress.getLastError());
  19. }
  20. }
  21. voidUpdateEngineDelegate::onSuccess()
  22. {
  23. autoengine=LuaEngine::getInstance();
  24. lua_State*pluaState=engine->getLuaStack()->getLuaState();
  25. staticLuaFunctor<Type_Null>selfonSuccess(pluaState,"UpdateLayer.onSuccess");
  26. if(!selfonSuccess(LUA_NOREF,nil))
  27. {
  28. log("UpdateLayer.onSuccessfailed!Because:%s",selfonSuccess.getLastError());
  29. }
  30. }
  31. voidUpdateEngineDelegate::onDownload(stringpackUrl)
  32. {
  33. autoengine=LuaEngine::getInstance();
  34. lua_State*pluaState=engine->getLuaStack()->getLuaState();
  35. staticLuaFunctor<Type_Null,string>selfonDownload(pluaState,"UpdateLayer.onDownload");
  36. if(!selfonDownload(LUA_NOREF,nil,packUrl))
  37. {
  38. log("UpdateLayer.onDownloadfailed!Because:%s",selfonDownload.getLastError());
  39. }
  40. }
  41. voidUpdateEngineDelegate::onUncompress(stringpackUrl)
  42. {
  43. autoengine=LuaEngine::getInstance();
  44. lua_State*pluaState=engine->getLuaStack()->getLuaState();
  45. staticLuaFunctor<Type_Null,string>selfonUncompress(pluaState,"UpdateLayer.onUncompress");
  46. if(!selfonUncompress(LUA_NOREF,nil,packUrl))
  47. {
  48. log("UpdateLayer.onUncompressfailed!Because:%s",selfonUncompress.getLastError());
  49. }
  50. }
最后把UpdateEngine使用PKG方式暴露给lua使用,这个lua文件是app里面调用的第一个lua文件,里面没有任何游戏内容相关,游戏内容都从main.lua开始加载,达到更新完毕后在加载其他lua文件的目的。
  1. classUpdateEngine:publicNode
  2. {
  3. public:
  4. staticUpdateEngine*create(constchar*versionFileUrl,constchar*storagePath);
  5. virtualvoidupdate();
  6. };
好了,主要代码和思路以及给出来了,现在我们看看如何使用吧!

  1. --update.lua
  2. require"Cocos2d"
  3. localtimer_local=nil
  4. --自动更新界面
  5. UpdateLayer={}
  6. localfunctionshowUpdate()
  7. iftimer_localthen
  8. cc.Director:getInstance():getScheduler():unscheduleScriptEntry(timer_local)
  9. timer_local=nil
  10. end
  11. locallayer=cc.Layer:create()
  12. localsceneGame=cc.Scene:create()
  13. localwinSize=cc.Director:getInstance():getWinSize()
  14. localbg_list=
  15. {
  16. "update/loading_bg_1.jpg",
  17. "update/loading_bg_2.jpg",
  18. "update/loading_bg_3.jpg",
  19. }
  20. localimageName=bg_list[math.random(3)]
  21. localbgSprite=cc.Sprite:create(imageName)
  22. bgSprite:setPosition(cc.p(winSize.width/2,winSize.height/2))
  23. layer:addChild(bgSprite)
  24. --进度条背景
  25. localloadingbg=cc.Sprite:create("update/loading_bd.png")
  26. loadingbg:setPosition(cc.p(winSize.width/2,winSize.height/2-40))
  27. layer:addChild(loadingbg)
  28. --进度条
  29. UpdateLayer._loadingBar=ccui.LoadingBar:create("update/loading.png",0)
  30. UpdateLayer._loadingBar:setSize(cc.size(880,20))
  31. UpdateLayer._loadingBar:setPosition(cc.p(winSize.width/2,winSize.height/2-40))
  32. layer:addChild(UpdateLayer._loadingBar)
  33. --提示信息
  34. UpdateLayer._labelNotice=cc.LabelTTF:create("","res/fonts/DFYuanW7-GB2312.ttf",25)
  35. UpdateLayer._labelNotice:setPosition(cc.p(winSize.width/2,winSize.height/2))
  36. layer:addChild(UpdateLayer._labelNotice)
  37. --动画切换场景
  38. sceneGame:addChild(layer)
  39. localtransScene=cc.TransitionFade:create(1.5,sceneGame,cc.c3b(0,0,0))
  40. cc.Director:getInstance():replaceScene(transScene)
  41. --初始化更新引擎
  42. localpath=cc.FileUtils:getInstance():getWritablePath().."temp/"
  43. UpdateLayer._updateEngine=UpdateEngine:create("http://203.195.148.180:8080/ts_update/version",path)
  44. UpdateLayer._updateEngine:retain()
  45. --启动定时器等待界面动画完成后开始更新
  46. localfunctionstartUpdate()
  47. UpdateLayer._loadingBar:setPercent(1)
  48. UpdateLayer._updateEngine:update()
  49. cc.Director:getInstance():getScheduler():unscheduleScriptEntry(timer_local)
  50. timer_local=nil
  51. end
  52. UpdateLayer._loadingBar:setPercent(0)
  53. UpdateLayer._labelNotice:setString(strg2u("正在检查新版本,请稍等"))
  54. timer_local=cc.Director:getInstance():getScheduler():scheduleScriptFunc(startUpdate,1.5,false)
  55. end
  56. --显示提示界面
  57. localfunctionshowNotice()
  58. iftimer_localthen
  59. cc.Director:getInstance():getScheduler():unscheduleScriptEntry(timer_local)
  60. timer_local=nil
  61. end
  62. locallayer=cc.Layer:create()
  63. localsceneGame=cc.Scene:create()
  64. localwinSize=cc.Director:getInstance():getWinSize()
  65. localnotice=cc.Sprite:create("update/notice.png")
  66. notice:setPosition(cc.p(winSize.width/2,winSize.height/2));
  67. layer:addChild(notice)
  68. sceneGame:addChild(layer)
  69. localtransScene=cc.TransitionFade:create(1.5,sceneGame,cc.c3b(0,0,0))
  70. cc.Director:getInstance():replaceScene(transScene)
  71. timer_local=cc.Director:getInstance():getScheduler():scheduleScriptFunc(showUpdate,2.6,false)
  72. end
  73. --显示logo界面
  74. localfunctionshowLogo()
  75. localsceneGame=cc.Scene:create()
  76. localwinSize=cc.Director:getInstance():getWinSize()
  77. locallayer=cc.LayerColor:create(cc.c4b(128,128,128,255),winSize.width,winSize.height)
  78. locallogo1=cc.Sprite:create("update/logo1.png")
  79. locallogo2=cc.Sprite:create("update/logo2.png")
  80. locallogo3=cc.Sprite:create("update/logo3.png")
  81. logo3:setPosition(cc.p(winSize.width/2,winSize.height/2))
  82. logo2:setPosition(cc.p(winSize.width-logo2:getContentSize().width/2,logo2:getContentSize().height/2))
  83. logo1:setPosition(cc.p(winSize.width-logo1:getContentSize().width/2,logo2:getContentSize().height+logo1:getContentSize().height/2))
  84. layer:addChild(logo1)
  85. layer:addChild(logo2)
  86. layer:addChild(logo3)
  87. sceneGame:addChild(layer)
  88. cc.Director:getInstance():runWithScene(sceneGame)
  89. timer_local=cc.Director:getInstance():getScheduler():scheduleScriptFunc(showNotice,1,false)
  90. end
  91. --更新主函数
  92. functionupdate()
  93. collectgarbage("collect")
  94. --avoidmemoryleak
  95. collectgarbage("setpause",100)
  96. collectgarbage("setstepmul",5000)
  97. math.randomseed(os.time())
  98. math.random(os.time())
  99. math.random(os.time())
  100. math.random(os.time())
  101. --显示logoo界面
  102. showLogo()
  103. end
  104. --c++更新信息回调
  105. localErrorCode=
  106. {
  107. NETWORK=0,
  108. CREATE_FILE=1,
  109. NO_NEW_VERSION=2,
  110. UNDOWNED=3,
  111. UNCOMPRESS=4,
  112. }
  113. localfunctionfinishUpdate()
  114. UpdateLayer.percent=0
  115. localfunctionaddPercent()
  116. ifUpdateLayer.percent<200then
  117. UpdateLayer.percent=UpdateLayer.percent+2
  118. ifUpdateLayer.percent<100then
  119. UpdateLayer._loadingBar:setPercent(UpdateLayer.percent)
  120. elseifUpdateLayer.percent<=100then
  121. UpdateLayer._loadingBar:setPercent(UpdateLayer.percent)
  122. UpdateLayer._labelNotice:setString(strg2u("当前版本已经最新,无需更新"))
  123. elseifUpdateLayer.percent>=200then
  124. cc.Director:getInstance():getScheduler():unscheduleScriptEntry(timer_local)
  125. timer_local=nil
  126. --进入游戏界面
  127. UpdateLayer=nil
  128. require"src.main"
  129. end
  130. end
  131. end
  132. timer_local=cc.Director:getInstance():getScheduler():scheduleScriptFunc(addPercent,0.05,false)
  133. end
  134. functionUpdateLayer.onError(errorCode)
  135. iferrorCode==ErrorCode.NO_NEW_VERSIONthen
  136. finishUpdate()
  137. elseiferrorCode==ErrorCode.NETWORKthen
  138. UpdateLayer._labelNotice:setString(strg2u("获取服务器版本失败,请检查您的网络"))
  139. elseiferrorCode==ErrorCode.UNDOWNEDthen
  140. UpdateLayer._labelNotice:setString(strg2u("下载文件失败,请检查您的网络"))
  141. elseiferrorCode==ErrorCode.UNCOMPRESSthen
  142. UpdateLayer._labelNotice:setString(strg2u("解压文件失败,请关闭程序重新更新"))
  143. end
  144. end
  145. functionUpdateLayer.onProgress(percent)
  146. localprogress=string.format("正在下载文件:%s(%d%%)",UpdateLayer._downfile,percent)
  147. print(strg2u(progress))
  148. UpdateLayer._labelNotice:setString(strg2u(progress))
  149. UpdateLayer._loadingBar:setPercent(percent)
  150. end
  151. functionUpdateLayer.onSuccess()
  152. UpdateLayer._labelNotice:setString(strg2u("自动更新完毕"))
  153. localfunctionupdateSuccess()
  154. cc.Director:getInstance():getScheduler():unscheduleScriptEntry(timer_local)
  155. timer_local=nil
  156. --进入游戏界面
  157. UpdateLayer=nil
  158. require"src.main"
  159. end
  160. timer_local=cc.Director:getInstance():getScheduler():scheduleScriptFunc(updateSuccess,2,false)
  161. end
  162. functionUpdateLayer.onDownload(str)
  163. UpdateLayer._downfile=str
  164. localdownfile=string.format("正在下载文件:%s(0%%)",str)
  165. print(strg2u(downfile))
  166. UpdateLayer._labelNotice:setString(strg2u(downfile))
  167. end
  168. functionUpdateLayer.onUncompress(str)
  169. localuncompress=string.format("正在解压文件:%s",str)
  170. print(strg2u(uncompress))
  171. UpdateLayer._labelNotice:setString(strg2u(uncompress))
  172. end
  173. --forCCLuaEnginetraceback
  174. function__G__TRACKBACK__(msg)
  175. print("----------------------------------------")
  176. print("LUAERROR:"..tostring(msg).."\n")
  177. print(debug.traceback())
  178. print("----------------------------------------")
  179. end
  180. xpcall(update,__G__TRACKBACK__)
最后说明一点,需要把下载解压的目录加到文件搜索的最前面,保证cocos2dx优先加载解压的lua文件和资源。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics