Unity中如何展示排行榜这类微信关系数据
背景和原理介绍
小游戏提供了一系列接口获取好友关系链数据,为了安全,绝大部分接口都只能在开放数据域内调用。
开放数据域虽然乍一看有点复杂,但只需要核心理解几个点即可:
- 开放数据域所有逻辑都在一个独立的文件夹内完成,它核心处理两件事:调用关系链接口拉好友数据和将数据绘制到 sharedCanvas;
- 开放数据域接触到的 sharedCanvas 是一个离屏的 canvas,它完全不理解自己最终是怎么被绘制到屏幕上的;
- 主域(其实就是开放数据域文件夹外的业务代码)同样能接触 sharedCanvas,一般是通过 sharedCanvas 创建精灵添加到游戏场景;
- 数据的通信一定是单向的,只能从主域流向开放数据域,主域无法感知到开放数据域发生了什么;
- 鉴于第4点,主域要更新 sharedCanvas 只能定期刷新纹理来实现同步开放数据域的变化;
Unity 里面要实现 sharedCanvas 的绘制,核心在于 hook Unity 的渲染,完整的原理为:
- Unity 侧有个占位的纹理;
- Unity WebGL 模式下这个占位纹理会对应有一个 WebGLObject,Unity 会调用 drawElements API 绘制到 canvas;
- 在需要绘制排行榜的时候,将原本要绘制的 WebGLObject 替换成通过 sharedCanvas 创建而来的 WebGLObject;
- 在关闭排行榜的时候,停止步骤 3 的 hook;
重点更新:我们在基础库3.6.6版本支持了 ScreenCanvas 模式的开放数据域
- 区别:sharedCanvas 变成了在屏模式(ScreenCanvas),不再需要依赖游戏域循环刷新渲染
- 好处:减少由于渲染离屏Canvas产生的干扰问题
- 如何实现:
WX.GetOpenDataContext(new OpenDataContextOption
{
sharedCanvasMode = CanvasType.ScreenCanvas
})
详细步骤
1、设置占位纹理
在游戏需要展示的地方创建一个 RawImage,其中 Texture 属性自己选择透明的图片即可,后续展示时会被动态替换。因为unity纹理与 Web 的绘制存在倒立的差异,请将先将 rotation的x 设置为180,即让 UI控件延X轴旋转180度
再调整到游戏中合适的位置,如下图
2、调用SDK的API
2.1 先初始化开放数据域类型
初始化后canvas类型无法修改,只初始化一次并保存即可
void InitOpenDataContext()
{
if (openDataContext == null)
{
WXOpenDataContext openDataContext = WX.GetOpenDataContext(new OpenDataContextOption
{
sharedCanvasMode = selectedCanvasType
});
}
}
CanvasType
有OffScreenCanvas
和ScreenCanvas
两种类型,OffScreenCanvas
为旧版本,推荐使用ScreenCanvas
2.2 通过 PostMessage 向开放数据域传递消息
如果需要在 Unity 中向开放域页面传递数据,可以调用openDataContext.PostMessage
,如下代码:
[System.Serializable]
public class OpenDataMessage
{
// type 用于表明时间类型
public string type;
}
OpenDataMessage msgData = new OpenDataMessage();
msgData.type = "showFriendsRank";
string msg = JsonUtility.ToJson(msgData);
openDataContext.PostMessage(msg);
开放数据域JS代码可以按照以下实现:
wx.onMessage(data => {
console.log("[WX OpenData] onMessage", data);
if (typeof data === "string") {
try {
data = JSON.parse(data);
} catch (e) {
console.error("[WX OpenData] onMessage data is not a object");
return;
}
}
if (data.type === 'showFriendsRank') {
// 执行好友排行榜渲染
}
});
2.3 在需要展示的地方调用,WX.ShowOpenData
WX.ShowOpenData(rawImage.texture, x, y, width, height);
其中 :
- x : 占位区域对应屏幕左上角横坐标
- y : 占位区域对应屏幕左上角纵坐标,注意左上角为(0,0)
- width : 占位区域对应的宽度
- height : 占位区域对应的高度
WX.ShowOpenData 最终会调用 minigame/unity-sdk/open-data.js 内的 WXShowOpenData 方法,核心是三个作用:
- 调用 wx.getOpenDataContext,这会触发开放数据域的初始化,也就是 open-data 文件夹下的代码在开放数据域初始化之后才能够执行;
- 给开放数据域侧抛一个事件,告知开放域去执行数据拉取和渲染操作,对于 WXRender 的处理没有任何要求,开放数据域甚至可以忽略这个事件;
// 注意,该代码是Unity模板中的代码,无需开发者实现
openDataContext.postMessage({
type: "WXRender",
x: x,
y: y,
width: width,
height: height,
devicePixelRatio: window.devicePixelRatio,
});
- 如果是
OffScreenCanvas
模式,则会开始 hook Unity 的渲染,原本的 RawImage 就会被替换成 sharedCanvas 的纹理。如果是ScreenCanvas
模式,则会修改宽高并直接在屏渲染
2.4 需要关闭开放数据域时则调用,WX.HideOpenData
如果是OffScreenCanvas
模式,这一步非常重要,如果仅仅在 Unity 侧隐藏了 RawImage 而没有调用 WX.HideOpenData,很可能导致排行榜关闭之后纹理错乱,比如有些地方的纹理变成了排行榜对应的纹理。
3、导出选项勾选使用好友关系链
这一步会做两个事情:
- game.json 会声明使用开放数据域;
- 将插件内置的 open-data 示例拷贝至 minigame 目录,请注意做好文件备份;
4、用JS开发排行榜这类微信关系逻辑
开放数据域的开发和普通小游戏并无区别,暂时只能通过 js 来开发,对技术选型并无要求,可以不依赖任何引擎调用 Canvas2D API 执行渲染,但如果需要事件点击、滚动列表等处理,就会显得很麻烦;也可以选择完整的 JS 游戏引擎比如 Cocos,但这会使得代码包明显增大进而影响启动速度。
因此插件内置的示例采用的是微信自研的轻量级渲染引擎,压缩后只有几十k,这需要你掌握一些简单的 Web 开发知识,包括 Flex布局、CSS,参照示例修修改改很快能够上手。
5、示例DEMO
可以参考Demo/Ranking下面的Unity工程。插件导出的 open-data 已经是一个比较功能完备的工程,进行简单的魔改就能够满足需求。
5.1 好友排行榜
- 展示 RawImage 的时候调用 SDK API
void ShowOpenData()
{
RankMask.SetActive(true);
RankingBox.SetActive(true);
//
// 注意这里传x,y,width,height是为了点击区域能正确点击,x,y 是距离屏幕左上角的距离,宽度传 (int)RankBody.rectTransform.rect.width是在canvas的UI Scale Mode为 Constant Pixel Size的情况下设置的。
/**
* 如果父元素占满整个窗口的话,pivot 设置为(0,0),rotation设置为180,则左上角就是离屏幕的距离
* 注意这里传x,y,width,height是为了点击区域能正确点击,因为开放数据域并不是使用 Unity 进行渲染而是可以选择任意第三方渲染引擎
* 所以开放数据域名要正确处理好事件处理,就需要明确告诉开放数据域,排行榜所在的纹理绘制在屏幕中的物理坐标系
* 比如 iPhone Xs Max 的物理尺寸是 414 * 896,如果排行榜被绘制在屏幕中央且物理尺寸为 200 * 200,那么这里的 x,y,width,height应当是 107,348,200,200
* x,y 是距离屏幕左上角的距离,宽度传 (int)RankBody.rectTransform.rect.width是在canvas的UI Scale Mode为 Constant Pixel Size的情况下设置的
* 如果是Scale With Screen Size,且设置为以宽度作为缩放,则要这要做一下换算,比如canavs宽度为960,rawImage设置为200 则需要根据 referenceResolution 做一些换算
* 不过不管是什么屏幕适配模式,这里的目的就是为了算出 RawImage 在屏幕中绝对的位置和尺寸
*/
CanvasScaler scaler = gameObject.GetComponent<CanvasScaler>();
var referenceResolution = scaler.referenceResolution;
var p = RankBody.transform.position;
WX.ShowOpenData(RankBody.texture, (int)p.x, Screen.height - (int)p.y, (int)((Screen.width / referenceResolution.x) * RankBody.rectTransform.rect.width), (int)((Screen.width / referenceResolution.x) * RankBody.rectTransform.rect.height));
}
- 发送事件给开放数据域,要求展示好友排行榜
OpenDataMessage msgData = new OpenDataMessage();
msgData.type = "showFriendsRank";
string msg = JsonUtility.ToJson(msgData);
openDataContext.PostMessage(msg);
- 开放数据域监听相应事件,展示群排行,详见 open-data。
整体流程示意:
5.2 群好友排行榜
- 为了使用群排行榜,需要调用 WX.UpdateShareMenu 设置分享菜单
/**
* 使用群排行功能需要特殊设置分享功能,详情可见链接
* https://developers.weixin.qq.com/minigame/dev/guide/open-ability/share/share.html
*/
WX.UpdateShareMenu(new UpdateShareMenuOption()
{
withShareTicket = true,
isPrivateMessage = true,
});
- 在分享时,带上相关query
WX.ShareAppMessage(new ShareAppMessageOption()
{
title = "最强战力排行榜!谁是第一?",
query = "minigame_action=show_group_list",
imageUrl = "https://mmgame.qpic.cn/image/5f9144af9f0e32d50fb878e5256d669fa1ae6fdec77550849bfee137be995d18/0",
});
- 监听 WX.OnShow 回调,给开放数据域发消息要求展示群排行
WX.OnShow((OnShowCallbackResult res) =>
{
string shareTicket = res.shareTicket;
Dictionary<string, string> query = res.query;
if (!string.IsNullOrEmpty(shareTicket) && query != null && query["minigame_action"] == "show_group_list")
{
InitOpenDataContext();
ShowOpenData();
OpenDataMessage msgData = new OpenDataMessage();
msgData.type = "showGroupFriendsRank";
msgData.shareTicket = shareTicket;
string msg = JsonUtility.ToJson(msgData);
openDataContext.PostMessage(msg);
}
});
- 开放数据域监听相应事件,展示群排行,详见 open-data。
整体流程示意:
常见问题QA
Q1. 为什么第一次调用 WX.ShowOpenData 之后画面先黑一下再展示排行榜?
WX.ShowOpenData
在openDataContext.postMessage
WXRender
的事件之后立马就会开始 hook Unity 的渲染,如果开放数据域在监听到WXRender
事件之后没有任何渲染行为,那么 sharedCanvas 纹理就还没有准备好,Unity 侧就可能出现黑一下的情况,解决办法是保证监听到WXRender
事件之后有个同步的渲染行为,比如绘制个文案”好友数据加载中..."。
Q2. 为什么我关闭排行榜之后界面上有些问题错乱了?
- 基本上只可能是没调用
WX.HideOpenData
,建议WX.HideOpenData
打些日志来辅佐排查。
Q3. 为什么开放数据域滚动事件不生效?
WX.ShowOpenData(rawImage.texture, x, y, width, height)
的后面四个参数,核心目的是告诉开放数据域 sharedCanvas 最终被绘制在了屏幕的位置和尺寸,开放数据域才能够正确处理事件监听,遇到事件不生效的问题,首先排查传进来的参数是否符合预期,比如 x / y 不应该是负数。
Q4. 为什么使用ScreenCanvas模式之后,ToTempFilePath没有开放数据域的画面了?
- 因为ScreenCanvas模式的画布是浮在游戏主画布之上的,并不是同一个画布,如需保存开放数据域画面,需要使用
WXSharedCanvas.ToTempFilePath
,使用示例参考示例