cocos creator大廳、子游戲實現方案
我們知道、在啟動cocos工程的時候,首先要載入main.js檔案,載入準備工作,執行一些配置操作。而由大廳跳轉到子游戲的話我們是否需要知道子游戲的配置檔案setting.js跟main.js。然後由大廳跳轉到子游戲的話其實就是獲取子游戲的main.js檔案就行了。
首先就是要用熱更新的方式下載子游戲。但是熱更新下載子游戲的話需要project.manifest檔案跟遠端檔案做對比,但是一開始的時候是子游戲是空空如也的,那就只能用遠端的方式獲取project.manifest檔案,然後進行熱更新了。
checkUpdate: function () {
let UIRLFILE = "http://192.168.92.59/update/remote-assets" ;
let remoteManifestUrl = this._storagePath + "/project.manifest";
this.manifestUrl = remoteManifestUrl;
let customManifestStr = JSON.stringify({
"packageUrl": UIRLFILE,
"remoteManifestUrl": UIRLFILE + "/project.manifest",
"remoteVersionUrl" : UIRLFILE + "/version.manifest",
"version": "1.0.0.0",
"assets": {},
"searchPaths": []
});
this._checkListener = new jsb.EventListenerAssetsManager(this._am, this.checkCb.bind(this));
cc.eventManager.addListener(this._checkListener, 1);
if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
if (jsb.fileUtils.isFileExist(remoteManifestUrl)) {
console.log('載入本地Manifest');
this._am.loadLocalManifest(this.manifestUrl);
} else {
console.log('載入網路Manifest');
let manifest = new jsb.Manifest(customManifestStr, this._storagePath);
this._am.loadLocalManifest(manifest, this._storagePath);
}
}
this.prmopt.string = '正在檢查版本資訊';
this._am.checkUpdate();
},
大廳的主要邏輯HotUpdate程式碼如下(主要負責下載子游戲,並跳轉到子游戲):
cc.Class({
extends: cc.Component,
properties: {
_am: null,
_updating: false,
_canRetry: true,
_storagePath: '',
_version: -1,
},
// use this for initialization
onLoad: function () {
this.initView();
this.initAssetsManage();
},
initView() {
this.percentLabel = cc.find('Canvas/percent').getComponent(cc.Label);
this.prmopt = cc.find('Canvas/prompt').getComponent(cc.Label);
},
initAssetsManage() {
if (cc.sys.isBrowser) {
return;
}
this._storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'ALLGame/subgame');
console.log('SubGame path ' + this._storagePath);
let versionCompareHandle = function (versionA, versionB) {
let vA = versionA.split('.');
let vB = versionB.split('.');
for (let i = 0; i < vA.length; ++i) {
let a = parseInt(vA[i]);
let b = parseInt(vB[i] || 0);
if (a !== b) {
return a - b;
}
}
return vB.length > vA.length ? -1 : 0;
};
this._am = new jsb.AssetsManager('', this._storagePath, versionCompareHandle);
if (!cc.sys.ENABLE_GC_FOR_NATIVE_OBJECTS) {
this._am.retain();
}
if (cc.sys.os === cc.sys.OS_ANDROID) {
this._am.setMaxConcurrentTask(2);
}
},
checkCb: function (event) {
let delayTime = 2000;
switch (event.getEventCode()) {
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
this.prmopt.string = "本地Manifest檔案未找到";
break;
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
this.prmopt.string = "載入Manifest檔案失敗";
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
this.prmopt.string = "已經是最新版本";
delayTime = 1000;
break;
case jsb.EventAssetsManager.NEW_VERSION_FOUND:
this.prmopt.string = '找到新版本';
cc.eventManager.removeListener(this._checkListener);
this._checkListener = null;
this._updating = false;
setTimeout(() => this.hotUpdate(), 1000);
return;
default:
return;
}
cc.eventManager.removeListener(this._checkListener);
this._checkListener = null;
this._updating = false;
},
updateCb: function (event) {
console.log('update eventCode = ' + event.getEventCode(), 'update msg = ' + event.getMessage());
switch (event.getEventCode()) {
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
this.prmopt.string = '未找到本地Manifest檔案';
break;
case jsb.EventAssetsManager.UPDATE_PROGRESSION:
let percent = event.getPercent().toFixed(2);
this.percentLabel.string = parseInt(percent * 100) + '%';
break;
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
this.prmopt.string = '下載Manifest檔案失敗';
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
this.prmopt.string = '已更新到最新版本';
break;
case jsb.EventAssetsManager.UPDATE_FINISHED:
this.prmopt.string = '更新成功,正在重啟遊戲...';
cc.eventManager.removeListener(this._updateListener);
this._updateListener = null;
this._updating = false;
break;
case jsb.EventAssetsManager.UPDATE_FAILED:
this._updating = false;
this.prmopt.string = '更新失敗';
break;
case jsb.EventAssetsManager.ERROR_UPDATING:
this.prmopt.string = '更新檔案錯誤 ' + event.getAssetId() + ', ' + event.getMessage();
break;
case jsb.EventAssetsManager.ERROR_DECOMPRESS:
this.prmopt.string = event.getMessage();
break;
default:
break;
}
},
retry: function () {
if (!this._updating && this._canRetry) {
this._canRetry = false;
this.prmopt.string = '更新失敗,再次更新';
this._am.downloadFailedAssets();
}
},
checkUpdate: function () {
let UIRLFILE = "http://192.168.92.59/update/remote-assets";
let remoteManifestUrl = this._storagePath + "/project.manifest";
this.manifestUrl = remoteManifestUrl;
let customManifestStr = JSON.stringify({
"packageUrl": UIRLFILE,
"remoteManifestUrl": UIRLFILE + "/project.manifest",
"remoteVersionUrl": UIRLFILE + "/version.manifest",
"version": "1.0.0.0",
"assets": {},
"searchPaths": []
});
this._checkListener = new jsb.EventListenerAssetsManager(this._am, this.checkCb.bind(this));
cc.eventManager.addListener(this._checkListener, 1);
if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
if (jsb.fileUtils.isFileExist(remoteManifestUrl)) {
console.log('載入本地Manifest');
this._am.loadLocalManifest(this.manifestUrl);
} else {
console.log('載入網路Manifest');
let manifest = new jsb.Manifest(customManifestStr, this._storagePath);
this._am.loadLocalManifest(manifest, this._storagePath);
}
}
this.prmopt.string = '正在檢查版本資訊';
this._am.checkUpdate();
this._updating = true;
},
hotUpdate: function () {
if (this._am) {
this._updateListener = new jsb.EventListenerAssetsManager(this._am, this.updateCb.bind(this));
cc.eventManager.addListener(this._updateListener, 1);
if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
console.log('load local manifest');
this._am.loadLocalManifest(this.manifestUrl);
}
console.log('update');
this._am.update();
}
},
getVersion: function () { //獲取版本資訊
if (cc.sys.isBrowser) {
return;
}
if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
this._am.loadLocalManifest(this.manifestUrl);
}
return this._am.getLocalManifest().getVersion();
},
enter_sub_game: function() {
if (!this._storagePath) {
this.prompt.string = "請先點選下載遊戲,檢查版本是否更新!!!";
return;
}
console.log('subgame path = '+ this._storagePath);
require(this._storagePath + "/src/main.js");
},
});
如果子游戲下載成功的話就要開始跳轉到子游戲了,首先要知道子游戲下載完成後的目錄
this._storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'ALLGame/subgame');
然後require子游戲的main.js
require(this._storagePath + "/src/main.js");
但是這個時候會報錯,因為在子游戲的src目錄下面根本沒有main.js檔案。那我們這個時候就要把main.js檔案加入到src當中,並且是要在生成project.manifest檔案前加進去,不然main.js跟setting.js檔案不會隨著熱更新下載到子游戲專案下的。所以在專案構建完成的時候就要把main.js跟setting.js檔案放在build/jsb-default/src目錄下,然後生成project.manifest檔案,放到伺服器上面,這樣的話熱更新子游戲就會把main.js檔案下載下來。
接著看一瞎main.js檔案的內容:
(function () {
// if (cc && cc.sys.isNative) {
// var hotUpdateSearchPaths = cc.sys.localStorage.getItem('HotUpdateSearchPaths');
// if (hotUpdateSearchPaths) {
// jsb.fileUtils.setSearchPaths(JSON.parse(hotUpdateSearchPaths));
// console.log('[main.js] 熱更新SearchPath: ' + JSON.parse(hotUpdateSearchPaths));
// }
// }
// 這是為了解決一個重啟的 bug 而新增的
cc.director.startAnimation();
'use strict';
cc.INGAME = (jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + "ALLGame/subgame";
console.log('cc.INGAME========子游戲============================'+cc.INGAME);
var _CCSettings = null;
function boot() {
console.log('setteing配置+'+_CCSettings);
var settings = _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]];
}
}
}
}
// init engine
var canvas;
if (cc.sys.isBrowser) {
canvas = document.getElementById('GameCanvas');
}
function setLoadingDisplay() {
// Loading splash scene
var splash = document.getElementById('splash');
var progressBar = splash.querySelector('.progress-bar span');
cc.loader.onProgress = function (completedCount, totalCount, item) {
var percent = 100 * completedCount / totalCount;
if (progressBar) {
progressBar.style.width = percent.toFixed(2) + '%';
}
};
splash.style.display = 'block';
progressBar.style.width = '0%';
cc.director.once(cc.Director.EVENT_AFTER_SCENE_LAUNCH, function () {
splash.style.display = 'none';
});
}
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);
}
//cc.view.setDesignResolutionSize(settings.designWidth, settings.designHeight, cc.ResolutionPolicy.SHOW_ALL);
if (cc.sys.isBrowser) {
setLoadingDisplay();
}
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: 'res/import',
rawAssetsBase: 'res/raw-',
rawAssets: settings.rawAssets,
packedAssets: settings.packedAssets,
md5AssetsMap: settings.md5AssetsMap
});
var launchScene = settings.launchScene;
// load scene
if (cc.runtime) {
cc.director.setRuntimeLaunchScene(launchScene);
}
cc.director.loadScene(launchScene, null,
function () {
if (cc.sys.isBrowser) {
// show canvas
canvas.style.visibility = '';
var div = document.getElementById('GameDiv');
if (div) {
div.style.backgroundImage = '';
}
}
cc.loader.onProgress = null;
// play game
// cc.game.resume();
console.log('Success to load scene: ' + launchScene);
}
);
};
// jsList
var jsList = settings.jsList;
var bundledScript = settings.debug ? 'project.dev.js' : 'project.js';
if (jsList) {
jsList.push(bundledScript);
}
else {
jsList = [bundledScript];
}
// anysdk scripts
if (cc.sys.isNative && cc.sys.isMobile) {
// jsList = jsList.concat(['jsb_anysdk.js', 'jsb_anysdk_constants.js']);
}
jsList = jsList.map(function (x) {
return cc.INGAME + 'src/' + x;
});
var option = {
//width: width,
//height: height,
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);
}
if (window.document) {
var splash = document.getElementById('splash');
splash.style.display = 'block';
var cocos2d = document.createElement('script');
cocos2d.async = true;
cocos2d.src = window._CCSettings.debug ? 'cocos2d-js.js' : 'cocos2d-js-min.js';
var engineLoaded = function () {
document.body.removeChild(cocos2d);
cocos2d.removeEventListener('load', engineLoaded, false);
window.eruda && eruda.init();
boot();
};
cocos2d.addEventListener('load', engineLoaded, false);
document.body.appendChild(cocos2d);
}
else if (window.jsb) {
console.log('返回大廳=======================================');
if (!cc.chilgame) {
cc.chilgame = _CCSettings = require(cc.INGAME + '/src/settings.js');
console.log('載入settings.js成功 cc.chilgame'+cc.chilgame);
require('src/project.js');
console.log('載入project.js成功');
} else {
_CCSettings = cc.chilgame;
}
boot();
}
})();
這個main.js跟jsb-default目錄下的main.js就改變了兩個地方,一個是增加了一個cc.INGAME的全域性變數,也就是子游戲目錄:
cc.INGAME = (jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + "ALLGame/subgame";
第二個地方就是改變了window.jsb原生的配置函式,把原先的
require('src/settings.js');
require('src/jsb_polyfill.js');
boot();
改變成
if (!cc.chilgame) {
cc.chilgame = _CCSettings = require(cc.INGAME + '/src/settings.js');
console.log('載入settings.js成功 cc.chilgame'+cc.chilgame);
require('src/project.js');
console.log('載入project.js成功');
} else {
_CCSettings = cc.chilgame;
}
boot();
上面的修改就是讓引擎執行子游戲的邏輯程式碼,setting.js的配置函式,跟project.js都執行的是子函式的,這樣的話就相當於跳轉到子游戲的邏輯了。
接下來就是從子游戲返回到大廳。根據大廳跳轉到子游戲的邏輯來看的話,返回到大廳的話其實就是再次執行大廳的遊戲邏輯,所以只要執行大廳的main.js檔案就可以了。返回大廳的邏輯很簡單,就是一個require大廳的js配置檔案程式碼,
cc.INGAME = (jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + "ALLGame/subgame";
require(cc.INGAME+"/src/dating.js");
dating.js檔案如下:
(function () {
// if (cc && cc.sys.isNative) {
// var hotUpdateSearchPaths = cc.sys.localStorage.getItem('HotUpdateSearchPaths');
// if (hotUpdateSearchPaths) {
// jsb.fileUtils.setSearchPaths(JSON.parse(hotUpdateSearchPaths));
// console.log('[main.js] 熱更新SearchPath: ' + JSON.parse(hotUpdateSearchPaths));
// }
// }
// 這是為了解決一個重啟的 bug 而新增的
cc.director.startAnimation();
'use strict';
cc.INGAME = '';//(jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/');
console.log('cc.INGAME========dating.js============================'+cc.INGAME);
var _CCSettings = null;
function boot() {
var settings = _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]];
}
}
}
}
// init engine
var canvas;
if (cc.sys.isBrowser) {
canvas = document.getElementById('GameCanvas');
}
function setLoadingDisplay() {
// Loading splash scene
var splash = document.getElementById('splash');
var progressBar = splash.querySelector('.progress-bar span');
cc.loader.onProgress = function (completedCount, totalCount, item) {
var percent = 100 * completedCount / totalCount;
if (progressBar) {
progressBar.style.width = percent.toFixed(2) + '%';
}
};
splash.style.display = 'block';
progressBar.style.width = '0%';
cc.director.once(cc.Director.EVENT_AFTER_SCENE_LAUNCH, function () {
splash.style.display = 'none';
});
}
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);
}
//cc.view.setDesignResolutionSize(settings.designWidth, settings.designHeight, cc.ResolutionPolicy.SHOW_ALL);
if (cc.sys.isBrowser) {
setLoadingDisplay();
}
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: 'res/import',
rawAssetsBase: 'res/raw-',
rawAssets: settings.rawAssets,
packedAssets: settings.packedAssets,
md5AssetsMap: settings.md5AssetsMap
});
var launchScene = settings.launchScene;
// load scene
if (cc.runtime) {
cc.director.setRuntimeLaunchScene(launchScene);
}
cc.director.loadScene(launchScene, null,
function () {
if (cc.sys.isBrowser) {
// show canvas
canvas.style.visibility = '';
var div = document.getElementById('GameDiv');
if (div) {
div.style.backgroundImage = '';
}
}
cc.loader.onProgress = null;
// play game
// cc.game.resume();
console.log('Success to load scene: ' + launchScene);
}
);
};
// jsList
var jsList = settings.jsList;
var bundledScript = settings.debug ? 'project.dev.js' : 'project.js';
if (jsList) {
jsList.push(bundledScript);
}
else {
jsList = [bundledScript];
}
// anysdk scripts
if (cc.sys.isNative && cc.sys.isMobile) {
// jsList = jsList.concat(['jsb_anysdk.js', 'jsb_anysdk_constants.js']);
}
jsList = jsList.map(function (x) {
return cc.INGAME + 'src/' + x;
});
var option = {
//width: width,
//height: height,
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);
}
if (window.document) {
var splash = document.getElementById('splash');
splash.style.display = 'block';
var cocos2d = document.createElement('script');
cocos2d.async = true;
cocos2d.src = window._CCSettings.debug ? 'cocos2d-js.js' : 'cocos2d-js-min.js';
var engineLoaded = function () {
document.body.removeChild(cocos2d);
cocos2d.removeEventListener('load', engineLoaded, false);
window.eruda && eruda.init();
boot();
};
cocos2d.addEventListener('load', engineLoaded, false);
document.body.appendChild(cocos2d);
}
else if (window.jsb) {
console.log('返回大廳=======================================');
if (!cc.dating) {
cc.dating = _CCSettings = require(cc.INGAME + 'src/settings.js');
require(cc.INGAME + 'src/project.js');
} else {
_CCSettings = cc.dating;
}
boot();
}
})();
可以看到dating.js的邏輯相比子游戲的main.js的程式碼只是把路徑修改到大廳的目錄,然後執行大廳的配置檔案。
然後一個非常粗略的大跟子游戲相互跳轉的功能就完成了。如果後續需要對子游戲跟大廳分別進行更新跟管理的話需要自行擴充套件了。。