Cocos Creator热更新操作(超详细)避免踩坑
Cocos Creator热更新操作(超详细)
解决热更新进入后第一次有效,之后再次进入又恢复到之前版本
1.安装插件
可以安装到项目中
也可以安装到本地 (全局)
安装完成之后要重启一下cocos
2.代码
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
客户端会用自己的配置文件和服务端的配置文件进行比较,若版本号大于自己,则更新。
新版本就生成了。
3.详细操作(第一版本)
1.构建项目
*在这里只构建,先不进行后续操作
2.打开热更新工具生成配置文件
若在选栏里没有此项,则是没有安装热更新插件或者没有安装成功。
- 版本号随便填一个,只是之后的后续的版本号必须比之前大
- 资源服务器url是后续版本的客户端更新时,需要连接服务器找到更新的配置文件,若有新版本则会更新。
- http://192.168.0.0:7777/hotUpdate ip地址为自己的服务器ip地址,端口号也是自己设置的,在哪个文件夹下面
- 点击生成,然后打开目录将生成的压缩包解压
3.将解压的文件在客户端和服务端配置
客户端, 在resources放创一个目录hot,放project与version文件,(两个文件来自生成的压缩包中)
然后保存场景
服务端的,hotUpdate里面覆盖之前全部文件。
4.再次构建
(只构建)
5.编译并且打包app(这一步有一个bug需要改动)
这里有一个bug,若是不修改,在后续版本更新中,部分场景的更新可能只有打开的第一次有,之后再次打开部分场景又恢复到之前版本。(贼坑)
1.找到当前打包项目的 main.js文件
2.将 这几行看起来极其不协调的代码段剪切,贴到后面去
3.放到 window.boot(); 上面
4.记得保存main.js,然后开始编译
这个main.js文件每一次点构建都会重新初始化,不过只需要初始版本的main.js修改就行了
构建 => 修改main.js => 点击编译 => 发安装包给手机。
后续版本不需要再更改main.js了
5.安装app
找到生成的app装到手机上。
这是发布第一版本的操作 (后续版本的操作会简单很多)
4.热更新操作(后续版本)
(1) 更改项目内容
后续版本要更改的内容。
(2) 再次构建
更改完内容后保存,再次构建,只构建!
(3) 再次启动热更新工具,将版本号+1,
版本号只能加 不能减!
(4) 将新生成的服务端配置文件,删掉,换成新版本的配置文件
(5) 开启服务端,手机和服务端确保在同一个局域网下,重启app即更新成功
最后提醒大家测试 的时候记得看看IP地址是不是对的,
服务端和客户端是不是在用一个局域网下面
配置文件有没有覆盖原有文件。
- Sublime Text 3 插件用法以及详细快捷键操作 【2014-4-18再详细更新】
- Git 操作基础总结详细(Mas版+Win版)更新中,,,
- ubuntu16.04 更新源详细操作步骤
- ubuntu16.04 更新源详细操作步骤
- ubuntu16.04 更新源详细操作步骤
- vb 操作excel(持续更新)
- oracle 更新或删除某条记录的时候,无法操作,或被锁解决
- Python中list的详细操作描述(举例说明)
- linux 常用操作指令(随时更新)
- Java动态加载,避免停服更新
- 关于ppt转换成pdf的详细操作
- [oracle]数据库备份还原操作详细操作
- 南阳SEO:网站SEO更新文章6原则,做好避免无用功
- spring项目执行dao.update等更新操作失败
- Hibernate批处理操作优化 (批量插入、更新与删除)
- 线性表之双向链表基本操作(C语言实现,详细注释版)
- mongodb 实现添加新字段(使用更新操作)
- 精选教程!从iTunes备份中恢复QQ聊天记录的详细操作
- LINQ 表必须有主键才能进行更新操作
- 封装一个sqlite工具类,避免频繁的sql语言操作,简单易用