微信小游戏开发-开放数据域

开放数据域

微信的开放数据域是一个封闭的,独立的javascript作用域,也称为子域。在开放数据域中可以获取到用户的关系链数据,用来制作好友排行榜等功能。
为了防止用户的关系链数据被窃取,除了将开放数据域独立出来,游戏主域中的许多wx接口在开放数据域中也是被屏蔽掉的,例如文件下载,文件写入等接口。这就给开发者开发带来了些困难,无法像开发游戏主域功能那样开发开放数据域的功能。
cocos creator从1.9版本之后支持将子域作为一个独立的工程进行开发,相对比最初子域工程需要裸写UI界面的代码,这大大的节省了开发者进行子域界面排版布局和调整屏幕适配的工作量。

子域渲染原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

_updaetSubDomainCanvas() {
if (!window.GameSettingConfig) {
console.log("没有游戏设置");
return;
}
if (!this._tex) {
console.log("没有tex");
return;
}
var openDataContext = wx.getOpenDataContext();
var sharedCanvas = openDataContext.canvas;
this._tex.initWithElement(sharedCanvas);
this._tex.handleLoadedTexture();
this.subContext.spriteFrame = new cc.SpriteFrame(this._tex);
},

代码其实并不复杂,子域其实是渲染在一块离屏的canvas上,在主域通过wx.getOpenDataContext获取到canvas,然后将这块canvas渲染到纹理_tex上,再将得到的纹理赋给sprite在主域中渲染。(原理在 webGL渲染到纹理 中有介绍)

不过cocos这个方案也有问题,下面是这个方案三个主要的问题。

子域包体过大

将子域作为独立的cocos工程来开发,不可避免的引入了cocos引擎文件和微信的适配接口库,这使得在不算资源文件的情况下,子域的包体已经接近1M。
因为小游戏主包的最大限制是4M,所以子域的资源文件必须像主域一样通过下载存放在手机外存中,然后通过修改加载路径读取外存中的资源文件。
在前面已经说过,微信屏蔽了子域的下载接口,所以子域的资源文件只能由主域下载。

1
2
3
4
5
6
7
8
9
10
bootSubContext: function () {
if (PlatformUtil.IsWeichatGame()) {
let openid = window.WeChatMgr.getOpenId();
wx.postMessage({
message: 'bootSubContext',
localResPath: window.GameSettingConfig.BIG_TANK_RES_PATH,
openid: openid,
});
}
},

在游戏启动的时候,我们读取配置中的手机外存地址,将地址通过postMessage发送给子域。

1
2
3
4
5
6
7
8
9

wx.onMessage(function(data){
if(data.message === 'bootSubContext')
{
window.localResPath = data.localResPath;
_this._openid = data.openid;
_this.boot();
}
}

子域在收到外存地址之后保存在window.localResPath中,接着我们修改子域中的文件读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

......

if (isSubdomain) {
if (REGEX.test(item.url)) {
callback(null, null);
return;
}

if(window.localResPath)
{
this.SUBCONTEXT_ROOT = window.localResPath;//修改文件路径
}

item.url = this.SUBCONTEXT_ROOT + item.url;

if (item.type && non_text_format.indexOf(item.type) !== -1) {
nextPipe(item, callback);
return;
}
}

......

我们修改子域读取资源的路径,将路径指向手机外存地址,而资源已经通过主域的下载模块下载解压在了相应的地址下。

安卓子域模糊问题

由于cocos实现的bug,子域模块在安卓上会变的模糊。

正常的排行榜:

模糊的排行榜:

通过调试我们发现,模糊的原因是在安卓上,子域的canvas尺寸与主域的canvas尺寸不同,导致子域的canvas在渲染到主域的时候被拉伸了。
于是,我们在开始渲染子域之前修改了子域canvas的尺寸,将其修改为将要渲染的sprite的尺寸。

1
2
3
4
5
6
7

//只处理安卓平台
if (PlatformUtil.isTargetPlatform(PlatformUtil.PLATFORM.ANDROID)) {
var openDataContext = wx.getOpenDataContext();
openDataContext.canvas.width = this.subContext.node.width;
openDataContext.canvas.height = this.subContext.node.height;
}

解决了模糊问题之后,我们发现列表的滑动出了问题,变得不正常。分析发现,是触摸的坐标转换出了问题,在子域中修改触摸坐标转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

var _devicePixelRatio = Math.min(2, window.devicePixelRatio || 1);

cc.view.convertToLocationInView = function (tx, ty, relatedPos, out) {
let result = out || cc.v2();
let x = _devicePixelRatio * (tx - relatedPos.left);
let y = _devicePixelRatio * (relatedPos.top + relatedPos.height - ty);
if (this._isRotated) {
result.x = this._viewportRect.width - y;
result.y = x;
}
else {
result.x = x;
result.y = y;
}
return result;
};

主要的修改是在原来的基础上乘上_devicePixelRatio,因为我们在前面仅仅修改的是canvas的尺寸,逻辑上的子域依然跟主域的尺寸不一样。

至于_devicePixelRatio的取值,我是参考cocos2.0版本的取值(2.0修复了子域模糊的问题)。我的猜测是cocos 1.9在安卓上错误的使用了屏幕的物理分辨率作为子域canvas的尺寸,所以主域和子域的尺寸刚好差了_devicePixelRatio倍。

子域渲染循环

子域的渲染循环在游戏的过程中会一直存在,但是我们目前只是在战场外的排行榜中使用到子域的功能,战场中并没有子域的相关功能,所以我们需要在进入战场的时候停掉子域的渲染循环,然后在战斗结束之后打开。

1
2
3
4
5
6
7
8
9
10
_SetEnableSubContext: function(value) {
if (PlatformUtil.IsWeichatGame()) {
if (value) {
wx.postMessage({ message: 'startMainLoop' });
}
else {
wx.postMessage({ message: 'pauseMainLoop' });
}
}
},

我们通过_SetEnableSubContext给子域发送消息,子域中通过startMainLoop和pauseMainLoop控制渲染循环。

1
2
3
4
5
6
7
8
9
10
11
//重启mainloop
startMainLoop: function()
{
cc.game.resume();
},

//停止mainloop
pauseMainLoop: function()
{
cc.game.pause();
},