Cocos Creator大厅+子游戏模式
轉載自:https://www.jianshu.com/p/fe54ca980384
一、前言
根據上一篇(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.jsrequire(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 assetif (typeof type === 'number') {entry[1] = assetTypes[type];}// retrieve uuidrealEntries[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.jsvar 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來運行,如果共用大廳的資源,以后想抽取出來,又是一項艱巨的任務。但是這樣必然會造成一定的重復資源。具體取舍,等到項目后期再協調。
總結
以上是生活随笔為你收集整理的Cocos Creator大厅+子游戏模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql显示RMB符号乱码_mysql
- 下一篇: STM32 —— LIN