当前位置 : 主页 > 手机开发 > 其它 >

Cocos2d-JS 热更新

来源:互联网 收集:自由互联 发布时间:2021-06-13
前言 工作需要,在空闲时间看了下Cocos2d-JS的热更新。对其进行了一个简单的实现,这里总结分享一下。 Cocos2d-JS 热更新 Cocos2d-JS 热更新是啥?Cocos2d-JS终归还是一个游戏引擎,就以游戏

前言
工作需要,在空闲时间看了下Cocos2d-JS的热更新。对其进行了一个简单的实现,这里总结分享一下。


Cocos2d-JS 热更新

Cocos2d-JS 热更新是啥?Cocos2d-JS终归还是一个游戏引擎,就以游戏的过程来理解吧。传统游戏需要更新人物动画、地图场景、游戏逻辑、背景音乐怎么办?新出一个APP放到应用商店等用户下载,或者好一点游戏内提示又升级并自行下载完整的新版本APP。

使用Cocos2d-JS的热更新,那就大不一样了。它可以做到进入游戏后下载需要更新的资源甚至是脚本本身,而且更新过程不需要退出游戏。只要用户联网,就能够保证使用到最新的资源。

这些场景就非常适合使用Hot Fix

  • 想在游戏中对春节开放新活动,不能保证应用商店能准时过审核上线

  • 发现一个严重的Bug,需要立即修复

  • 需要经常换游戏资源,提升新鲜感

  • 先放一个中规中矩的版本过市场审核,然后绕过审核自己直接推新内容,哈哈

对于游戏来说,这个特性是比较重量级的。不过因为JavaScript的语言特性能支持这一功能,所以Cocos2d-JS能用此特性,而Cocos2d-x无法使用。


特性

以下是官方提供的特性

  • 多线程并行下载支持

  • 两层进度统计信息:文件级以及字节级

  • Zip压缩文件支持

  • 断点续传

  • 详细的错误报告

  • 文件下载失败重试支持


Simple

1. manifest配置文件

热更新需要用到的配置文件有2种,都是以.manifest结尾。

一种是精简版,一种是完整版。精简版称为version.manifest,完整版称为project.manifest

version.manifest:

1 2 3 4 5 6 7 8 9 10 11 {      "packageUrl"  "http://127.0.0.1:8080/JsUpdateServer/res"      "remoteManifestUrl"  "http://127.0.0.1:8080/JsUpdateServer/res/project.manifest"      "remoteVersionUrl"  "http://127.0.0.1:8080/JsUpdateServer/res/version.manifest"      "version"  "1.0.0" ,      "groupVersions"  : {          "1"  "1.0.1" ,          "2"  "1.0.2"      },      "engineVersion"  "3.3" }

version.manifest是可选的,其中的所有字段也出现在project.manifest中,并且内容一样,如果没有就会从服务端下载完整版。但是如果完整版特别大,那么这个小版本的优势就很明显了。

project.manifest:

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 {      "packageUrl"  "http://127.0.0.1:8080/JsUpdateServer/res"      "remoteManifestUrl"  "http://127.0.0.1:8080/JsUpdateServer/res/project.manifest"      "remoteVersionUrl"  "http://127.0.0.1:8080/JsUpdateServer/res/version.manifest"      "version"  "1.0.0" ,      "groupVersions"  : {          "1"  "1.0.1" ,          "2"  "1.0.2"      },      "engineVersion"  "3.3" ,      "assets"  : {          "update1"  : {              "path"  "src/app.zip" ,              "md5"  "41D8E948052B5B714B14F81612CF534D" ,              "compressed"  true ,              "group"  "1"          },           "update2"  : {              "path"  "res/HelloWorld.png" ,              "md5"  "A0FA3FA681D500575012D5E802F74D50" ,              "group"  "1"          },          "update3"  : {              "path"  "res/Bound.png" ,              "md5"  "E7D4218B02CD0C5BB35ADC55E133DBA2" ,              "group"  "1"          },          "update4"  : {              "path"  "src/resource.js" ,              "md5"  "BA47101EBB65FBFCFB61C4CC57A306CA" ,              "group"  "2"          }      },      "searchPaths"  : [      "res/"      ] }

