cocos2d-js 热更新代码

前端之家收集整理的这篇文章主要介绍了cocos2d-js 热更新代码前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

工作需要,在空闲时间看了下Cocos2dJS的热更新。对其进行了一个简单的实现,这里总结分享一下。忍不住吐槽一下Cocos2dJS的官方文档…版本落后很多,当前最新Release版本是V3.3,文档还是V3.0的东西,虽然有借鉴意义,不过感觉还是略坑,不能因为自己是开源就放松对文档的完善啊!国外大的开源项目,文档不都好好的么。

吐槽结束,开始干活!

Cocos2dJS 热更新

Cocos2dJS 热更新是啥?Cocos2dJS终归还是一个游戏引擎,就以游戏的过程来理解吧。传统游戏需要更新人物动画、地图场景、游戏逻辑、背景音乐怎么办?新出一个APP放到应用商店等用户下载,或者好一点游戏内提示升级并自行下载完整的新版本APP。
使用Cocos2dJS的热更新,那就大不一样了。它可以做到进入游戏后下载需要更新的资源甚至是脚本本身,而且更新过程不需要退出游戏。只要用户联网,就能够保证使用到最新的资源。
这些场景就非常适合使用Hot Fix

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

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

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

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

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

特性

以下是官方提供的特性

Simple

1. manifest配置文件

热更新需要用到的配置文件有2种,都是以.manifest结尾。
一种是精简版,一种是完整版。精简版称为version.manifest,完整版称为project.manifest
version.manifest:

{ "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:

"engineVersion" : "3.3", "assets" : { "update1" : { "path" : "src/app.zip", "md5" : "41D8E948052B5B714B14F81612CF534D", "compressed" : true, "group" : "1" }, "update2" : { "path" : "res/HelloWorld.png", "md5" : "A0FA3FA681D500575012D5E802F74D50", "update3" : { "path" : "res/Bound.png", "md5" : "E7D4218B02CD0C5BB35ADC55E133DBA2", "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. 创建Cocos2dJS工程

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

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

"version" : "1.0", "assets" : { }, "searchPaths" : [ ] }

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

var @H_846_502@jsList = [ "src/resource.js", "src/app.js" ]

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

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

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

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

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

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

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

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

main.js:

@H_846_502@cc.@H_846_502@game.@H_846_502@onStart = function(){ @H_846_502@cc.@H_846_502@view.@H_846_502@adjustViewPort(true); @H_846_502@cc.@H_846_502@view.@H_846_502@setDesignResolutionSize(800, 450, @H_846_502@cc.@H_846_502@ResolutionPolicy.@H_846_502@SHOW_ALL); @H_846_502@cc.@H_846_502@view.@H_846_502@resizeWithBrowserSize(true); var @H_846_502@scene = new @H_846_502@AssetsManagerLoaderScene(); @H_846_502@scene.@H_846_502@run(); }; @H_846_502@cc.@H_846_502@game.@H_846_502@run();

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

project.json:

{ "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文件夹,在其中分别建立srcres分别对应Cocos2dJS工程中的资源、脚本文件夹。

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

  1. 升级文件的路径,一定要和配置文件中的path内容一致。
  2. 升级文件是.zip,记得compressed改为true。
  3. 添加新的资源文件要记得同步更新resource.js
  4. 新增js文件也记得同步更新jsList.js

5. 回到Cocos2dJS工程

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

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

参考

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

其他可以使用到的教程
http://cocos2d-x.org/docs/manual/framework/html5/v3/assets-manager/zh
https://github.com/faint2death/cocos2d-js/blob/master/assetsmanager.md
原文链接:https://www.f2er.com/cocos2dx/340990.html

猜你在找的Cocos2d-x相关文章