cocoscreator版本:2.3.2 带版本管理的版本

cocoscreator 热更的实现原理:存在2个版本,本地安装包是一个版本;另一个版本放在服务器上。

增加一个程序实现:1.比对两个版本。2.下载服务器上版本到客户端程序上,并实现覆盖。

cocoscreator热更实现:

一、在项目里新建一个层或者场景来显示的提示热更操作。我加的一个层和label来提示热更。

cordova 热更新 显示进度_游戏

二、使用cocoscreator自带的热更插件生成本地的project.manifest和version.manifest文件。或者使用cocoscreator热更github上提供的 version_generator.js 脚本,具体命令示例:

//官方给出的命令格式
>node version_generator.js -v 1.0.0 -u http://your-server-address/tutorial-hot-update/remote-assets/ -s native/package/ -d assets/

//我的命令
>node version_generator.js -v 1.0.0 -u http://x.x.x.x:8000/HotUpdate/ -s build/jsb-default/ -d assets

//由于我们version_generator文件中,都配置好了参数
//因此可以简单调用以下命令即可
>node version_generator.js

2.1使用热更插件生成文件的时候,注意:1.先构建一次项目。2.使用插件生成一个版本,将生成的2个manifest文件拷贝到assets目录里,资源服务器的地址需要按照实际情况填写。

cordova 热更新 显示进度_游戏_02

cordova 热更新 显示进度_cordova 热更新 显示进度_03

三、编写HotUpdate.js文件控制热更,然后加到之前创建的层上。第一贴图。我的文件如下:

// HotUpdate.js

cc.Class({

    extends: cc.Component,



    properties: {

        manifestUrl: cc.Asset,

        _updating: false,

        _canRetry: false,

        _storagePath: '',

        label: {

            default: null,

            type: cc.Label

        },

    },



    checkCb(event) {


        cc.log('Code: ' + event.getEventCode());

        switch (event.getEventCode()) {

            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:

                this.node.active =true;
                this.label.string = '本地文件丢失';

                break;

            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:

            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:

                this.node.active =true;
                this.label.string = '下载远程mainfest文件错误';

                break;

            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:

                this.node.active =false;
                this.label.string = '已经是最新版本';

                break;

            case jsb.EventAssetsManager.NEW_VERSION_FOUND:

                this.node.active =true;
                this.label.string = '有新版本发现';

            //    this.hotUpdate();
                break;

            default:

                return;

        }



        this._am.setEventCallback(null);

        this._checkListener = null;

        this._updating = false;

    },



    updateCb(event) {

        var needRestart = false;

        var failed = false;

        switch (event.getEventCode()) {

            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:

                this.label.string = '本地版本文件丢失,无法更新';

                failed = true;

                break;

            case jsb.EventAssetsManager.UPDATE_PROGRESSION:

                let percent = parseInt(event.getPercent() * 100);

                if (Number.isNaN(percent)) percent = 0;

                this.label.string = '更新进度:' + percent;

                break;

            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:

            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:

                this.label.string = '下载远程版本文件失败';

                failed = true;

                break;

            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:

                this.node.active = false;
                this.label.string = '当前为最新版本';
                failed = true;


                break;

            case jsb.EventAssetsManager.UPDATE_FINISHED:

                this.label.string = '更新完成. ' + event.getMessage();

                needRestart = true;

                this.node.active = false;

                break;

            case jsb.EventAssetsManager.UPDATE_FAILED:

                this.label.string = '更新失败. ' + event.getMessage();

                this._updating = false;

                this._canRetry = true;

                break;

            case jsb.EventAssetsManager.ERROR_UPDATING:

                this.label.string = '资源更新错误: ' + event.getAssetId() + ', ' + event.getMessage();

                break;

            case jsb.EventAssetsManager.ERROR_DECOMPRESS:

                this.label.string = event.getMessage();

                break;

            default:

                break;

        }



        if (failed) {

            this._am.setEventCallback(null);

            this._updateListener = null;

            this._updating = false;

        }



        if (needRestart) {

            this._am.setEventCallback(null);

            this._updateListener = 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(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.audioEngine.stopAll();

            cc.game.restart();

        }

    },



    retry() {

        if (!this._updating && this._canRetry) {

            this._canRetry = false;



            this.label.string = '重现获取失败资源...';

            this._am.downloadFailedAssets();

        }

    },



    checkUpdate() {

        if (this._updating) {

        //    this.label.string = '检查更新中...';

            return;

        }

        if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {

            this._am.loadLocalManifest(this.manifestUrl);

        }

        if (!this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()) {

            this.node.active = true;

            this.label.string = '本地manifest加载失败...';

            return;

        }

        this._am.setEventCallback(this.checkCb.bind(this));

        this._am.checkUpdate();

        this._updating = true;

    },



    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.manifestUrl);

            } 

            this._failCount = 0;

            this._am.update();

            this._updating = true;


        }

    },

    // use this for initialization

    onLoad() {

        if (!cc.sys.isNative) {

            return;

        }

        this.node.active = false;

        this._storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'remote-asset');

        cc.log('Storage path for remote asset : ' + this._storagePath);



        this.versionCompareHandle = (versionA, versionB) => {

        //    this.label.string = '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 {

                    return a - b;

                }

            }

            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);

        this._am.setVerifyCallback((path, asset) => {

            var compressed = asset.compressed;

            var expectedMD5 = asset.md5;

            var relativePath = asset.path;

            var size = asset.size;

            if (compressed) {

                this.label.string = 'Verification passed : ' + relativePath;

                return true;

            } else {

                this.label.string = 'Verification passed : ' + relativePath + ' (' + expectedMD5 + ')';

                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(2);

            // this.label.string = 'Max concurrent tasks count have been limited to 2';

        }

        //检查更新
         this.checkUpdate();

    },



    onDestroy() {

        if (this._updateListener) {

            this._am.setEventCallback(this.onStartIn.bind(this));

            this._updateListener = null;

        }

    },

    onStartIn(){

        cc.game.restart();
    },

});

 