所有字段具体含义,官方文档的定义还是比较详细,只是少了groupVersions这个新字段:

  • packageUrl : 远程资源的下载根路径。

  • remoteVersionUrl : 远程版本文件的路径,用来判断服务器端是否有新版本的资源。

  • remoteManifestUrl : 远程配置文件的路径,包含版本信息以及所有资源信息。

  • version : 配置文件对应的版本。

  • groupVersions : 是新增的功能字段,用于做增量更新很方便。

  • engineVersion : 配置文件对应的引擎版本。

  • assets : 所有资源信息。

    • key : 升级名称

    • path : 键代表资源的相对路径(相对于packageUrl)。

    • md5 : md5值代表资源文件的版本信息。

    • compressed : [可选项] 如果值为true,文件被下载后会自动被解压,目前仅支持zip压缩格式。

  • searchPaths : 需要添加到Cocos2d引擎中的搜索路径列表。

以我的配置为例,一个完整的更新流程是这样的:

先通过本地的version.manifest和服务端的version.manifest比较,如果本地version低于服务端,那么就会再去获取project.manifest。

如果version相同,那么会比较groupVersions。

如果本地没有下载过groupVersions中的任何更新,那么会依次下载升级包。

如果本地下载过1.0.1版本的升级包,那么就会跳过1.0.1下载属于1.0.2版本的升级内容。

如果下载失败,或者没有网络导致更新失败的,会继续使用未更新前的版本。并且下次启动会继续尝试更新。

2. 创建后台

用一种你喜欢的方式起一个后台服务。我朴实无华的用IDE(Eclipse for JavaEE)建立了一个空的Web工程,用Tomcat起了一个后台。

从刚才的配置文件可以看出,Web工程名为JsUpdateServer。

WebContent的根目录下新建一个index.html文件,随便在里边写些东西,用来验证我的后台已经起来。

Run起来之后,在浏览器里输入http://localhost:8080/JsUpdateServer/index.html看看是不是能够看到你刚才写的内容?

到这里你的后台已经OK了~

3. 创建Cocos2d-JS工程

新建一个Cocos2d-JS工程,可以用专用的IDE(cocos Code IDE),官网有下载,也有如何配置的教程,这里就不多说了。

res目录下,新建一个project.manifest文件。这个是初始版本信息,之后的更新会用到这个文件。内容如下:

1 2 3 4 5 6 7 8 9 10 11      "packageUrl"  "http://127.0.0.1:8080/JsUpdateServer/res"      "remoteManifestUrl"  "http://127.0.0.1:8080/JsUpdateServer/res/project.manifest"      "remoteVersionUrl"  "http://127.0.0.1:8080/JsUpdateServer/res/version.manifest"      "version"  "1.0"      "engineVersion"  "3.3"      "assets"  : {      },      "searchPaths"  : [       }

在src目录下,新建一个jsList.js文件,内容如下:

1 2 3 4 var jsList = [                 "src/resource.js"                "src/app.js"                 ]

这个文件里包括的是需要加载的js文件路径,将会在其他地方加载。

src目录下,新建一个assetsManagerScene.js文件,这里就是热更新的主要逻辑了。代码如下:

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 var failCount = 0;  var maxFailCount = 1;    //最大错误重试次数 /**    * 自动更新js和资源    */  var AssetsManagerLoaderScene = cc.Scene.extend({  _am:null,  _progress:null,  _percent:0,  run:function(){  if  (!cc.sys.isNative) {  this .loadGame();  return } var layer =  new  cc.Layer();  this .addChild(layer);  this ._progress =  new  cc.LabelTTF.create( "update 0%" "Arial" , 12);  this ._progress.x = cc.winSize.width / 2;  this ._progress.y = cc.winSize.height / 2 + 50;  layer.addChild( this ._progress); var storagePath = (jsb.fileUtils ? jsb.fileUtils.getWritablePath() :  "./" ); cc. log ( "storagePath is "  + storagePath); this ._am =  new  jsb.AssetsManager( "res/project.manifest" , storagePath);  this ._am.retain(); if  (! this ._am.getLocalManifest().isLoaded())  //if (true) cc. log ( "Fail to update assets, step skipped." );  this .loadGame();  else  var that =  this cc.EventListenerAssetsManager var listener =  new  jsb.EventListenerAssetsManager( this ._am, function(event) {  switch  (event.getEventCode()){  case  jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:  cc. log ( "No local manifest file found, skip assets update." );  that.loadGame();  break case  jsb.EventAssetsManager.UPDATE_PROGRESSION:  that._percent = event.getPercent();  cc. log (that._percent +  "%" );  var msg = event.getMessage();  if  (msg) {  cc. log (msg);  break case  jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:  case  jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:  cc. log ( "Fail to download manifest file, update skipped." );  that.loadGame();  break case  jsb.EventAssetsManager.ALREADY_UP_TO_DATE:  cc. log ( "ALREADY_UP_TO_DATE." );  that.loadGame();  break case  jsb.EventAssetsManager.UPDATE_FINISHED:  cc. log ( "Update finished." );  that.loadGame();  break case  jsb.EventAssetsManager.UPDATE_FAILED:  cc. log ( "Update failed. "  + event.getMessage());  failCount++;  if  (failCount < maxFailCount)  that._am.downloadFailedAssets();  else  cc. log ( "Reach maximum fail count, exit update process" );  failCount = 0;  that.loadGame();  break case  jsb.EventAssetsManager.ERROR_UPDATING:  cc. log ( "Asset update error: "  + event.getAssetId() +  ", "  + event.getMessage());  that.loadGame();  break case  jsb.EventAssetsManager.ERROR_DECOMPRESS:  cc. log (event.getMessage());  that.loadGame();  break default break }); cc.eventManager.addListener(listener, 1);  this ._am.update();  cc.director.runScene( this );  } this .schedule( this .updateProgress, 0.5);  }, loadGame:function(){  //jsList是jsList.js的变量,记录全部js。  cc.loader.loadJs([ "src/jsList.js" ], function(){  cc.loader.loadJs(jsList, function(){  cc.director.runScene( new  HelloWorldScene());  });  });  }, updateProgress:function(dt){  this ._progress.string =  "update"  this ._percent +  "%" }, onExit:function(){  cc. log ( "AssetsManager::onExit" ); this ._am.release();  this ._super();  });

