基于熱更新的基礎(chǔ)上可缚,將子游戲構(gòu)建生成的main.js文件一并移入src目錄霎迫,在運(yùn)行子游戲的時(shí)候,我們只需要require main.js這個(gè)文件即可帘靡。
大廳跳到子游戲
首先是大廳封裝好的子游戲管理類知给,包括子游戲下載、更新描姚、進(jìn)入
export class SubgameManager {
private static serverUrl;
private static storagePath:[] = [];
private static assertsMg;
private static jsbCallback;
private static subgameUpdateCallback;
private static progressCallback;
private static finishCallback;
public static init(serverUrl:string){
this.serverUrl = serverUrl;
}
public static isSubgameDownload(name:string):boolean{
let file = (jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'ALLGame/' + name + '/project.manifest';
if (jsb.fileUtils.isFileExist(file)) {
return true;
} else {
return false;
}
}
public static isNeedUpdateSubgame(name:string,subgameUpdateCallback?:Function){
this.prepareJsb(name);
this.subgameUpdateCallback = subgameUpdateCallback;
this.jsbCallback = new jsb.EventListenerAssetsManager(this.assertsMg, this.needUpdateCallback.bind(this));
cc.eventManager.addListener(this.jsbCallback, 1);
this.assertsMg.checkUpdate();
}
private static needUpdateCallback(event){
let self = this;
switch (event.getEventCode()) {
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
cc.log('子游戲已經(jīng)是最新的涩赢,不需要更新');
self.subgameUpdateCallback && self.subgameUpdateCallback(false);
break;
case jsb.EventAssetsManager.NEW_VERSION_FOUND:
cc.log('子游戲需要更新');
self.subgameUpdateCallback && self.subgameUpdateCallback(true);
break;
// 檢查是否更新出錯(cuò)
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;
}
}
public static downloadSubgame(name:string,progressCallback?:Function,finishCallback?:Function){
this.prepareJsb(name);
this.progressCallback = progressCallback;
this.finishCallback = finishCallback;
this.jsbCallback = new jsb.EventListenerAssetsManager(this.assertsMg, this.downloadCallback.bind(this));
cc.eventManager.addListener(this.jsbCallback, 1);
this.assertsMg.update();
}
private static downloadCallback(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下載配置文件錯(cuò)誤*/
cc.log('updateCb下載配置文件錯(cuò)誤');
failed = true;
break;
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
/*2 解析文件錯(cuò)誤*/
cc.log('updateCb解析文件錯(cuò)誤');
failed = true;
break;
case jsb.EventAssetsManager.NEW_VERSION_FOUND:
/*3發(fā)現(xiàn)新的更新*/
cc.log('updateCb發(fā)現(xiàn)新的更新');
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
/*4 已經(jīng)是最新的*/
cc.log('updateCb已經(jīng)是最新的');
failed = true;
break;
case jsb.EventAssetsManager.UPDATE_PROGRESSION:
/*5 最新進(jìn)展 */
cc.log("event.getPercentByFile()"+event.getPercentByFile());
self.progressCallback && self.progressCallback(event.getPercentByFile());
break;
case jsb.EventAssetsManager.ASSET_UPDATED:
/*6需要更新*/
break;
case jsb.EventAssetsManager.ERROR_UPDATING:
/*7更新錯(cuò)誤*/
cc.log('updateCb更新錯(cuò)誤');
break;
case jsb.EventAssetsManager.UPDATE_FINISHED:
/*8更新完成*/
cc.log("UPDATE_FINISHED");
self.finishCallback && self.finishCallback(true);
break;
case jsb.EventAssetsManager.UPDATE_FAILED:
/*9更新失敗*/
cc.log('UPDATE_FAILED');
self.assertsMg.downloadFailedAssets();
break;
case jsb.EventAssetsManager.ERROR_DECOMPRESS:
/*10解壓失敗*/
cc.log('解壓失敗');
break;
}
if (failed) {
cc.eventManager.removeListener(self.jsbCallback);
self.jsbCallback = null;
self.finishCallback && self.finishCallback(false);
}
}
public static enterSubgame(name) {
if (!this.storagePath[name]) {
this.downloadSubgame(name);
return;
}
window.require(this.storagePath[name] + '/src/main.js');
}
private static prepareJsb(name:string){
this.storagePath[name] = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'ALLGame/' + name);
var UIRLFILE = this.serverUrl +"/"+ name;
var customManifestStr = JSON.stringify({
'packageUrl': UIRLFILE,
'remoteManifestUrl': UIRLFILE + '/project.manifest',
'remoteVersionUrl': UIRLFILE + '/version.manifest',
'version': '0.0.1',
'assets': {},
'searchPaths': []
});
this.assertsMg = new jsb.AssetsManager('', this.storagePath[name], this.versionCompare);
if (!cc.sys.ENABLE_GC_FOR_NATIVE_OBJECTS) {
this.assertsMg.retain();
}
this.assertsMg.setVerifyCallback(function(path, asset) {
var compressed = asset.compressed;
if (compressed) {
return true;
} else {
return true;
}
});
if (cc.sys.os === cc.sys.OS_ANDROID) {
this.assertsMg.setMaxConcurrentTask(2);
}
if (this.assertsMg.getState() === jsb.AssetsManager.State.UNINITED) {
var manifest = new jsb.Manifest(customManifestStr, this.storagePath[name]);
this.assertsMg.loadLocalManifest(manifest, this.storagePath[name]);
}
}
private static versionCompare(versionA, versionB):number{
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;
}
}
}
接著看使用這個(gè)類:
import { SubgameManager } from "./SubgameManager";
const {ccclass, property} = cc._decorator;
@ccclass
export default class Subgame extends cc.Component {
@property(cc.Label)
label: cc.Label = null;
private subgame = "Niuniu"
onLoad(){
SubgameManager.init("http://192.168.0.136:8000");
if(SubgameManager.isSubgameDownload(this.subgame)){
SubgameManager.isNeedUpdateSubgame(this.subgame,(isSuccess)=>{
this.label.string = isSuccess ? "子游戲需要更新" : "子游戲不需要更新";
});
}else{
this.label.string = "子游戲未下載";
}
}
click(){
SubgameManager.downloadSubgame(this.subgame,(progress)=>{
if (isNaN(progress)) {
progress = 0;
}
this.label.string = "資源下載中 " + ~~(progress * 100) + "%";
},(success)=>{
if (success) {
SubgameManager.enterSubgame(this.subgame);
}
})
}
}
準(zhǔn)備好之后,開始準(zhǔn)備小游戲轩勘,首先將小游戲構(gòu)建下筒扒,模板是default,如果使用腳本加密绊寻,那么大廳與子游戲腳本加密的key一定要相同;ǘ铡!因?yàn)橹鞒绦蚴谴髲d澄步,解密腳本都是用大廳的key冰蘑。構(gòu)建成功后,將main.js復(fù)制一份到src下村缸,然后打開修改兩個(gè)地方祠肥。無(wú)論creator哪個(gè)版本,以構(gòu)建出來的main.js為主梯皿,然后同樣修改這兩地方就好了:
//~~~~~~~~~1仇箱、添加這段~~~~~~~~~~~~~~~
cc.director.startAnimation(); //官方說解決個(gè)BUG
'use strict';
//后面的路徑根據(jù)自己的游戲修改
cc.INGAME = (jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/')+'ALLGame/Niuniu/';
//~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~2.修改這段~~~~~~~~~~~~~~~
require(cc.INGAME + 'src/settings.js');
require(cc.INGAME +window._CCSettings ? 'src/project.dev.js' : 'src/project.js');
// require('src/jsb_polyfill.js');
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
整份main.js如下(v1.10.2):
(function () {
//~~~~~~~~~1县恕、添加這段~~~~~~~~~~~~~~~
cc.director.startAnimation(); //官方說解決個(gè)BUG
'use strict';
cc.INGAME = (jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/')+'ALLGame/Niuniu/';
//~~~~~~~~~~~~~~~~~~~~~~~~~~
function boot () {
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]];
}
}
}
}
// init engine
var canvas;
if (cc.sys.isBrowser) {
canvas = document.getElementById('GameCanvas');
}
if (false) {
var ORIENTATIONS = {
'portrait': 1,
'landscape left': 2,
'landscape right': 3
};
BK.Director.screenMode = ORIENTATIONS[settings.orientation];
initAdapter();
}
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.loader.downloader._subpackages = settings.subpackages;
if (false) {
BK.Script.loadlib();
}
cc.view.resizeWithBrowserSize(true);
if (!false && !false) {
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);
}
cc.view.enableAutoFullScreen([
cc.sys.BROWSER_TYPE_BAIDU,
cc.sys.BROWSER_TYPE_WECHAT,
cc.sys.BROWSER_TYPE_MOBILE_QQ,
cc.sys.BROWSER_TYPE_MIUI,
].indexOf(cc.sys.browserType) < 0);
}
// Limit downloading max concurrent task to 2,
// more tasks simultaneously may cause performance draw back on some android system / browsers.
// 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
});
if (false) {
cc.Pipeline.Downloader.PackDownloader._doPreload("WECHAT_SUBDOMAIN", settings.WECHAT_SUBDOMAIN_DATA);
}
var launchScene = settings.launchScene;
// load scene
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;
console.log('Success to load scene: ' + launchScene);
}
);
};
// jsList
var jsList = settings.jsList;
if (!false) {
var bundledScript = settings.debug ? 'src/project.dev.js' : 'src/project.js';
if (jsList) {
jsList = jsList.map(function (x) {
return 'src/' + x;
});
jsList.push(bundledScript);
}
else {
jsList = [bundledScript];
}
}
// anysdk scripts
if (cc.sys.isNative && cc.sys.isMobile) {
// jsList = jsList.concat(['src/anysdk/jsb_anysdk.js', 'src/anysdk/jsb_anysdk_constants.js']);
}
var option = {
//width: width,
//height: height,
id: 'GameCanvas',
scenes: settings.scenes,
debugMode: settings.debug ? cc.DebugMode.INFO : cc.DebugMode.ERROR,
showFPS: (!false && !false) && settings.debug,
frameRate: 60,
jsList: jsList,
groupList: settings.groupList,
collisionMatrix: settings.collisionMatrix,
renderMode: 0
}
cc.game.run(option, onStart);
}
if (false) {
BK.Script.loadlib('GameRes://libs/qqplay-adapter.js');
BK.Script.loadlib('GameRes://src/settings.js');
BK.Script.loadlib();
BK.Script.loadlib('GameRes://libs/qqplay-downloader.js');
qqPlayDownloader.REMOTE_SERVER_ROOT = "";
var prevPipe = cc.loader.md5Pipe || cc.loader.assetLoader;
cc.loader.insertPipeAfter(prevPipe, qqPlayDownloader);
// <plugin script code>
boot();
return;
}
if (false) {
require(window._CCSettings.debug ? 'cocos2d-js.js' : 'cocos2d-js-min.js');
require('./libs/weapp-adapter/engine/index.js');
var prevPipe = cc.loader.md5Pipe || cc.loader.assetLoader;
cc.loader.insertPipeAfter(prevPipe, wxDownloader);
boot();
return;
}
if (window.jsb) {
//~~~~~~~~~2.修改這段~~~~~~~~~~~~~~~
require(cc.INGAME + 'src/settings.js');
require(cc.INGAME +window._CCSettings ? 'src/project.dev.js' : 'src/project.js');
// require('src/jsb_polyfill.js');
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
boot();
return;
}
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);
if (typeof VConsole !== 'undefined') {
window.vConsole = new VConsole();
}
boot();
};
cocos2d.addEventListener('load', engineLoaded, false);
document.body.appendChild(cocos2d);
}
})();
修改完成后,利用上一篇熱更新提到的version_generator.js剂桥,生成project. manifest和version. manifest忠烛,這里步驟不能變,一定先構(gòu)建好子游戲渊额,復(fù)制main.js到src并修改况木,再利用version_generator.js生成project. manifest和version. manifest。準(zhǔn)備好之后旬迹,將src、res求类、project. manifest奔垦、version. manifest放在服務(wù)器:
然后可以測(cè)試跳到子游戲了。
子游戲返回大廳
在大廳跳到子游戲時(shí)尸疆,我們利用了main.js椿猎,同理的,返回大廳也是寿弱。首先準(zhǔn)好返回大廳的代碼犯眠,注意我目前的版本需要window.require,網(wǎng)上其他文章好像1.5.1以前只需要require:
returnHall(){
let subgameSearchPath = (jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/')+'ALLGame/Niuniu/';
window.require(subgameSearchPath + 'src/hall.js');
}
然后設(shè)置好點(diǎn)擊事件構(gòu)建后症革,與上面的步驟一樣復(fù)制main.js到src并修改筐咧,然后將修改完的main.js復(fù)制一份,改名為hall.js噪矛,修改hall.js的 cc.INGAME量蕊,這里區(qū)分Android與iOS :
if (cc.sys.os === cc.sys.OS_ANDROID) {
cc.INGAME = 'assets/';
}else if(cc.sys.os == cc.sys.OS_IOS){
cc.INGAME = jsb.reflection.callStaticMethod("AppController", "getHallPath")+"/";
}
iOS還需要在xcode中,AppController類下加入方法getHallPath:
+ (NSString *)getHallPath
{
return [[NSBundle mainBundle] bundlePath];
}
解決游戲之間cid艇挨、classname沖突問題
A Class already exists with the same cid
cid沖突可能是復(fù)制原因造成的残炮,解決的方法是把沖突的腳本移出工程,再等creator刷新后缩滨,重新導(dǎo)入進(jìn)來势就。
A Class already exists with the same classname
classname沖突,如果是公用的腳本脉漏,比如一些通用類苞冯,在各個(gè)游戲一樣的話,可以忽略鸠删,creator不會(huì)重新加載抱完,但那些有區(qū)別的類名又相同的,目前的做法是每個(gè)游戲都類名都加游戲前綴刃泡。
解決內(nèi)存問題
已知的問題:
假如進(jìn)去子游戲一次巧娱,退出到大廳碉怔,發(fā)現(xiàn)更新了,更新子游戲了禁添,再進(jìn)去子游戲沒有更新到撮胧,因?yàn)樽佑螒虻臄?shù)據(jù)還在內(nèi)存,不會(huì)再去重新load老翘。
子游戲退出到大廳芹啥,內(nèi)存數(shù)據(jù)還在,下次進(jìn)入子游戲的數(shù)據(jù)還是最后一次修改的數(shù)據(jù)铺峭,不會(huì)重置墓怀。
目前沒有很好的方案,我們用了一種偏方卫键,返回大廳都用cc.game.restart傀履,黑屏的問題,利用原生交互彈一張loading莉炉,因?yàn)閏c.game.restart不會(huì)重啟應(yīng)用钓账,用一張loading圖先蓋住creator,等大廳onenable是時(shí)候隱藏了loading絮宁。