1. 程式人生 > >cocos creator 大廳+子游戲模式探討(creator版本1.8.2)

cocos creator 大廳+子游戲模式探討(creator版本1.8.2)

之前一直從事android開發,接觸cocos creator不久。近期公司安排我研究大廳子游戲模式的熱更新,前後花了近一週時間,在論壇上查資料,請教大神,期間得到了一些幫助,在此很感謝cocos中文論壇裡的前輩們。

談到cocos creator的子游戲的熱更新,相較於其他,諸如cocos-js,cocos2d-x等開發模式是有本質區別的。究其原因,是由於creator專案中無論是指令碼或是資源,對整體專案而言,都是資料檔案。由creator生成的專案是純粹的資料驅動而非程式碼驅動,一切皆為資料,包括場景、指令碼、圖片、音訊、動畫等。而資料驅動需要有一個入口,即是creator編譯器為我們生成的

main.js檔案,當然除了main.js這個入口檔案,還有描述整個資源的配置檔案settings.js,而我們自己編寫的指令碼,在編譯後統一存放在了檔案project.js中。程式碼驅動模式下的子游戲熱更只需要更新資源以及專案的程式碼檔案,很好理解。而在資料驅動模式下,開發者開發的專案是純資料,要想執行這份資料,必須要入口檔案(main.js)以及配置檔案(setting.js),這就是兩種模式在子游戲熱更新領域的本質區別。

基於上述特性,在官方熱更新的基礎上(具體詳見官方文件),我們開闢了一條子游戲獨有的更新思路,那就是將編譯生成的main.js檔案一併移入src目錄,讓其成為資料的一部分,在子游戲更新的時候會下載完整的

ressrc目錄,其中就囊括了我們執行所需的main.js檔案,在執行子游戲的時候,我們只需要require main.js這個檔案即可。

對於構建所生成的main.js檔案,顯然並非是我們想要的,因為它會載入預設的配置,對於整個原生應用而言,預設配置即為大廳的配置路徑,所以我們必須修改main.js檔案,使其找到子游戲自身的整個遊戲環境配置檔案,以下是子游戲main.js檔案的精簡程式碼,基本上註釋已經很完整,寫這篇文章的目的不是為了寫實現流程,因為大廳子游戲模式的實現流程論壇中已有較為完整的demo,這篇文章主要是分析子游戲的兩個進出檔案,另外是想說明在真實專案中運用這種方式會遇到哪些問題,以及我們怎麼處理這些問題。

(function () {

 //以下兩句主要作用是釋放資源,垃圾回收,個人感覺作用不大,有或者沒有可

//能對記憶體有一點影響,釋放不會很乾淨

    cc.loader.releaseAll();

    cc.sys.garbageCollect();

   //以下這句話官方解釋是fix bug ,不過實際上似乎沒什麼用,在這加著也無妨

    cc.director.startAnimation();

    'use strict';

    var settings = null;

    //以下是定義子游戲的路徑,與大廳中熱更新的某個子游戲路徑對應

    cc.INGAME = (jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + “ALLGame/ subgame/4/“;

   //以下的邏輯判斷做了下配置載入快取,第二次進入就不重複載入setting.js project.js很有必要

    if (!cc.subgame4) {

        console.log("首次載入路徑名稱-------" + cc.INGAME);

        //這裡的require,論壇中沒有說清楚,在1.5.2版本中creator可以通過var settings =require(‘…..')這種方式給settings賦值,但是後面引擎組對require                   //做了修改,後續版本中當require(‘…’)後,去取變數要看require對應的檔案中的程式碼而定.

        require(cc.INGAME + 'src/settings.js');

        cc.subgame4 = settings = window._CCSettings;

        console.log("首次載入成功-------" + JSON.stringify(cc.subgame4));

        require(cc.INGAME + 'src/project.js');

    } else {

        console.log("非首次載入路徑名稱-------" + cc.INGAME);

        console.log("非首次載入成功-------" + JSON.stringify(cc.subgame4));

        settings = cc.subgame4;

    }

   //_ccsettings置位undefinedmain.js都是這麼寫的

    window._CCSettings = undefined;

    //以下程式碼就是處理setting.js中的資源了,debug模式與非debug模式是不一樣的,下面的程式碼都是在非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]];

            }

        }

    }

