官方文档:https://docs.cocos.com/creator/manual/zh/advanced-topics/hot-update.html
官方热更新Github 仓库获取地址
官方的示例及文档,这里就不累赘说明了。这里直接从零开始制作实现一个简单的热更新示例。这里分享下本人在这过程中遇到的问题。费话不多说,直接进入正题。
开发环境:
nodejs(nodejs的安装请参考https://www.jianshu.com/p/d9dac85846b3)
CocosCreator 1.10.2
创建热更新工程
1,创建HotUpate项目
直接默认就好,这个场景呆会我们会直接用来做为1.2.0版本的升级使用
2,创建新更新场景hotupdate
基本内容为三个label 三个按钮 两个进度条,如下图
filelabel fileprogressBar 文件总数下载进度
bytelabel byteLabel 文件下载进度
info 显示信息
check 检查更新
gotoscene 场景跳转
update 更新
3,脚本编写
创建HotUpdate.ts脚本并挂载到hotupdate场景的Canvas上
脚本如下:
const {ccclass, property} = cc._decorator;
@ccclass
export default class HotUpdate extends cc.Component {
@property(cc.Label)
fileLabel : cc.Label = null;
@property(cc.Label)
byteLabel : cc.Label = null;
@property(cc.Label)
info: cc.Label = null;
@property(cc.Node)
fileProgressNode : cc.Node = null;
@property(cc.Node)
byteProgressNode : cc.Node = null;
@property(cc.RawAsset)
mainifestUrl: cc.RawAsset= null;
private fileProgressBar : cc.ProgressBar = null;
private byteProgressBar : cc.ProgressBar = null;
private _storagePath = "";
private _am = null;
private _updating = false;
checkUpdate( ){
cc.log(`--checkUpdate--`);
if ( this._updating ){
this.info.string = "Checking or updating...";
return;
}
if ( this._am.getState() == jsb.AssetsManager.State.UNINITED){
let url = this.mainifestUrl;
cc.log(`mainifestUrl : ${this.mainifestUrl}`);
this._am.loadLocalManifest(url);
}
if ( !this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()){
this.info.string = "Failed to load local manifest ....";
return;
}
this._updating = true;
this._am.setEventCallback(this.checkCb.bind(this));
this._am.checkUpdate();
}
checkCb( event ){
let needRestart = false;
let failed = false;
cc.log(`checkCb event code : ${event.getEventCode()}`);
switch (event.getEventCode())
{
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
this.info.string = "No local manifest file found, hot update skipped.";
break;
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
this.info.string = "Fail to download manifest file, hot update skipped.";
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
this.info.string = "Already up to date with the latest remote version.";
break;
case jsb.EventAssetsManager.NEW_VERSION_FOUND:
this.info.string = 'New version found, please try to update.';
//this.checkBtn.active = false;
this.fileProgressBar.progress = 0;
this.byteProgressBar.progress = 0;
break;
default:
return;
}
this._am.setEventCallback(null);
this._updating = false;
}
hotUpdate( ){
if ( this._am && !this._updating ){
this._am.setEventCallback(this.updateCb.bind(this));
if ( this._am.getState() === jsb.AssetsManager.State.UNINITED){
this._am.loadLocalManifest(this.mainifestUrl);
}
//this._updating = true;
this._am.update();
}
}
private updateCb( event ){
var needRestart = false;
var failed = false;
cc.log( `--update cb code : ${event.getEventCode()}`)
switch (event.getEventCode())
{
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
this.info.string = 'No local manifest file found, hot update skipped.';
failed = true;
break;
case jsb.EventAssetsManager.UPDATE_PROGRESSION:
this.byteProgressBar.progress = event.getPercent();
this.fileProgressBar.progress = event.getPercentByFile();
this.fileLabel.string = event.getDownloadedFiles() + ' / ' + event.getTotalFiles();
this.byteLabel.string = event.getDownloadedBytes() + ' / ' + event.getTotalBytes();
var msg = event.getMessage();
if (msg) {
this.info.string = 'Updated file: ' + msg;
}
break;
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
this.info.string = 'Fail to download manifest file, hot update skipped.';
failed = true;
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
this.info.string = 'Already up to date with the latest remote version.';
failed = true;
break;
case jsb.EventAssetsManager.UPDATE_FINISHED:
this.info.string = 'Update finished. ' + event.getMessage();
needRestart = true;
break;
case jsb.EventAssetsManager.UPDATE_FAILED:
this.info.string = 'Update failed. ' + event.getMessage();
this._updating = false;
break;
case jsb.EventAssetsManager.ERROR_UPDATING:
this.info.string = 'Asset update error: ' + event.getAssetId() + ', ' + event.getMessage();
break;
case jsb.EventAssetsManager.ERROR_DECOMPRESS:
this.info.string = event.getMessage();
break;
default:
break;
}
if (failed) {
this._am.setEventCallback(null);
this._updating = false;
}
if (needRestart) {
this._am.setEventCallback(null);
// Prepend the manifest's search path
var searchPaths = jsb.fileUtils.getSearchPaths();
var newPaths = this._am.getLocalManifest().getSearchPaths();
cc.log(JSON.stringify(newPaths));
Array.prototype.unshift.apply(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));
jsb.fileUtils.setSearchPaths(searchPaths);
cc.game.restart();
}
cc.log(`update cb failed : ${failed} , need restart : ${needRestart} , updating : ${this._updating}`);
}
changeScene() {
cc.director.loadScene("helloworld");
}
// LIFE-CYCLE CALLBACKS:
onLoad () {
this.fileProgressBar = this.fileProgressNode.getComponent(cc.ProgressBar);
this.byteProgressBar = this.byteProgressNode.getComponent(cc.ProgressBar);
this.fileProgressBar.progress = 0;
this.byteProgressBar.progress = 0;
if ( cc.sys.isNative ){
this._storagePath = jsb.fileUtils.getWritablePath();
cc.log(`Storage path for remote asset : ${this._storagePath}`);
this._am = new jsb.AssetsManager('', this._storagePath, this.versionCompareHanle.bind(this));
let self = this;
this._am.setVerifyCallback( function(path , asset) {
let compressed = asset.compressed;
let expectedMD5 = asset.md5;
let relativePath = asset.path;
let size = asset.size;
if( compressed ){
self.info.string = "Verification passed : " + relativePath;
return true;
}
else{
self.info.string = "Verification passed : " + relativePath + "(" + expectedMD5 + ")";
return true;
}
});
this.info.string = "Hot update is ready , please check or directly update.";
}
}
private versionCompareHanle( versionA : string , versionB : string ){
cc.log(`JS Custom Version Compare : version A is ${versionA} , version B is ${versionB}`);
let vA = versionA.split('.');
let vB = versionB.split('.');
cc.log(`version A ${vA} , version B ${vB}`);
for( let i = 0 ; i < vA.length && i < vB.length ; ++i ){
let a = parseInt(vA[i]);
let b = parseInt(vB[i]);
if ( a === b ){
continue;
}
else{
return a - b;
}
}
if ( vB.length > vA.length){
return -1;
}
return 0;
}
start () {
}
onDestroy(){
this._am.setEventCallback(null);
}
// update (dt) {}
}
4,构建编译版本
设置初始化场景为hotupdate场景
编译,构建版本完成后,我们会在build目录下生成一个jsb-link的目录如下
5,生成新旧版本
这里我们先生成一个新的版本资源,再生成旧版本的资源。到官方的示例中把version_generator.js复制到我们的项目目录下
生成新版本
执行命令: node version_generator.js -v 1.2.0 -u http://localhost/remote-assets/ -s build/jsb-link/ -d assets/
生成成功,此时项目assets目录下会生成两个版本文件
打开工程,配置mainifestUrl ,如图
6,本地版本服务器测试环境搭建
新建nodejs目录
编写脚本
var express = require('express')
var path = require('path')
var app = express();
app.use(express.static(path.join(__dirname,'hotUpdate')));
app.listen(80);
另存为app.js
在nodejs下新建hotUpdate 作为热更新资源的目录
在hotUpdate目录下新建remote-assets远程版本资源目录
将生成的版本信息文件、代码、资源复制到该目录下
运行资源服务器
控制台执行 node app.js
浏览器运行查看资源服务器是否正常启动,在浏览器中输入http://localhost/remote-assets/project.manifest ,如果看到如下内容,证明资源服务器正常运行
7,旧版本的制作
这里为了方便,直接删除提helloworld场景作为旧版本
重新构建编译,这里就不重写说明构建编译的过程了
构建编译完成后,生成旧版本,控制台执行
node version_generator.js -v 1.1.0 -u http://localhost/remote-assets/ -s build/jsb-link/ -d assets/
8,修改main.js文件
找到build/jsb-link/main.js
在对应位置添加如下代码,
代码:
if (window.jsb) {
var hotUpdateSearchPaths = cc.sys.localStorage.getItem('HotUpdateSearchPaths');
if (hotUpdateSearchPaths) {
jsb.fileUtils.setSearchPaths(JSON.parse(hotUpdateSearchPaths));
}
require('src/settings.js');
require('src/jsb_polyfill.js');
boot();
return;
}
9,运行测试热更新
打开jsb-link/frameworks/runtime-src/proj.win32工程
如果没有自己导出c++类到js中使用,这个步骤可以不做。
运行
以下是相关运行截图
检查更新(check)
更新(update)
下载的更新window上会自动下载到该目录下
C:\Users\Administrator\AppData\Local\你的项目名
跳转(goto):
参考链接:https://www.jianshu.com/p/cec263b6b9ac