zoukankan      html  css  js  c++  java
  • Cocos Creator大厅+子游戏模式

    一、前言

    根据上一篇(Cocos Creator热更新),可以看出以下几点:

    • build-default目录下的main.js,为cocos creator项目的入口;
    • 热更新一文中,放置在服务器上的,仅有资源,脚本,配置等,没有入口程序,因此本文中,我们需要创造一个入口程序。
    还是解释一下什么叫大厅+子游戏模式:

      1. 将大厅单独作为一个完整的项目,不同的子游戏,则为不同的项目
      2. 然后要实现不同项目之间的互调,即大厅调子游戏,或者子游戏调大厅
      3. 资源共享,共用的资源放在大厅项目中,并且子游戏中可以调用

    这样做的好处:

      1. 减小上架包的体积
      2. 提高热更新的效率(打开指定子游戏,才会更新子游戏)
      3. 降低项目的耦合性(如果不共享资源,子游戏完全可以随时抽取出来作为一个单独的包使用)

    二、修改子游戏

    1. 添加version_generato.js
    2. 构建项目
    3. 在原生src下,添加 main.js 入口文件
      3.1 每次构建完项目,拷贝main.js到原生目录的src中

      main.js的内容如下:

    (function () {
        'use strict';
    
        if (window.jsb) {
            /// 1.初始化资源Lib路径Root.
            var subgameSearchPath = (jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/')+'ALLGame/subgame/';
    
            /// 2.subgame资源未映射,则初始化资源映射表,否则略过映射.
            if(!cc.HallAndSubGameGlobal.subgameGlobal){
                cc.HallAndSubGameGlobal.subgameGlobal = {};
    
                /// 加载settings.js
                require(subgameSearchPath + 'src/settings.js');
                var settings = window._CCSettings;
                window._CCSettings = undefined;
    
                if ( !settings.debug ) {
                    var uuids = settings.uuids;
    
                    var rawAssets = settings.rawAssets;
                    var assetTypes = settings.assetTypes;
                    var realRawAssets = settings.rawAssets = {};
                    for (var mount in rawAssets) {
                        var entries = rawAssets[mount];
                        var realEntries = realRawAssets[mount] = {};
                        for (var id in entries) {
                            var entry = entries[id];
                            var type = entry[1];
                            // retrieve minified raw asset
                            if (typeof type === 'number') {
                                entry[1] = assetTypes[type];
                            }
                            // retrieve uuid
                            realEntries[uuids[id] || id] = entry;
                        }
                    }
    
                    var scenes = settings.scenes;
                    for (var i = 0; i < scenes.length; ++i) {
                        var scene = scenes[i];
    
                        if (typeof scene.uuid === 'number') {
                            scene.uuid = uuids[scene.uuid];
                        }
                    }
    
                    var packedAssets = settings.packedAssets;
                    for (var packId in packedAssets) {
                        var packedIds = packedAssets[packId];
                        for (var j = 0; j < packedIds.length; ++j) {
                            if (typeof packedIds[j] === 'number') {
                                packedIds[j] = uuids[packedIds[j]];
                            }
                        }
                    }
                }
    
                /// 加载project.js
                var projectDir = 'src/project.js';
                if ( settings.debug ) {
                    projectDir = 'src/project.dev.js';
                }
                require(subgameSearchPath + projectDir);
    
                /// 如果当前搜索路径没有subgame,则添加进去搜索路径。
                var currentSearchPaths = jsb.fileUtils.getSearchPaths();
                if(currentSearchPaths && currentSearchPaths.indexOf(subgameSearchPath) === -1){
                    jsb.fileUtils.addSearchPath(subgameSearchPath, true);
                    console.log('subgame main.js 之前未添加,添加下subgameSearchPath' + currentSearchPaths);
                }
    
                cc.AssetLibrary.init({
                    libraryPath: 'res/import',
                    rawAssetsBase: 'res/raw-',
                    rawAssets: settings.rawAssets,
                    packedAssets: settings.packedAssets,
                    md5AssetsMap: settings.md5AssetsMap
                });
    
                cc.HallAndSubGameGlobal.subgameGlobal.launchScene = settings.launchScene;
    
                /// 将subgame的场景添加到cc.game中,使得cc.director.loadScene可以从cc.game._sceneInfos查找到相关场景
                for(var i = 0; i < settings.scenes.length; ++i){
                    cc.game._sceneInfos.push(settings.scenes[i]);
                }
            }
    
            /// 3.加载初始场景
            var launchScene = cc.HallAndSubGameGlobal.subgameGlobal.launchScene;
            cc.director.loadScene(launchScene, null,
                function () {
                    console.log('subgame main.js 成功加载初始场景' + launchScene);
                }
            );
        }
    })();
    

    ps: 不用管src外部的main.js文件

      3.2 或者 添加build-templates目录,自动在每次构建项目后生成main.js文件

    这里的main.js内容和上面的内容一致

    4. 执行version_generator.js文件

      生成version.manifest 和 project.mainfest。这个在上一篇中已经讲过,就不细说了。

    三、拷贝res,src,version.manifest 和 project.mainfest到服务器目录下

      很明显,现在我们只是把子游戏生成了资源包,但是没有做任何热更新的操作。
    接下来,就需要在大厅项目中,添加下载,更新的逻辑了。

    四、在大厅项目中,添加相应逻辑

      负责下载,检测更新,更新子游戏的工具库文件内容如下:

    const SubgameManager = {
        _storagePath: [],
    
        _getfiles: function(name, type, downloadCallback, finishCallback) {
            this._storagePath[name] = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'ALLGame/' + name);
            this._downloadCallback = downloadCallback;
            this._finishCallback = finishCallback;
            this._fileName = name;
    
            /// 替换该地址
            var UIRLFILE = "http://192.168.200.117:8000/" + name + "/remote-assets";
            var filees = this._storagePath[name] + '/project.manifest';
    
            var customManifestStr = JSON.stringify({
                'packageUrl': UIRLFILE,
                'remoteManifestUrl': UIRLFILE + '/project.manifest',
                'remoteVersionUrl': UIRLFILE + '/version.manifest',
                'version': '0.0.1',
                'assets': {},
                'searchPaths': []
            });
    
            var versionCompareHandle = function(versionA, 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;
                }
            };
    
            this._am = new jsb.AssetsManager('', this._storagePath[name], versionCompareHandle);
    
            if (!cc.sys.ENABLE_GC_FOR_NATIVE_OBJECTS) {
                this._am.retain();
            }
    
            this._am.setVerifyCallback(function(path, asset) {
                var compressed = asset.compressed;
                if (compressed) {
                    return true;
                } else {
                    return true;
                }
            });
    
    
            if (cc.sys.os === cc.sys.OS_ANDROID) {
                this._am.setMaxConcurrentTask(2);
            }
    
            if (type === 1) {
                this._updateListener = new jsb.EventListenerAssetsManager(this._am, this._updateCb.bind(this));
            } else if (type == 2) {
                this._updateListener = new jsb.EventListenerAssetsManager(this._am, this._checkCb.bind(this));
            } else {
                this._updateListener = new jsb.EventListenerAssetsManager(this._am, this._needUpdate.bind(this));
            }
    
            cc.eventManager.addListener(this._updateListener, 1);
    
            if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
                var manifest = new jsb.Manifest(customManifestStr, this._storagePath[name]);
                this._am.loadLocalManifest(manifest, this._storagePath[name]);
            }
    
            if (type === 1) {
                this._am.update();
                this._failCount = 0;
            } else {
                this._am.checkUpdate();
            }
            this._updating = true;
            cc.log('更新文件:' + filees);
        },
    
        // type = 1
        _updateCb: function(event) {
            var failed = false;
            let self = this;
            switch (event.getEventCode()) {
                case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                    /*0 本地没有配置文件*/
                    cc.log('updateCb本地没有配置文件');
                    failed = true;
                    break;
    
                case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
                    /*1下载配置文件错误*/
                    cc.log('updateCb下载配置文件错误');
                    failed = true;
                    break;
    
                case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                    /*2 解析文件错误*/
                    cc.log('updateCb解析文件错误');
                    failed = true;
                    break;
    
                case jsb.EventAssetsManager.NEW_VERSION_FOUND:
                    /*3发现新的更新*/
                    cc.log('updateCb发现新的更新');
                    break;
    
                case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                    /*4 已经是最新的*/
                    cc.log('updateCb已经是最新的');
                    failed = true;
                    break;
    
                case jsb.EventAssetsManager.UPDATE_PROGRESSION:
                    /*5 最新进展 */
                    self._downloadCallback && self._downloadCallback(event.getPercentByFile());
                    break;
    
    
                case jsb.EventAssetsManager.ASSET_UPDATED:
                    /*6需要更新*/
                    break;
    
                case jsb.EventAssetsManager.ERROR_UPDATING:
                    /*7更新错误*/
                    cc.log('updateCb更新错误');
                    break;
    
                case jsb.EventAssetsManager.UPDATE_FINISHED:
                    /*8更新完成*/
                    self._finishCallback && self._finishCallback(true);
                    break;
    
                case jsb.EventAssetsManager.UPDATE_FAILED:
                    /*9更新失败*/
                    self._failCount++;
                    if (self._failCount <= 3) {
                        self._am.downloadFailedAssets();
                        cc.log(('updateCb更新失败' + this._failCount + ' 次'));
                    } else {
                        cc.log(('updateCb失败次数过多'));
                        self._failCount = 0;
                        failed = true;
                        self._updating = false;
                    }
                    break;
    
                case jsb.EventAssetsManager.ERROR_DECOMPRESS:
                    /*10解压失败*/
                    cc.log('updateCb解压失败');
                    break;
            }
    
            if (failed) {
                cc.eventManager.removeListener(self._updateListener);
                self._updateListener = null;
                self._updating = false;
                self._finishCallback && self._finishCallback(false);
            }
        },
    
        // type = 2
        _checkCb: function(event) {
            var failed = false;
            let self = this;
            switch (event.getEventCode()) {
                case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                    /*0 本地没有配置文件*/
                    cc.log('checkCb本地没有配置文件');
                    break;
    
                case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
                    /*1下载配置文件错误*/
                    cc.log('checkCb下载配置文件错误');
                    failed = true;
                    break;
    
                case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                    /*2 解析文件错误*/
                    cc.log('checkCb解析文件错误');
                    failed = true;
                    break;
    
                case jsb.EventAssetsManager.NEW_VERSION_FOUND:
                    /*3发现新的更新*/
                    self._getfiles(self._fileName, 1, self._downloadCallback, self._finishCallback);
                    break;
    
                case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                    /*4 已经是最新的*/
                    cc.log('checkCb已经是最新的');
                    self._finishCallback && self._finishCallback(true);
                    break;
    
                case jsb.EventAssetsManager.UPDATE_PROGRESSION:
                    /*5 最新进展 */
                    break;
    
                case jsb.EventAssetsManager.ASSET_UPDATED:
                    /*6需要更新*/
                    break;
    
                case jsb.EventAssetsManager.ERROR_UPDATING:
                    /*7更新错误*/
                    cc.log('checkCb更新错误');
                    failed = true;
                    break;
    
    
                case jsb.EventAssetsManager.UPDATE_FINISHED:
                    /*8更新完成*/
                    cc.log('checkCb更新完成');
                    break;
    
                case jsb.EventAssetsManager.UPDATE_FAILED:
                    /*9更新失败*/
                    cc.log('checkCb更新失败');
                    failed = true;
                    break;
    
                case jsb.EventAssetsManager.ERROR_DECOMPRESS:
                    /*10解压失败*/
                    cc.log('checkCb解压失败');
                    break;
    
            }
            this._updating = false;
            if (failed) {
                self._finishCallback && self._finishCallback(false);
            }
        },
    
        // type = 3
        _needUpdate: function(event) {
            let self = this;
            switch (event.getEventCode()) {
                case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                    cc.log('子游戏已经是最新的,不需要更新');
                    self._finishCallback && self._finishCallback(false);
                    break;
    
                case jsb.EventAssetsManager.NEW_VERSION_FOUND:
                    cc.log('子游戏需要更新');
                    self._finishCallback && self._finishCallback(true);
                    break;
    
                // 检查是否更新出错
                case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
                case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                case jsb.EventAssetsManager.ERROR_UPDATING:
                case jsb.EventAssetsManager.UPDATE_FAILED:
                    self._downloadCallback();
                    break;
            }
        },
    
        /**
         * 下载子游戏
         * @param {string} name - 游戏名
         * @param progress - 下载进度回调
         * @param finish - 完成回调
         * @note finish 返回true表示下载成功,false表示下载失败
         */
        downloadSubgame: function(name, progress, finish) {
            this._getfiles(name, 2, progress, finish);
        },
    
        /**
         * 进入子游戏
         * @param {string} name - 游戏名
         */
        enterSubgame: function(name) {
            if (!this._storagePath[name]) {
                this.downloadSubgame(name);
                return;
            }
    
            require(this._storagePath[name] + '/src/main.js');
        },
    
        /**
         * 判断子游戏是否已经下载
         * @param {string} name - 游戏名
         */
        isSubgameDownLoad: function (name) {
            let file = (jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'ALLGame/' + name + '/project.manifest';
            if (jsb.fileUtils.isFileExist(file)) {
                return true;
            } else {
                return false;
            }
        },
    
        /**
         * 判断子游戏是否需要更新
         * @param {string} name - 游戏名
         * @param isUpdateCallback - 是否需要更新回调
         * @param failCallback - 错误回调
         * @note isUpdateCallback 返回true表示需要更新,false表示不需要更新
         */
        needUpdateSubgame: function (name, isUpdateCallback, failCallback) {
            this._getfiles(name, 3, failCallback, isUpdateCallback);
        },
    };
    
    module.exports = SubgameManager;
    
    

      调用的过程如下:
        1. 判断子游戏是否已下载
        2. 已下载,判断是否需要更新
        3.1 下载游戏
        3.2 更新游戏
        4. 进入子游戏

    const SubgameManager = require('SubgameManager');
    
    cc.Class({
        extends: cc.Component,
    
        properties: {
            downloadBtn: {
                default: null,
                type: cc.Node
            },
            downloadLabel: {
                default: null,
                type: cc.Label
            }
        },
    
        onLoad: function () {
            const name = 'subgame';    
            //判断子游戏有没有下载
            if (SubgameManager.isSubgameDownLoad(name)) {
                //已下载,判断是否需要更新
                SubgameManager.needUpdateSubgame(name, (success) => {
                    if (success) {
                        this.downloadLabel.string = "子游戏需要更新";
                    } else {
                        this.downloadLabel.string = "子游戏不需要更新";
                    }
                }, () => {
                    cc.log('出错了');
                });
            } else {
                this.downloadLabel.string = "子游戏未下载";
            }
    
            this.downloadBtn.on('click', () => {
                //下载子游戏/更新子游戏
                SubgameManager.downloadSubgame(name, (progress) => {
                    if (isNaN(progress)) {
                        progress = 0;
                    }
                    this.downloadLabel.string = "资源下载中   " + parseInt(progress * 100) + "%";
                }, function(success) {
                    if (success) {
                        SubgameManager.enterSubgame('subgame');
                    } else {
                        cc.log('下载失败');
                    }
                });
            }, this);
        },
    });
    

    说到这呢,就得提一下,
    如果界面设计时,从大厅点击子游戏,中间有loading的界面的话,
    loading界面就应该放在大厅的工程中了。

    五、测试

      打开服务------>编译大厅目录------>安装运行
    注意:
        一定要生成原生apk,在真机(也可以是类似于夜神的模拟器啦)上运行测试。
    结果:
        1. 第一次,本地没有子游戏,提示“游戏未下载”,下载后,无需重启,可直接进入子游戏;
        2. 修改version_generator.js中的版本号,将步骤二,再走一遍,能检测到更新,同样无需重启;
        3. 在大厅中,使用cc.sys.localStorage存储的值,在子游戏中可以获取到;

    本人的一点小思考:

    在研究之前,想着一定要研究一下资源共享的问题;
    现在想来,既然要将子游戏独立出一个项目,自然也期望以后子游戏可以作为一个单独的apk来运行,如果共用大厅的资源,以后想抽取出来,又是一项艰巨的任务。但是这样必然会造成一定的重复资源。具体取舍,等到项目后期再协调。

     来源:https://www.jianshu.com/p/fe54ca980384

  • 相关阅读:
    实验四
    实验三
    实验二
    实验一
    6
    5
    4
    3
    shiyan2
    实验1
  • 原文地址:https://www.cnblogs.com/gao88/p/11632653.html
Copyright © 2011-2022 走看看