//遊戲的啟動函式

    var onStart = function () {

        cc.view.resizeWithBrowserSize(true);

        // UC browser on many android devices have performance issue with retina display

        if (cc.sys.os !== cc.sys.OS_ANDROID || cc.sys.browserType !== cc.sys.BROWSER_TYPE_UC) {

            cc.view.enableRetina(true);

        }

        if (cc.sys.isMobile) {

            if (settings.orientation === 'landscape') {

                cc.view.setOrientation(cc.macro.ORIENTATION_LANDSCAPE);

            }

            else if (settings.orientation === 'portrait') {

                cc.view.setOrientation(cc.macro.ORIENTATION_PORTRAIT);

            }

            // qq, wechat, baidu

            cc.view.enableAutoFullScreen(

                cc.sys.browserType !== cc.sys.BROWSER_TYPE_BAIDU &&

                cc.sys.browserType !== cc.sys.BROWSER_TYPE_WECHAT &&

                cc.sys.browserType !== cc.sys.BROWSER_TYPE_MOBILE_QQ

            );

        }

        // Limit downloading max concurrent task to 2,

        // more tasks simultaneously may cause performance draw back on some android system / brwosers.

        // You can adjust the number based on your own test result, you have to set it before any loading process to take effect.

        if (cc.sys.isBrowser && cc.sys.os === cc.sys.OS_ANDROID) {

            cc.macro.DOWNLOAD_MAX_CONCURRENT = 2;

        }

        //初始化資源方法,下面的路徑就是子游戲目錄下的,所以要加上cc.INGAME

        cc.AssetLibrary.init({

            libraryPath: cc.INGAME + 'res/import',

            rawAssetsBase: cc.INGAME + 'res/raw-',

            rawAssets: settings.rawAssets,

            packedAssets: settings.packedAssets,

            md5AssetsMap: settings.md5AssetsMap

        });

        var launchScene = settings.launchScene;

        if (cc.runtime) {

            cc.director.setRuntimeLaunchScene(launchScene);

        }

        // load scene

        cc.director.loadScene(launchScene, null,

            function () {

                cc.loader.onProgress = null;

                console.log('Success to load scene: ' + launchScene);

            }

        );

    };

    // jsList,指令碼的路徑也要加cc.INGAME   

    var jsList = settings.jsList;

    var bundledScript = settings.debug ? cc.INGAME + 'src/project.dev.js' : cc.INGAME + 'src/project.js';

    if (jsList) {

        jsList = jsList.map(function (x) {

            return cc.INGAME + 'src/' + x;

        });

        jsList.push(bundledScript);

    }

    else {

        jsList = [bundledScript];

    }

    var option = {

        id: 'GameCanvas',

        scenes: settings.scenes,

        debugMode: settings.debug ? cc.DebugMode.INFO : cc.DebugMode.ERROR,

        showFPS: settings.debug,

        frameRate: 60,

        jsList: jsList,

        groupList: settings.groupList,

        collisionMatrix: settings.collisionMatrix,

        renderMode: 0

    };

   //運行遊戲

    cc.game.run(option, onStart);

    cc.sys.garbageCollect();

})();

再來看dating.js檔案

(function () {

    cc.loader.releaseAll();

    cc.sys.garbageCollect();

    'use strict’;

   //這裡只針對android的路徑做了修改,因為我發現論壇中如果定義cc.INGAME=“”,會找不到主大廳資源,ios沒有測

    cc.INGAME = "assets/";

    require(cc.INGAME+'src/settings.js');

    var settings = window._CCSettings;

    window._CCSettings = undefined;

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

            }

        }

    }

    var onStart = function () {

        cc.view.resizeWithBrowserSize(true);

        // UC browser on many android devices have performance issue with retina display

        if (cc.sys.os !== cc.sys.OS_ANDROID || cc.sys.browserType !== cc.sys.BROWSER_TYPE_UC) {

            cc.view.enableRetina(true);

        }

        if (cc.sys.isMobile) {

            if (settings.orientation === 'landscape') {

                cc.view.setOrientation(cc.macro.ORIENTATION_LANDSCAPE);

            }

            else if (settings.orientation === 'portrait') {

                cc.view.setOrientation(cc.macro.ORIENTATION_PORTRAIT);

            }

            // qq, wechat, baidu

            cc.view.enableAutoFullScreen(

                cc.sys.browserType !== cc.sys.BROWSER_TYPE_BAIDU &&

                cc.sys.browserType !== cc.sys.BROWSER_TYPE_WECHAT &&

                cc.sys.browserType !== cc.sys.BROWSER_TYPE_MOBILE_QQ

            );

        }

        // Limit downloading max concurrent task to 2,

        // more tasks simultaneously may cause performance draw back on some android system / brwosers.

        // You can adjust the number based on your own test result, you have to set it before any loading process to take effect.

        if (cc.sys.isBrowser && cc.sys.os === cc.sys.OS_ANDROID) {

            cc.macro.DOWNLOAD_MAX_CONCURRENT = 2;

        }

        // init assets

        cc.AssetLibrary.init({

            libraryPath: cc.INGAME + 'res/import',

            rawAssetsBase: cc.INGAME + 'res/raw-',

            rawAssets: settings.rawAssets,

            packedAssets: settings.packedAssets,

            md5AssetsMap: settings.md5AssetsMap

        });

        //我們遊戲大廳就一個場景,所以無論是直接定義還是使用settings檔案中的launchScene都是可以的,如果大廳包含兩個場景,那需要這種方式了,不過做這種熱更方式,不建議大廳有多場景,可能會遇到意想不到的結果.

        var launchScene = "db://assets/scene/MainScene.fire";

        // load scene

        if (cc.runtime) {

            cc.director.setRuntimeLaunchScene(launchScene);

        }

        cc.director.loadScene(launchScene, null,

            function () {

                cc.loader.onProgress = null;

                console.log('Success to load scene: ' + launchScene);

            }

        );

    };

    // jsList

    var jsList = settings.jsList;

    var bundledScript = settings.debug ? cc.INGAME + 'src/project.dev.js' : cc.INGAME + 'src/project.js';

    if (jsList) {

        jsList = jsList.map(function (x) {

            return cc.INGAME + 'src/' + x;

        });

        jsList.push(bundledScript);

    }

    else {

        jsList = [bundledScript];

    }

    var option = {

        id: 'GameCanvas',

        scenes: settings.scenes,

        debugMode: settings.debug ? cc.DebugMode.INFO : cc.DebugMode.ERROR,

        showFPS: settings.debug,

        frameRate: 60,

        jsList: jsList,

        groupList: settings.groupList,

        collisionMatrix: settings.collisionMatrix,

        renderMode: 0

    };

    cc.game.run(option, onStart);

    cc.sys.garbageCollect();

})();

