Cocos Creator热更新操作(超详细)
解决热更新进入后第一次有效,之后再次进入又恢复到之前版本
1.安装插件
可以安装到项目中
也可以安装到本地 (全局)
安装完成之后要重启一下cocos
2.详细操作(第一版本)
1.构建项目
*在这里只构建,先不进行后续操作
2.打开热更新工具生成配置文件
若在选栏里没有此项,则是没有安装热更新插件或者没有安装成功。
-
版本号随便填一个,只是之后的后续的版本号必须比之前大
-
资源服务器url是后续版本的客户端更新时,需要连接服务器找到更新的配置文件,若有新版本则会更新。
比如: http://192.168.0.0:7777/hotUpdate ip地址为自己的服务器ip地址,端口号也是自己设置的,后面是静态文件夹目录
-
点击生成,然后打开目录将生成的压缩包解压
3.将解压的文件在客户端和服务端配置
客户端, 在resources放创一个目录hot,放project与version文件,(两个文件来自生成的压缩包中)
然后保存场景
服务端的,hotUpdate里面覆盖之前全部文件。
4.再次构建,编译并且打包app发给手机进行第一次版本测试
5(这一步有一个bug需要改动)
这里有一个bug,若是不修改,在后续版本更新中,部分场景的更新可能只有打开的第一次有,之后再次打开部分场景又恢复到之前版本。(贼坑)
1.找到当前打包项目的 main.js文件
2.将 这几行看起来极其不协调的代码段剪切,贴到后面去
3.放到 window.boot(); 上面
4.记得保存main.js,然后开始编译
这个main.js文件每一次点构建都会重新初始化,不过只需要初始版本的main.js修改就行了
构建 => 修改main.js => 点击编译 => 发安装包给手机。
后续版本不需要再更改main.js了
5.安装app
找到生成的app装到手机上。
这是发布第一版本的操作 (后续版本的操作会简单很多)
3.热更新操作(后续版本)
(1) 更改项目内容
后续版本要更改的内容。
(2) 再次构建
更改完内容后保存,再次构建,只构建!
(3) 再次启动热更新工具,将版本号+1,
版本号只能加 不能减!
(4) 将新生成的服务端配置文件,删掉,换成新版本的配置文件
(5) 开启服务端,手机和服务端确保在同一个局域网下,重启app即更新成功
最后提醒大家测试 的时候记得看看IP地址是不是对的,
服务端和客户端是不是在用一个局域网下面
配置文件有没有覆盖原有文件。
4.代码
1.创建更新场景
用来跳转,更新完成之后就执行什么代码。(这里是执行完成之后把按钮显示出来,点击跳入main场景)
在这里写了一个进度条来查看进度。里面部分配置文件的 由来后面有说
)
2.客户端代码
hot.js
// jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST = 0;
// jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST = 1;
// jsb.EventAssetsManager.ERROR_PARSE_MANIFEST = 2;
// jsb.EventAssetsManager.NEW_VERSION_FOUND = 3;
// jsb.EventAssetsManager.ALREADY_UP_TO_DATE = 4;
// jsb.EventAssetsManager.UPDATE_PROGRESSION = 5;
// jsb.EventAssetsManager.ASSET_UPDATED = 6;
// jsb.EventAssetsManager.ERROR_UPDATING = 7;
// jsb.EventAssetsManager.UPDATE_FINISHED = 8;
// jsb.EventAssetsManager.UPDATE_FAILED = 9;
// jsb.EventAssetsManager.ERROR_DECOMPRESS = 10;
cc.Class({
extends: cc.Component,
properties: {
manifestUrl: {
type: cc.Asset,
default: null
},
_updating: false,
_canRetry: false,
_storagePath: '',
},
checkCb(event) {
let isUpdate = false;
console.log("checkCb", event.getEventCode());
switch (event.getEventCode())
{
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
cc.systemEvent.emit("hot_is_new"); // 已经是最新版本 调用对应代码段(跳转主场景)
break;
case jsb.EventAssetsManager.NEW_VERSION_FOUND: // 发现新版本
cc.systemEvent.emit("hot_update_begin"); // 开始更新,将显示(一个给用户看的label)改为正在更新。
isUpdate = true;
break;
case jsb.EventAssetsManager.UPDATE_PROGRESSION:
cc.systemEvent.emit("hot_check_updating");
default:
// cc.systemEvent.emit("hot_check_failed",event.getEventCode()); // 检测失败
break;
}
this._am.setEventCallback(null);
this._checkListener = null;
this._updating = false;
if (isUpdate){
console.log("updateTimes", this._updateTimes);
if (this._updateTimes > 3){
cc.systemEvent.emit("hot_update_out_times"); // 更新超过次数
}
else{
this._updateTimes++;
this.hotUpdate();
}
}
},
updateCb(event) { // event.getEventCode() 数字0-10 对应10个不同的状态,执行对应的代码
var needRestart = false;
var failed = false;
console.log("updateCb", event.getEventCode());
switch (event.getEventCode())
{
case jsb.EventAssetsManager.UPDATE_PROGRESSION:
let data = {
curFile:event.getDownloadedFiles(),
totalFile:event.getTotalFiles(),
curByte:event.getDownloadedBytes(),
totalByte:event.getTotalBytes(),
};
cc.systemEvent.emit("hot_update_progress", data); // 更新进度
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
cc.systemEvent.emit("hot_is_new"); // 已经是最新版本
failed = true;
break;
case jsb.EventAssetsManager.UPDATE_FINISHED: // 更新完成,重启游戏
failed = true;
needRestart = true;
break;
default:
this._updating = false;
break;
}
if (failed) {
this._am.setEventCallback(null);
this._updateListener = null;
this._updating = false;
}
if (needRestart) {
this.isUpdate = false;
this._am.setEventCallback(null);
this._updateListener = null;
// Prepend the manifest's search path
var searchPaths = jsb.fileUtils.getSearchPaths();
var newPaths = this._am.getLocalManifest().getSearchPaths();
Array.prototype.unshift.apply(searchPaths, newPaths);
cc.sys.localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths));
jsb.fileUtils.setSearchPaths(searchPaths);
cc.audioEngine.stopAll();
cc.game.restart();
}
},
loadCustomManifest() {
if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
var manifest = new jsb.Manifest(customManifestStr, this._storagePath);
this._am.loadLocalManifest(manifest, this._storagePath);
console.log('Using custom manifest');
}
},
retry() {
if (!this._updating && this._canRetry) {
this._canRetry = false;
console.log('Retry failed Assets...');
this._am.downloadFailedAssets();
}
},
checkUpdate() {
if (this._updating) {
console.log('Checking or updating ...');
return;
}
if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
// Resolve md5 url
var url = this.manifestUrl.nativeUrl;
if (cc.loader.md5Pipe) {
url = cc.loader.md5Pipe.transformURL(url);
}
this._am.loadLocalManifest(url);
}
if (!this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()) {
console.log('Failed to load local manifest ...');
cc.systemEvent.emit("hot_no_manifest");
return;
}
this._am.setEventCallback((event)=>{ this.checkCb(event); });
this._am.checkUpdate();
this._updating = true;
},
hotUpdate() {
if (this._am && !this._updating) {
this._am.setEventCallback((event)=>{ this.updateCb(event); });
if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
// Resolve md5 url
var url = this.manifestUrl.nativeUrl;
if (cc.loader.md5Pipe) {
url = cc.loader.md5Pipe.transformURL(url);
}
this._am.loadLocalManifest(url);
}
this._failCount = 0;
this._am.update();
this._updating = true;
}
},
// use this for initialization
onLoad() {
// Hot update is only available in Native build
if (!cc.sys.isNative) {
return;
}
this._updateTimes = 0;
this._storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'blackjack-remote-asset');
// Setup your own version compare handler, versionA and B is versions in string
// if the return value greater than 0, versionA is greater than B,
// if the return value equals 0, versionA equals to B,
// if the return value smaller than 0, versionA is smaller than B.
this.versionCompareHandle = (versionA, versionB)=>{
console.log("JS Custom Version Compare: version A is " + versionA + ', version B is ' + versionB);
var vA = versionA.split('.');
var vB = versionB.split('.');
for (var i = 0; i < vA.length; ++i) {
var a = parseInt(vA[i]);
var b = parseInt(vB[i] || 0);
if (a === b) {
continue;
}
else {
setTimeout(()=>{ // 版本不同
this.hotUpdate();
},500);
return a - b;
}
}
setTimeout(()=>{ // 版本相同
cc.systemEvent.emit("hot_is_new");
},100);
if (vB.length > vA.length) {
return -1;
}
else {
return 0;
}
};
// Init with empty manifest url for testing custom manifest
this._am = new jsb.AssetsManager('', this._storagePath, this.versionCompareHandle);
// Setup the verification callback, but we don't have md5 check function yet, so only print some message
// Return true if the verification passed, otherwise return false
this._am.setVerifyCallback((path, asset)=>{
// When asset is compressed, we don't need to check its md5, because zip file have been deleted.
var compressed = asset.compressed;
// Retrieve the correct md5 value.
var expectedMD5 = asset.md5;
// asset.path is relative path and path is absolute.
var relativePath = asset.path;
// The size of asset file, but this value could be absent.
var size = asset.size;
if (compressed) {
return true;
}
else {
return true;
}
});
if (cc.sys.os === cc.sys.OS_ANDROID) {
// Some Android device may slow down the download process when concurrent tasks is too much.
// The value may not be accurate, please do more test and find what's most suitable for your game.
this._am.setMaxConcurrentTask(1);
}
cc.systemEvent.on("hot_auto_update", ()=>{
this.checkUpdate();
});
},
onDestroy() {
console.log('destroy hot');
if (this._updateListener) {
this._am.setEventCallback(null);
this._updateListener = null;
}
cc.systemEvent.off("hot_auto_update");
}
});
panelUpdate.js
cc.Class({
extends: cc.Component,
properties: {
},
onClick(){
cc.systemEvent.emit("hot_auto_update");
},
// LIFE-CYCLE CALLBACKS:
// onLoad () {},
start () {
this.info = this.node.getChildByName("info").getComponent(cc.Label);
this.filePro = this.node.getChildByName("filePro").getComponent(cc.ProgressBar);
this.fileProLabel = this.filePro.node.getChildByName("pro").getComponent(cc.Label);
this.downPro = this.node.getChildByName("downPro").getComponent(cc.ProgressBar);
this.downProLabel = this.downPro.node.getChildByName("pro").getComponent(cc.Label);
this.btnStart = this.node.getChildByName("btnStart");
this.btnStart.active = false;
this.btnStart.on(cc.Node.EventType.TOUCH_START,()=>{ //更新完成后对应跳转的场景
cc.director.loadScene("main");
})
cc.systemEvent.on("hot_is_new", ()=>{
console.log("切换场景");
this.btnStart.active = true;
});
cc.systemEvent.on("hot_update_begin", ()=>{
console.log("开始更新");
this.info.string = "开始更新";
});
cc.systemEvent.on("hot_check_updating", ()=>{
console.log("正在更新");
this.info.string = "正在更新";
});
cc.systemEvent.on("hot_check_failed", (e)=>{
console.log("检测更新失败");
this.info.string = "检测更新失败";
});
cc.systemEvent.on("hot_update_out_times", ()=>{
console.log("更新次数达到最大");
this.info.string = "更新次数达到最大";
});
cc.systemEvent.on("hot_update_progress", (data)=>{
console.log("更新进度");
let { curFile, totalFile, curByte, totalByte } = data;
this.filePro.progress = curFile / totalFile;
this.downPro.progress = curByte / totalByte;
this.fileProLabel.string = `${curFile}/${totalFile}`;
this.downProLabel.string = `${curByte}/${totalByte}`;
});
cc.systemEvent.on("hot_no_manifest", ()=>{
console.log("没有Manifest文件");
this.info.string = "没有Manifest文件";
});
setTimeout(()=>{
cc.systemEvent.emit("hot_auto_update");
}, 100);
},
// update (dt) {},
});
3.服务端代码
图中红框里的内容是之后生成的压缩包里的内容。
app.js
let express = require("express");
let expressWS = require("express-ws");
let path = require("path");
let app = express();
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
next();
});
// expressWS(app);
app.use(express.static("static"));
app.listen(7714);
4.更新原理
在第一个版本中,客户端和服务端都需要配置生成的热更新文件。(具体操作在后面)
客户端
服务端
服务端和客户端的 project 和 version 是一样的。
在Canvas上绑定两个东西,一个是hot热更新脚本。
另外一个一开始没有,在后面 构建完=>热更新工具生成的配置文件中才有
场景布置完毕后,就点击构建
具体操作:后续版本对应之前版本,将服务端开启
手机上打开软件,连接在同一个wifi下。之前配置ip地址的wifi
客户端会用自己的配置文件和服务端的配置文件进行比较,若版本号大于自己,则更新。
新版本就生成了。