热更新主要是通过引擎提供的AssetsManager来实现的。

AssetsManagerLoaderScene里,用到了res/project.manifest,是用来对版本对比的。

下载完成后的资源会被解压,然后放在jsb.fileUtils.getWritablePath()的路径下,加到引擎的搜索范围中,并且他们的优先级是高于APP原本的资源路径的。所以相同的资源名称,引擎会优先使用更新路径下的文件,就达到更新的目的。

下载成功后会执行loadGame方法,里边会加载src/jsList.js的所有资源。也就是说通过AssetsManagerLoaderScene来确保热更新完成后,再加载所有资源。

由于资源和脚本的加载顺序发生了改变,所以还要修改根目录下的main.js和“project.json”。

main.js:

1 2 3 4 5 6 7 8 9 cc.game.onStart = function(){      cc.view.adjustViewPort( true );      cc.view.setDesignResolutionSize(800, 450, cc.ResolutionPolicy.SHOW_ALL);      cc.view.resizeWithBrowserSize( true );            var scene =  new  AssetsManagerLoaderScene();       scene.run();  }; cc.game.run();

将启动场景改为我的热更新场景AssetsManagerLoaderScene。

project.json:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 {      "project_type" : "javascript" ,      "debugMode" :1,      "showFPS" : true ,      "frameRate" :60,      "id" : "gameCanvas" ,      "renderMode" :0,      "engineDir" : "frameworks/cocos2d-html5" ,      "modules" :[          "cocos2d" ,          "extensions"      ],      "jsList" :[          "src/assetsManagerScene.js"       ] }

将jsList的值改为src/assetsManagerScene.js,只要加载这个js,其他js会在AssetsManagerLoaderScene中被加载。

到这里你可以跑起来看下,虽然获取不到升级文件,但是可以以最原始的版本跑起来。

4. 在后台配置升级文件

现在到后台工程的WebContent目录下添加升级文件。

从我的manifest配置文件你可以看到,我又建立了一个res文件夹,在其中分别建立src和res分别对应Cocos2d-JS工程中的资源、脚本文件夹。

随意修改一下app.js,将其作为更新内容使用。总结了几个要注意的地方。

升级文件的路径,一定要和配置文件中的path内容一致。

升级文件是.zip,记得compressed改为true。

添加新的资源文件要记得同步更新resource.js

新增js文件也记得同步更新jsList.js

5. 回到Cocos2d-JS工程

核对一下project.manifest文件中的地址是不是和后台一致,内容是否正确。

没问题的话就可以跑起来了,会发现你的升级内容被下载下来并且更新了。


参考

本文工程后台&Cocos2d-JS的简陋源码戳这里,Cocos2d-JS工程中的文件替换你的文件,通用的引擎工程太大没有上传。

其他可以使用到的教程

资源管理器ASSETS MANAGER

Cocos2d-JS热更新

来源网址:http://karelgt.com/Cocos2dJS 热更新/

网友评论