以上是從大廳進入子游戲的main.js與子游戲回大廳的dating.js的精簡版,當然還可以更精簡。

我們利用程式碼執行兩個helloword子游戲時是不會存在任何問題的,一旦把實際專案整合進來時問題就逐一顯現了,下面來一一列舉。

問題一:從大廳進入一個子遊戲,退回大廳,再進另一個子遊戲時,畫面整個錯亂

引發原因:兩個子游戲中,有部分資源uuid重複,很可能是在開發一個遊戲的時候,匯出了該遊戲的部分資源或場景進入了第二個遊戲。

解決方法下載uuid.php這個指令碼,放入assets目錄下執行 php uuid.php 將專案中所有的uuid替換,前提是事先要安裝php環境,注意指令碼中$QIANZIU=b11”;每一個子遊戲都需要改一下這個值,因為這個指令碼會把所有uuid的前三位替換為在此定義的三位。

問題二:修改了uuid覺得萬事大吉的時候,那就想的太簡單了,你忽然發現進了一個遊戲,進另外一個黑了。

原因:場景、指令碼、節點名稱重複,程式沒有重新載入。

解決辦法:每個遊戲非公有的程式碼的指令碼名稱,元件名稱以及場景全部改名,建議以子游戲名稱打頭。

問題三:運行了一個遊戲後,再執行另一個遊戲,某些元件為null,聲音還在播放。

原因:返回遊戲時事件沒有反註冊,事件監聽器中存在元件例項。垃圾回收後,元件例項被回收,但事件監聽器中引用仍然存在,下一個遊戲發了同一條事件後,就會報錯,聲音資源沒有被釋放。

解決辦法:返回遊戲時,清空事件監聽器,釋放聲音資源。

問題四:全域性變數被覆蓋。

原因:兩個遊戲中存在相同名稱全域性變數,值不同,導致遊戲執行會有問題。

解決辦法:每個遊戲的全域性變數必須保證名稱不一致。

問題五:回大廳發現存在子游戲的畫面。

原因之一:定時任務沒有在返回時關閉。

解決辦法:查詢遊戲中的每一個定時任務,在返回時記得關閉任務。

以上問題都解決完之後似乎可以不衝突運行了,伴隨而來的是記憶體的壓力,不過無所謂,問題肯定還會有,不過已經少了。

對上述問題的的總結:在公司專案確定要使用這種方案來做子游戲更新時,勸大家評估一下專案命名是否規範,是否能容忍進出遊戲的緩慢,對專案命名的建議是:公共程式碼保證所有遊戲一樣時,無需對程式碼及變數改名,一旦公共程式碼載入記憶體,不會載入第二次,所以不用理會,同時在公共程式碼中不要儲存任何非公共的變數以及物件引用。對於非公共部分,要保證資源名稱,js指令碼名稱以及場景、節點名稱儘量使用子游戲名稱打頭來命名,然而即使都沒問題了,進入遊戲與返回大廳的速度依然是硬傷。

子游戲+大廳共享資源臨時實現方案,此方案通過修改jsb_polyfill.js 程式碼,也就是定義幾個全域性變數,這種方式在creator 1.8.2中行不通,進大廳就黑屏,creator1.7.2可行。這種方式確實能訪問到主大廳的資源,甚至能通過載入場景的方式進出子游戲,而且速度非常快,與載入一般場景的速度沒什麼區別,但是這其中的問題也非常多,就我遇到的,重啟大廳後進子游戲,子游戲都打不開了,還有一些資源的丟失問題,要使用這種方式需要對引擎有足夠多的瞭解,小弟不才,對cocos引擎研究不深。

無論是require方式還是通過修改jsb_polyfill.js方式,都有弊病,這種非官方的載入方式能否商用,值得商榷,至少我覺得真想做creator大廳子游戲模式還是等待官方更新比較靠譜,以上是我一個禮拜的cocos子游戲更新的心得,藉此分享一下。