creator 热更新方法:
大致原理:
creator 打包出来的工程目录和vs项目的目录基本相同,在客户端实际执行的文件其实就是build目录下的src和res文件,游戏中只要动态的改变src res 内容即可达到热更新的目的。
实际上资源包里的文件不可能修改,但是可以在游戏运行开始时设置搜索路径,将需要更新的资源的路径放在搜索路径的最前面即可在加载游戏时优先加载需要热更新的文件,间接达到热更新的目的。
过程:
1、在服务器端放置一个记录版本号的文件 project.manifest文件(此文件用md5值记录当前版本)
2、在服务器端放置 需要热更新的src res 文件(在creator中点击构建项目 生成的build 目录下的src res 文件)
3、客户端本地也存放有一个记录版本号的文件 project.manifest (此文件用md5值记录当前版本)
4、在游戏启动后先加载服务器中的project.manifest文件 然后与本地的project.manifest文件对比 得出哪些文件需要加载(对比部分有引擎内部调用,用户程序中不用管)
5、下载服务器中的需要更新的src res文件 并保存到客户端的可写路径中 (可以将这个可写路径保存在userdefault中 在后面游戏启动时设置搜索路径)
6、重启游戏
7、在游戏启动时(main.js)中设置上一步的可写路径为搜索路径并前置(保证游戏加载的是最新的版本)
操作
1、生成project.manifest版本文件
在插件商店中下载 安装
生成manifest文件
creator中 点击 扩展---》热更新工具
版本号: 要求服务器中的版本号要高于本地的版本号(程序中利用对比版本号决定是否下载新版本 版本对比方法可以自己设置也可以用默认方法 )
资源服务器跟目录: 就是服务器中 src 和res 的上层目录
build项目资源文件目录: 用creator构建出的项目的build/jsb-default目录
manifest存储目录:
生成的manifest文件目录(这里直接放置assets目录下了 ,有人说需要生成在别的地方,然后在copy到工程项目中
不然会出问题,但是经测试这么做没有问题。。。。。)
点击生成 会在manifest存储目录中生成两个manifest文件(project.manifest 和version.manifest)
这里需要注意的是:
1.每次在生成manifest文件前都需要重新构建项目(保证manifest文件标识的是最新的文件) 只需要构建 不用编译
2.version.manifest里的内容实际上只是project.manifest文件的一部分,version文件只标识:远程包的 Manifest 文件中的packageUrl、remoteManifestUrl和remoteVersionUrl,
project文件还包含了每个src 和res
文件的md5值,当工程非常大是此文件可能比较大,添加一个version 文件的目的就是方便每次判断版本时先下载一个尽量小的version文件
先判断版本号是否一致 再决定是否需要下载更新project文件(优化 此部分用户也不需要管 ,引擎内部已封装)
2、部署服务器,
再web服务器中放置 由creator生成的 src ,res,versin.manifest, project.manifest文件 (也可以搭建本地服务器测试 具体搭建过程自行百度 。。。。。)
项目热更新代码:
cc.Class({
extends: cc.Component,
properties: {
manifestUrl: {
default: null,
url: cc.RawAsset //这里指向生成的project.manifest文件
},
tip: cc.Label, //这里都是标识更新进度的 可以删除相关代码
file: cc.Label,
progress: cc.Label,
filePath: cc.Label
},
checkCb: function (event) {
cc.log('Code: ' + event.getEventCode());
switch (event.getEventCode())
{
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
cc.log("No local manifest file found, hot update skipped.");
this.tip.getComponent(cc.Label).string = "没有本地manifest文件,跳过更新";
cc.eventManager.removeListener(this._checkListener);
break;
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
cc.log("Fail to download manifest file, hot update skipped.");
this.tip.getComponent(cc.Label).string = "下载manifest文件失败,跳过更新";
cc.eventManager.removeListener(this._checkListener);
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
cc.log("Already up to date with the latest remote version.");
this.tip.getComponent(cc.Label).string = "已经是最新版本,无需更新";
cc.eventManager.removeListener(this._checkListener);
break;
case jsb.EventAssetsManager.NEW_VERSION_FOUND:
cc.log("New version found start update ");
this.tip.getComponent(cc.Label).string = "发现新版本准备更新";
this._needUpdate = true;
cc.eventManager.removeListener(this._checkListener);
break;
default:
break;
}
this.hotUpdate();
},
updateCb: function (event) {
var needRestart = false;
var failed = false;
switch (event.getEventCode())
{
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
cc.log('No local manifest file found, hot update skipped.');
failed = true;
break;
case jsb.EventAssetsManager.UPDATE_PROGRESSION:
this.tip.getComponent(cc.Label).string = "正在更新";
cc.log( event.getDownloadedFiles() + ' / ' + event.getTotalFiles());
cc.log( event.getDownloadedBytes() + ' / ' + event.getTotalBytes());
var percent = event.getPercent();
var percentByFile = event.getPercentByFile();
this.file.getComponent(cc.Label).string = "下载文件数: " +
event.getDownloadedFiles() + ' / ' + event.getTotalFiles();
var msg = event.getMessage();
if (msg) {
cc.log(msg);
}
this.progress.getComponent(cc.Label).string = "文件进度:" + percent.toFixed(2) * 100 + "%"
cc.log(percent.toFixed(2)* 100 + '%');
break;
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
cc.log('Fail to download manifest file, hot update skipped.');
this.tip.getComponent(cc.Label).string = "下载失败";
failed = true;
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
cc.log('Already up to date with the latest remote version.');
this.tip.getComponent(cc.Label).string = "已经是最新版本";
failed = true;
break;
case jsb.EventAssetsManager.UPDATE_FINISHED:
cc.log('Update finished. ' + event.getMessage());
this.tip.getComponent(cc.Label).string = "更新完成:正在重启游戏";
needRestart = true;
break;
case jsb.EventAssetsManager.UPDATE_FAILED:
cc.log('Update failed. ' + event.getMessage());
this._failCount ++;
if (this._failCount < 5)
{
this._am.downloadFailedAssets();
}
else
{
cc.log('Reach maximum fail count, exit update process');
this._failCount = 0;
failed = true;
}
break;
case jsb.EventAssetsManager.ERROR_UPDATING:
cc.log('Asset update error: ' + event.getAssetId() + ', ' + event.getMessage());
break;
case jsb.EventAssetsManager.ERROR_DECOMPRESS:
cc.log(event.getMessage());
break;
default:
break;
}
if (failed) {
cc.eventManager.removeListener(this._updateListener);
}
if (needRestart) { //重新启动游戏
cc.eventManager.removeListener(this._updateListener);
// Prepend the manifest's search path
var searchPaths = jsb.fileUtils.getSearchPaths();
var newPaths = this._am.getLocalManifest().getSearchPaths();
Array.prototype.unshift(searchPaths, newPaths);
// This value will be retrieved and appended to the default search path during game startup,
// please refer to samples/js-tests/main.js for detailed usage.
// !!! Re-add the search paths in main.js is very important, otherwise, new scripts won't take effect.
cc.sys.localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths));
console.log("测试===getPath",searchPaths);
jsb.fileUtils.setSearchPaths(searchPaths);
cc.game.restart();
}
},
hotUpdate: function () {
if (this._am && this._needUpdate) {
this._updateListener = new jsb.EventListenerAssetsManager(this._am, this.updateCb.bind(this));
cc.eventManager.addListener(this._updateListener, 1);
this._failCount = 0;
this._am.update();
}
},
// use this for initialization
onLoad: function () {
// Hot update is only available in Native build
if (!cc.sys.isNative) {
return;
}
console.log("热更新部分");
var storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'remote-asset');
cc.log('Storage path for remote asset : ' + storagePath);
cc.log('Local manifest URL : ' + this.manifestUrl);
this._am = new jsb.AssetsManager(this.manifestUrl, storagePath);
var searchPaths = jsb.fileUtils.getSearchPaths();
cc.log("搜索路径",searchPaths);
this._am.retain();
this._needUpdate = false;
if (this._am.getLocalManifest().isLoaded())
{
this._checkListener = new jsb.EventListenerAssetsManager(this._am, this.checkCb.bind(this));
cc.eventManager.addListener(this._checkListener, 1);
this._am.checkUpdate();
}
},
onDestroy: function () {
this._am && this._am.release();
}
});
3、将 src res version.manifest project.manifest 文件放置到服务器中(与manifest文件中远程服务器目录一致)
4、随便修改项目(与服务器中项目作区别)
5、构建项目 ----》重新生成manifest文件(按照上面的步骤 版本号要低于服务器上的版本号)
这里注意:
服务器上的版本和本地版本 需要都是release版本,或者都是debug版本 否则会出现jsc文件的优先级高于js文件导致更新成功但热更新资源不生效的问题
6、编译打包 测试
这是就会看到更新到了服务器的最新版本