Unity中如何展示排行榜这类微信关系数据
微信官方的排行榜 Demo
微信自己的 canvas 渲染引擎
wechat-miniprogram.github.io/minigame-ca…
微信自己的 canvas 渲染引擎提供的好友排行榜示例
wechat-miniprogram.github.io/minigame-ca…
排行榜样式相关的文件夹结构
styles 文件夹里面的文件负责定义样式,也就是 class
tpls 文件夹里面的文件负责定义内容,也就是写一些 html 标签,然后标签用到你之前定义的 class
排行榜的内容是怎么定义的
tpls 的 friendRank.js 定义了排行榜的内容
可以看一下微信给的样式是怎么样的
<view class="container" id="main">
<view class="rankList">
<scrollview class="list" scrollY="true">
{{~it.data :item:index}}
<view class="listItem">
<image src="open-data/render/image/rankBg.png" class="rankBg"></image>
<image class="rankAvatarBg" src="open-data/render/image/rankAvatar.png"></image>
<image class="rankAvatar" src="{{= item.avatarUrl }}"></image>
<view class="rankNameView">
<image class="rankNameBg" src="open-data/render/image/nameBg.png"></image>
<text class="rankName" value="{{=item.nickname}}"></text>
<text class="rankScoreTip" value="战力值:"></text>
<text class="rankScoreVal" value="{{=item.score || 0}}"></text>
</view>
<view class="shareToBtn" data-isSelf="{{= item.isSelf ? true : false}}" data-id="{{= item.openid || ''}}">
<image src="open-data/render/image/{{= item.isSelf ? 'button3' : 'button2'}}.png" class="shareBtnBg"></image>
<text class="shareText" value="{{= item.isSelf ? '你自己' : '分享'}}"></text>
</view>
</view>
{{~}}
</scrollview>
</view>
</view>
他这里很简单,就是用 view 嵌套,然后在 scrollview 中一路排下来,就得到了排行榜
因为他是一个 flex 排布的,在 Assets\WX-WASM-SDK\wechat-default\open-data\render\styles\tips.js 中定义了 container 的 flexDirection = 'row'
可以看看它的效果:
具体的话,他这里每一个元素都定义了一个 class,也就是样式,想要看 class 是什么样的,可以去 styles 文件夹看
怎么在按行排列的 scroll 中定义一个上下分层排列的元素
他对于需要实现上下分层的效果的元素,是使用子 view 中的绝对位置来防止上下的,例如这里 rankNameView 是上下分层的,那么创建一个子 view
<view class="rankNameView">
...
...
...
</view>
那么 rankNameBg rankName 通过定义 top 来放在上面,rankScoreTip rankScoreVal 通过定义 bottom 放在下面
可以看到它们的 class 里面是怎么定义位置的
rankNameView: {
position: 'relative',
marginLeft: data.width * 0.06,
width: data.width * 0.35,
height: data.height / 2 / 3,
},
rankNameBg: {
position: 'absolute',
top: (data.height / 2 / 3) * 0.14,
left: 0,
width: data.width * 0.35,
height: (data.height / 2 / 3) * 0.4,
},
rankName: {
position: 'absolute',
top: (data.height / 2 / 3) * 0.14,
left: 0,
width: data.width * 0.35,
height: (data.height / 2 / 3) * 0.4,
textAlign: 'center',
lineHeight: (data.height / 2 / 3) * 0.4,
fontSize: data.width * 0.043,
textOverflow: 'ellipsis',
color: '#fff',
},
rankScoreTip: {
position: 'absolute',
bottom: (data.height / 2 / 3) * 0.1,
left: 0,
width: data.width * 0.15,
height: (data.height / 2 / 3) * 0.3,
lineHeight: (data.height / 2 / 3) * 0.3,
fontSize: data.width * 0.042,
color: '#fff',
},
rankScoreVal: {
position: 'absolute',
bottom: (data.height / 2 / 3) * 0.1,
left: data.width * 0.15,
width: data.width * 0.18,
height: (data.height / 2 / 3) * 0.3,
lineHeight: (data.height / 2 / 3) * 0.3,
fontSize: data.width * 0.042,
color: '#fff',
},
编译得到的模板函数,稍微排版一下就能知道他是什么意思
最终这些 html 要通过微信自己的渲染引擎,所以这些 html 需要变成微信的渲染引擎所需要的函数
它在示例中给出了一个默认的 html 转化成的所需函数
export default function anonymous(it) {
let out = '<view class="container" id="main"> <view class="rankList"> <scrollview class="list" scrollY="true"> ';
const arr1 = it.data;
if (arr1) {
let item;
let index = -1;
const l1 = arr1.length - 1;
while (index < l1) {
item = arr1[(index += 1)];
out += ` <view class="listItem"> <image src="open-data/render/image/rankBg.png" class="rankBg"></image> <image class="rankAvatarBg" src="open-data/render/image/rankAvatar.png"></image> <image class="rankAvatar" src="${
item.avatarUrl
}"></image> <view class="rankNameView"> <image class="rankNameBg" src="open-data/render/image/nameBg.png"></image> <text class="rankName" value="${
item.nickname
}"></text> <text class="rankScoreTip" value="战力值:"></text> <text class="rankScoreVal" value="${
item.score || 0
}"></text> </view> <view class="shareToBtn" data-isSelf="${!!item.isSelf}" data-id="${
item.openid || ''
}"> <image src="open-data/render/image/${
item.isSelf ? 'button3' : 'button2'
}.png" class="shareBtnBg"></image> <text class="shareText" value="${
item.isSelf ? '你自己' : '分享'
}"></text> </view> </view> `;
}
}
out += ' </scrollview> </view></view>';
return out;
}
其实这样子看会非常丑,只要稍微排版一下就会好很多
export default function anonymous(it) {
let out = '<view class="container" id="main"> <view class="rankList"> <scrollview class="list" scrollY="true"> ';
const arr1 = it.data;
if (arr1) {
let item;
let index = -1;
const l1 = arr1.length - 1;
while (index < l1) {
item = arr1[(index += 1)];
out += ` <view class="listItem">
<image src="open-data/render/image/rankBg.png" class="rankBg"></image>
<image class="rankAvatarBg" src="open-data/render/image/rankAvatar.png"></image>
<image class="rankAvatar" src="${item.avatarUrl}"></image>
<view class="rankNameView">
<image class="rankNameBg" src="open-data/render/image/nameBg.png"></image>
<text class="rankName" value="${item.nickname}"></text>
<text class="rankScoreTip" value="战力值:"></text>
<text class="rankScoreVal" value="${item.score || 0}"></text>
</view>
<view class="shareToBtn" data-isSelf="${!!item.isSelf}" data-id="${item.openid || ''}">
<image src="open-data/render/image/${item.isSelf ? 'button3' : 'button2'}.png" class="shareBtnBg"></image>
<text class="shareText" value="${item.isSelf ? '你自己' : '分享'}"></text>
</view>
</view> `;
}
}
out += ' </scrollview> </view></view>';
return out;
}
这样的话一看就懂了,他微信自己的渲染引擎其实也是大部分解析的 h5 的标签,只是在定义逻辑结构,比如条件语句啊,循环语句啊,获取变量啊这方面是不一样的
“仅显示前 50 位好友排名”的功能
这是一个提供“仅显示前 50 位好友排名”的功能的例子
export default function anonymous(it) {
let out = `
<view class="container" id="main">
<view class="rankList">
<scrollview class="scrollList" scrollY="true"> `;
const arr1 = it.data;
let tempStr ='';
if (arr1) {
let item;
let count = arr1.length;
for(var index = 0; index < count ; index++){
item = arr1[index];
out += `
<view class="listItem">
<image src="open-data/render/image/rankBg.png" class="listItemBackground"></image>
<text class="listItemRank" value="${index +1 }"></text>
<image class="listItemAvatar" src="${item.avatarUrl}"></image>
<text class="listItemName" value="${item.nickname}"></text>
<text class="listItemScore" value="${item.score || 0}"></text>
</view>`;
if(item.isSelf){
tempStr = `
<view class="listItem">
<image src="open-data/render/image/rankBg.png" class="listItemBackground"></image>
<text class="listItemRank" value="${index +1 }"></text>
<image class="listItemAvatar" src="${item.avatarUrl}"></image>
<text class="listItemName" value="${item.nickname}"></text>
<text class="listItemScore" value="${item.score || 0}"></text>
</view>`;
}
}}
out += `
</scrollview>
<text class="listTips" value="仅展示前50位好友排名"></text>`
out += tempStr;
out += `
</view>
</view>`;
return out;
}
效果可以看官方的文档
wechat-miniprogram.github.io/minigame-ca…
这一部分是好友的排行
这一部分是仅展示前 50 再加上自己的排行
它使用的这个实时渲染的插件 codepen 是需要额外定义环境的,所以才与我们在 Assets\WX-WASM-SDK\wechat-default\open-data\render 中看到的文件是不一样的
自定义数据
自定义数据相关的东西要参照官方的排行榜 Demo
如果你需要在排行榜里面自定义数据,首先需要在 Unity 项目里面设置好 OpenDataMessage
[System.Serializable]
public class OpenDataMessage
{
// type 用于表明时间类型
public string type;
public string shareTicket;
public int score;
}
然后在需要设置这个自定义属性的时候,使用 setUserRecord
这是官方的用例,核心就在 msgData.type = "setUserRecord"; 然后 msgData.score = ...
OpenDataMessage msgData = new OpenDataMessage();
msgData.type = "setUserRecord";
msgData.score = Random.Range(1, 1000);
string msg = JsonUtility.ToJson(msgData);
Debug.Log(msg);
WX.GetOpenDataContext().PostMessage(msg);
然后在你定义的排行榜 h5 中就可以使用这个数据了
<text class="listItemScore" value="${item.score || 0}"></text>
痛苦 Debug
快速调整 js
可以直接在微信开发者工具的内置 IDE 中修改 js 脚本,保存之后,就会重新编译小游戏
这样就可以立即看到 js 方面的更改,而不用每次都要去 Unity 中导出
style/friendRank.js 中缺少 class 导致的报错
有一个错误是,明明提示的是 请进入设置页允许获取微信朋友信息 但是我在设置里面确实看到了是可以允许获取微信朋友信息的
我在 LayoutWithTplAndStyle 这里放了一个 try catch 但是没有捕捉到,那就只能是 getFriendRankData 的错误了
然后控制台中的报错是
之后发现是我的 style/friendRank.js 中忘记定义一些 class 了
每一个标签的长宽都是相等的,但是渲染出来的效果像是被拉伸了
被拉伸的效果:
我在微信开放社区上提问了,但是我觉得这里应该没有活人……
developers.weixin.qq.com/community/m…
渲染样式:
open-data\render\styles\friendRank.js
/**
* 定义一些样式,方便在 tpls 文件夹中的定义内容的文件来使用
*/
export default function getStyle(data) {
// let data = { width: 356, height: 400 };
let itemHeight = data.height / 2 / 3;
let itemWidth = Math.ceil(data.width * 0.94);
return {
// 容器
container: {
width: data.width,
height: data.height,
borderRadius: 12,
paddingLeft: data.width * 0.03,
paddingRight: data.width * 0.03,
},
// 每一行排名的背景
rankBg: {
position: 'absolute',
top: 0,
left: 0,
width: itemWidth,
height: itemHeight,
},
// 列表中一个排名对象的大小
listItem: {
position: 'relative',
width: itemWidth,
height: itemHeight,
flexDirection: 'row',
alignItems: 'center',
marginTop: 2,
},
// 每个列表对象的排名
listItemRank: {
position: 'relative',
width: itemHeight * 0.8,
height: itemHeight * 0.8,
fontSize: 12,
fontWeight: 'bold',
textAlign: 'center',
//marginTop: itemHeight*0.4,
//marginBottom: itemHeight*0.1,
},
// 每个列表对象的头像
listItemAvatar: {
position: 'relative',
width: itemHeight * 0.8 ,
height: itemHeight * 0.8,
//borderRadius: 15,
//borderWidth: 5,
borderColor: 'black',
},
// 每个列表对象的名称
listItemName: {
position: 'relative',
// 除去其他 4 个元素,剩下的长度就是名字的长度
// 但是每一行排名的背景图本身占据的范围不一定是 itemWidth
// 所以这个列表对象的名称的 width 还要稍微缩小一点
width: (itemWidth - 4 * itemHeight * 0.8) * 0.8,
height: itemHeight*0.5,
fontSize: 12,
//marginLeft: 30,
//marginTop: itemHeight*0.4,
//marginBottom: itemHeight*0.1,
},
// 超级动物的数量
listItemSuperAnimalCount: {
position: 'relative',
width: itemHeight * 0.8,
height: itemHeight * 0.8,
fontSize: 12,
fontWeight: 'bold',
textAlign: 'right',
//marginRight: 30,
//marginTop: itemHeight*0.4,
//marginBottom: itemHeight*0.1,
},
// 普通动物的数量
listItemNormalAnimalCount: {
position: 'relative',
width: itemHeight * 0.8,
height: itemHeight * 0.8,
fontSize: 12,
fontWeight: 'bold',
textAlign: 'right',
//marginRight: 30,
//marginTop: itemHeight*0.4,
//marginBottom: itemHeight*0.1,
},
};
}
h5 转微信渲染引擎
open-data\render\tpls\friendRank.js
export default function anonymous(it) {
let out = '<view class="container" id="main"> <view class="rankList"> <scrollview class="list" scrollY="true"> ';
const arr1 = it.data;
if (arr1) {
let item;
let index = -1;
const l1 = arr1.length - 1;
while (index < l1) {
item = arr1[(index += 1)];
out += ` <view class="listItem">
<image src="open-data/render/image/rankBg.png" class="rankBg"></image>
<text class="listItemRank" value="${index +1 }"></text>
<image class="listItemAvatar" src="${item.avatarUrl}"></image>
<text class="listItemName" value="${item.nickname}"></text>
<text class="listItemSuperAnimalCount" value="${item.superAnimCount || 0}"></text>
<text class="listItemNormalAnimalCount" value="${item.normalAnimCount || 0}"></text>
</view> `;
}
}
out += ' </scrollview> </view></view>';
return out;
}
这代码里真不知道哪错了
排行榜使用的 RawImage 在场景树中的排布如图
RawImage 的每一个父节点我都看过了,Scale 都是 1
RawImage 的父节点中包含的与 Canvas 调整相关的组件也就是一个 Canvas Scaler,这个是用来适配不同尺寸的设备的,我也尝试禁用这个组件了,禁用了之后还是有这个被拉伸的问题
就算是用代码打印一遍
void Start()
{
RawImage rawImage = GetComponent<RawImage>();
Debug.Log(string.Format("rawImage.uvRect = {0}", rawImage.uvRect));
Debug.Log(string.Format("rawImage.transform.localScale = {0}", rawImage.transform.localScale));
}
得到的也都是 1
放在 Update 中也是一样
最后发现问题在于获取组件的这一步
我的 CanvasScaler 放在父级,但是官方 Demo 的应该是就放在跟 RawImage 一起,我应该用 GetComponentInParent<CanvasScaler>() 的
void ShowOpenData()
{
//
// 注意这里传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 放在 RawImage 的哪里
// 官方 Demo 的是放在跟 RawImage 我的 CanvasScaler 在 RawImage 的父级
// CanvasScaler scaler = gameObject.GetComponent<CanvasScaler>();
CanvasScaler scaler = gameObject.GetComponentInParent<CanvasScaler>();
var referenceResolution = scaler.referenceResolution;
var p = rankListBody.transform.position;
var rect = rankListBody.rectTransform.rect;
WX.ShowOpenData(rankListBody.texture, (int)p.x, Screen.height - (int)p.y,
(int)((Screen.width / referenceResolution.x) * rect.width),
(int)((Screen.width / referenceResolution.x) * rect.height));
}
最终得到的一个感觉还可以的样式:
/**
* 定义一些样式,方便在 tpls 文件夹中的定义内容的文件来使用
*/
export default function getStyle(data) {
// let data = { width: 356, height: 400 };
let itemHeight = data.height / 2 / 3;
let itemWidth = Math.ceil(data.width * 0.94);
return {
// 容器
container: {
width: data.width,
height: data.height,
borderRadius: 12,
paddingLeft: data.width * 0.03,
paddingRight: data.width * 0.03,
},
// 每一行排名的背景
rankBg: {
position: 'absolute',
top: 0,
left: 0,
width: itemWidth,
height: itemHeight,
},
// 列表中一个排名对象的大小
listItem: {
position: 'relative',
width: itemWidth,
height: itemHeight,
flexDirection: 'row',
alignItems: 'center',
marginTop: 2,
},
// 每个列表对象的排名
listItemRank: {
position: 'relative',
width: itemHeight * 0.7,
height: itemHeight * 0.7,
fontSize: 56,
textAlign: 'center',
//marginRight: 30,
marginTop: itemHeight*0.4,
marginBottom: itemHeight*0.1,
},
// 每个列表对象的头像
listItemAvatar: {
position: 'relative',
width: itemHeight * 0.7 ,
height: itemHeight * 0.7,
borderRadius: 15,
borderWidth: 5,
borderColor: 'black',
},
// 每个列表对象的名称
listItemName: {
position: 'relative',
// 除去其他 4 个元素,剩下的长度就是名字的长度
// 但是每一行排名的背景图本身占据的范围不一定是 itemWidth
// 所以这个列表对象的名称的 width 还要稍微缩小一点
width: itemWidth - 4 * itemHeight * 0.7,
height: itemHeight*0.5,
fontSize: 56,
marginLeft: 30,
marginTop: itemHeight*0.4,
marginBottom: itemHeight*0.1,
},
// 超级动物的数量
listItemSuperAnimalCount: {
position: 'absolute',
right: itemHeight * 0.7, // 距离最右一个单位
width: itemHeight * 0.7,
height: itemHeight * 0.7,
fontSize: 56,
textAlign: 'center',
//marginRight: 30,
marginTop: itemHeight*0.4,
marginBottom: itemHeight*0.1,
},
// 普通动物的数量
listItemNormalAnimalCount: {
position: 'absolute',
right: 0, // 距离最右 0 个单位
width: itemHeight * 0.7,
height: itemHeight * 0.7,
fontSize: 56,
textAlign: 'center',
//marginRight: 30,
marginTop: itemHeight*0.4,
marginBottom: itemHeight*0.1,
},
};
}
效果: