[Unity] Unity WebGL 转微信小游戏,接入微信排行榜,自定义排行榜样式相关的内容

2,676 阅读6分钟

Unity中如何展示排行榜这类微信关系数据

github.com/wechat-mini…

微信官方的排行榜 Demo

github.com/wechat-mini…

微信自己的 canvas 渲染引擎

wechat-miniprogram.github.io/minigame-ca…

微信自己的 canvas 渲染引擎提供的好友排行榜示例

wechat-miniprogram.github.io/minigame-ca…

排行榜样式相关的文件夹结构

styles 文件夹里面的文件负责定义样式,也就是 class

tpls 文件夹里面的文件负责定义内容,也就是写一些 html 标签,然后标签用到你之前定义的 class

图片.png

排行榜的内容是怎么定义的

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 中定义了 containerflexDirection = 'row'

可以看看它的效果:

图片.png

具体的话,他这里每一个元素都定义了一个 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…

这一部分是好友的排行

图片.png

这一部分是仅展示前 50 再加上自己的排行

图片.png

它使用的这个实时渲染的插件 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

图片.png

可以直接在微信开发者工具的内置 IDE 中修改 js 脚本,保存之后,就会重新编译小游戏

这样就可以立即看到 js 方面的更改,而不用每次都要去 Unity 中导出

style/friendRank.js 中缺少 class 导致的报错

有一个错误是,明明提示的是 请进入设置页允许获取微信朋友信息 但是我在设置里面确实看到了是可以允许获取微信朋友信息的

图片.png

我在 LayoutWithTplAndStyle 这里放了一个 try catch 但是没有捕捉到,那就只能是 getFriendRankData 的错误了

然后控制台中的报错是

图片.png

之后发现是我的 style/friendRank.js 中忘记定义一些 class 了

每一个标签的长宽都是相等的,但是渲染出来的效果像是被拉伸了

被拉伸的效果:

图片.png

我在微信开放社区上提问了,但是我觉得这里应该没有活人……

developers.weixin.qq.com/community/m…

渲染样式:

open-data\render\styles\friendRank.js

/**

 * 定义一些样式,方便在 tpls 文件夹中的定义内容的文件来使用

 */

export default function getStyle(data) {

  // let data = { width356, 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 在场景树中的排布如图

图片.png

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,
    },
  };
}

效果:

图片.png