参考cocos论坛:http://forum.cocos.com/t/1-5-2-demo/48200
demo: https://github.com/zhangjiangyi/HallAndChild
主要的是实现下载子游戏、跳转到子游戏,然后由子游戏返回到大厅。
我们知道、在启动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的代码只是把路径修改到大厅的目录,然后执行大厅的配置文件。
然后一个非常粗略的大跟子游戏相互跳转的功能就完成了。如果后续需要对子游戏跟大厅分别进行更新跟管理的话需要自行扩展了。。