四、构建打包出原始包。在 打包之前 需要修改你存放打包目录里的main.js文件,在其头部加上如下内容:

// 在 main.js 的开头添加如下代码
(function () {
    if (typeof window.jsb === 'object') {
        var hotUpdateSearchPaths = localStorage.getItem('HotUpdateSearchPaths');
        if (hotUpdateSearchPaths) {
            var path = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'filename/');
            var paths = [];
            paths.push(path);
            //var paths = JSON.parse(hotUpdateSearchPaths);
            jsb.fileUtils.setSearchPaths(paths);

            var fileList = [];
            var storagePath = paths[0] || '';
            var tempPath = storagePath + '_temp/';
            var baseOffset = tempPath.length;

            if (jsb.fileUtils.isDirectoryExist(tempPath) && !jsb.fileUtils.isFileExist(tempPath + 'project.manifest.temp')) {
                jsb.fileUtils.listFilesRecursively(tempPath, fileList);
                fileList.forEach(srcPath => {
                    var relativePath = srcPath.substr(baseOffset);
                    var dstPath = storagePath + relativePath;

                    if (srcPath[srcPath.length] == '/') {
                        cc.fileUtils.createDirectory(dstPath)
                    }
                    else {
                        if (cc.fileUtils.isFileExist(dstPath)) {
                            cc.fileUtils.removeFile(dstPath)
                        }
                        cc.fileUtils.renameFile(srcPath, dstPath);
                    }
                })
                cc.fileUtils.removeDirectory(tempPath);
            }
        }
    }
})();

 

如果不加,那么打出来的包将无法更新。在构建编译的时候指的模块和编译的目录对应。我的位置如下图:

cordova 热更新 显示进度_sed_04

cordova 热更新 显示进度_sed_05

五、成功打包之后,将包安装到手机或者模拟器上。然后,对项目进行修改、更新。

注意:在main.js文件里加上之前的函数!

然后,“构建” 一次项目,生产出新版本的资源文件,不用编译!然后使用热更插件 生产出来一个新版本的资源,提高一个版本号,生成出来之后,放到服务器。

六、打开程序 就看到提示更新了。