cocos2d-js热更新
1. 热更新基本思路
得到cocoachina论坛上fysp和akira_cn的帮助,理清了游戏热更新的思路:
- 执行AssetsManager后,搜索路径增加了jsb.fileUtils.getWritablePath()目录,并且是优先搜索;
- 需要热更新js不放在project.json中定义,等AssetsManager更新完了,用cc.loader.load动态加载;
- 所以在jsb.fileUtils.getWritablePath()目录下载的资源和js文件,与项目目录保持一致,那么优先加载新下载的资源和js文件,再进入游戏,从而实现热更新。
2. AssetsManager
cocos2d-js 3.0 rc0对AssetsManager功能进行了完善增强,支持多线程下载、断点续传、文件压缩、更好的进度信息以及错误重试机制,实现游戏资源文件和脚本文件的热更新变的更加方便。 用cocos new MyGame -l js -d /directory/to/project
方式新建一个测试项目,参考sample写的src/AssetsManager.js:
var@H_404_27@ __failCount =@H_404_27@ 0@H_404_27@;
var@H_404_27@ AssetsManagerLoaderScene =@H_404_27@ cc.Scene.extend({
_am:@H_404_27@null@H_404_27@,_progress0@H_404_27@,_percentByFilerun@H_404_27@:function@H_404_27@(){
if@H_404_27@ (!@H_404_27@cc.sys.isNative) {
this@H_404_27@.loadGame();
return@H_404_27@;
}
var@H_404_27@ layer =@H_404_27@ new@H_404_27@ cc.Layer@H_404_27@();
this@H_404_27@.addChild(layer);
this@H_404_27@._progress cc.LabelTTF@H_404_27@.create("@H_404_27@0%"@H_404_27@@H_404_27@,"@H_404_27@Arial"@H_404_27@@H_404_27@,12@H_404_27@);
this@H_404_27@._progress.x@H_404_27@ =@H_404_27@ cc.winSize.width@H_404_27@ / 2@H_404_27@;
y@H_404_27@ height@H_404_27@ / 2@H_404_27@ +@H_404_27@ 50@H_404_27@;
layer.addChild(this@H_404_27@._progress);
// android: /data/data/com.huanle.magic/files/@H_404_27@
var@H_404_27@ storagePath =@H_404_27@ (jsb.fileUtils ?@H_404_27@ jsb.fileUtils.getWritablePath() :@H_404_27@ "@H_404_27@./"@H_404_27@@H_404_27@);
this@H_404_27@._am jsb.AssetsManager@H_404_27@("@H_404_27@res/project.manifest"@H_404_27@@H_404_27@,storagePath);
this@H_404_27@._am.retain();
!@H_404_27@this@H_404_27@._am.getLocalManifest().isLoaded())
{
cc.log@H_404_27@("@H_404_27@Fail to update assets,step skipped."@H_404_27@@H_404_27@);
this@H_404_27@.loadGame();
}
else@H_404_27@
{
var@H_404_27@ that =@H_404_27@ this@H_404_27@;
var@H_404_27@ listener cc.EventListenerAssetsManager@H_404_27@(this@H_404_27@._am,function@H_404_27@(event@H_404_27@) {
switch@H_404_27@ (event@H_404_27@.getEventCode()){
case@H_404_27@ cc.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:@H_404_27@
cc"@H_404_27@No local manifest file found,skip assets update."@H_404_27@@H_404_27@);
that.loadGame();
break@H_404_27@;
case@H_404_27@ cc.EventAssetsManager.UPDATE_PROGRESSION:@H_404_27@
that._percent event@H_404_27@.getPercent();
that._percentByFile event@H_404_27@.getPercentByFile();
cc.log@H_404_27@(that._percent +@H_404_27@ "@H_404_27@%"@H_404_27@@H_404_27@);
var@H_404_27@ msg event@H_404_27@.getMessage();
if@H_404_27@ (msg) {
cc.log@H_404_27@(msg);
}
case@H_404_27@ cc.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:@H_404_27@
case@H_404_27@ cc.EventAssetsManager.ERROR_PARSE_MANIFEST"@H_404_27@Fail to download manifest file,update skipped."@H_404_27@@H_404_27@);
that.loadGame();
case@H_404_27@ cc.EventAssetsManager.ALREADY_UP_TO_DATEcase@H_404_27@ cc.EventAssetsManager.UPDATE_FINISHED"@H_404_27@Update finished."@H_404_27@@H_404_27@);
that.loadGame();
case@H_404_27@ cc.EventAssetsManager.UPDATE_Failed"@H_404_27@Update Failed. "@H_404_27@@H_404_27@ event@H_404_27@.getMessage());
__failCount ++@H_404_27@;
if@H_404_27@ (__failCount <@H_404_27@ 5@H_404_27@)
{
that._am.downloadFailedAssets();
}
else@H_404_27@
{
cc"@H_404_27@Reach maximum fail count,exit update process"@H_404_27@@H_404_27@);
__failCount 0@H_404_27@;
that.loadGame();
}
case@H_404_27@ cc.EventAssetsManager.ERROR_UPDATING"@H_404_27@Asset update error: "@H_404_27@@H_404_27@ event@H_404_27@.getAssetId() "@H_404_27@,"@H_404_27@@H_404_27@ event@H_404_27@.getMessage());
that.loadGame();
case@H_404_27@ cc.EventAssetsManager.ERROR_DECOMPRESS.log@H_404_27@(default@H_404_27@:@H_404_27@
break@H_404_27@;
}
});
cc.eventManager.addListener(listener,179)">1@H_404_27@);
this@H_404_27@._am.update();
cc.director.runScene(this@H_404_27@);
}
this@H_404_27@.schedule(this@H_404_27@.updateProgress,179)">0.5@H_404_27@);
},163)">loadGame@H_404_27@:function@H_404_27@(){
cc.loader.loadJs(["@H_404_27@src/files.js"@H_404_27@@H_404_27@],93)">function@H_404_27@(err@H_404_27@){
cc.loader.loadJs(jsFiles,93)">function@H_404_27@(err@H_404_27@){
cc.director.runScene(HelloWorldScene@H_404_27@());
});
});
},163)">updateProgress@H_404_27@:function@H_404_27@(dt@H_404_27@){
this@H_404_27@._progress.string =@H_404_27@ "@H_404_27@"@H_404_27@@H_404_27@ +@H_404_27@ this@H_404_27@._percent;
},163)">onExit@H_404_27@:function@H_404_27@(){
cc"@H_404_27@AssetsManager::onExit"@H_404_27@@H_404_27@);
this@H_404_27@._am.release();
this@H_404_27@._super();
}
});
修改项目目录下的main.js:
修改项目目录下的project.json:
就留一个AssetsManager.js,其他的js都通过它来加载。
增加一个src/files.js,需要动态加载的js文件都写在jsFiles这个数组里,这样js文件有增加变化,这个files.js一并更新,方便动态加载:
项目res目录增加一个project.manifest文件,AssetsManager.js里会用到:
这里主要配置服务端资源下载地址,具体字段说明,在下面服务端配置里说明。然后用cocos compile -p android
编译打包成一个apk安装包,等配置好服务端更新资源安装测试。
3. 服务端配置
需要建一个WEB服务器做下载用,在其WEB目录http://10.0.128.219/res
(我的测试机),增加version.manifest文件:
测试发现,AssetsManager首先会下载version.manifest文件,如果有更新的版本,那么才会去下载project.manifest,然后下载其中描述的资源文件。
project.manifest如下:
由于客户端本地project.manifest里groupVersions的版本信息比服务器端的低,所以AssetsManager会下载http://10.0.128.219/res/src/app.zip
到手机的/data/data/org.cocos2dx.hellojavascript/files/src/app.zip
,并且会自动解压,但不会删除压缩包本身。
建议用root过的android手机测试,否则/data/data是没有权限查看。运行客户端测试程序后用adb连接查看:
用firefox调试连上手机,发现app.js资源地址是/data/data/org.cocos2dx.hellojavascript/files/src/app.js,而不是assets/src/app.js,实现了热更新:
4. 增量更新
修改服务端version.manifest:这时在android客户端测试,已经更新到update1的,只会下载update2的更新,而没有更新过的,会把update1和update2都下载下来。