Cocos Creator热更新操作(超详细)避免踩坑

Cocos Creator热更新操作(超详细)

解决热更新进入后第一次有效,之后再次进入又恢复到之前版本

1.安装插件

可以安装到项目中

也可以安装到本地 (全局)

安装完成之后要重启一下cocos

2.详细操作(第一版本)

1.构建项目

*在这里只构建,先不进行后续操作

2.打开热更新工具生成配置文件

若在选栏里没有此项,则是没有安装热更新插件或者没有安装成功。

  1. 版本号随便填一个,只是之后的后续的版本号必须比之前大
  2. 资源服务器url是后续版本的客户端更新时,需要连接服务器找到更新的配置文件,若有新版本则会更新。

比如: http://192.168.0.0:7777/hotUpdate ip地址为自己的服务器ip地址,端口号也是自己设置的,后面是静态文件夹目录

  1. 点击生成,然后打开目录将生成的压缩包解压

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

客户端会用自己的配置文件和服务端的配置文件进行比较,若版本号大于自己,则更新。

新版本就生成了。

你可能感兴趣的:(Cocos Creator热更新操作(超详细)避免踩坑)