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
目录,让其成为数据的一部分,在子游戏更新的时候会下载完整的
res
、
src
目录,其中就囊括了我们执行所需的
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
置位
undefined
,
main.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
子游戏更新的心得,借此分享一下。