- [Cocos Creator] 微信小游戏接入好友排行榜
前言 对于一个微信小游戏来说,好友排行榜绝对是必不可少的功能,能一定程度上增加玩家的战斗力和活跃度,实实在在地增加小游戏的曝光量。 这篇文章皮皮将讲解 如何给小游戏项目加入微信好友排行榜功能 ~ 不吹不黑,这绝对是新手开发者的福音!不接受任何反驳! 前排提示:文章中的 排行榜子域项目 已经上传至我的开源主页,甚至 改都不用改,接上项目就能用 ,链接在底部传送门~ 正文 微信开放数据域 要让小游戏接入微信好友排行榜功能,我们必须先了解下什么是 开放数据域 ,来看看 Cocos 官方文档中的解释: [图片] 也就是说,我们的小游戏项目想要加入好友排行榜功能,就需要 单独再创建一个子项目专门用来展示好友排行榜 ,并且 只有在子项目中才可以调用微信提供的数据操作 API 。 主域(主项目) 首先我们需要在主项目中 增加一个显示排行榜的按钮 并 搭建排行榜的 UI 框架 。 我们应该尽可能将 UI 部分放在主域中展示。 必要的细节部分才在子域(子项目)中展示,保证效果的前提下越少越好啦。 [图片] 注意到 wxSubContextView (叫啥名随你啦)节点了吗? 这个节点上有一个 WXSubContentView 组件,有了它,这个节点就会成为子域的容器。也就是说,子域的内容会显示在这个节点上,所以 子域的大小必须和这个节点一致 。 另外需要注意的是,容器节点还需要一个空的 Sprite 组件来渲染子域的内容。 [图片] 然后来写一下排行榜的控制脚本,在主域中我们可以通过 wx.postMessage 函数向子域发送信息,获取排行榜或者设置玩家的分数。 [代码]const { ccclass, property } = cc._decorator; @ccclass export default class RankPanel extends cc.Component { @property(cc.Node) private main: cc.Node = null; @property(cc.Node) private closeBtnNode: cc.Node = null; private static instance: RankPanel = null; protected onLoad() { RankPanel.instance = this; this.closeBtnNode.on('touchend', RankPanel.hide, this); } protected onDestroy() { this.closeBtnNode.off('touchend', RankPanel.hide, this); } public static show() { this.instance.main.active = true; this.getRank(); } public static hide() { this.instance.main.active = false; } /** * 设置用户的分数 * @param value */ public static setScore(value: number) { wx.postMessage({ event: 'setScore', score: value }); } /** * 获取排行榜 */ public static getRank() { wx.postMessage({ event: 'getRank' }); } } [代码] 子域(子项目) 新建一个项目作为我们的子域,关于子域我们需要 注意 以下两点: 将子域的 Canvas 节点的 Canvas 组件的设计分辨率调整为我们主域的容器节点的大小 ,否则子域内容会被缩放,导致运行效果与预期不一致! 这里还要提一点,子域的 分辨率越低性能越高 ,反之 分辨率越高性能越低但是显示效果更好 。 [图片] 将子域的 Main Camera 节点的 Camera 组件的 Background Color 属性的不透明度(Alpha)设为 0 ,否则运行时子域内容就是一片漆黑! [图片] 接下来就是和普通场景一样制作我们需要的好友列表 UI: 用 ScrollView 组件来展示我们的排行榜,并制作展示好友信息的预制体 item 。 [图片] 编写 RankItem 组件,添加到 item 预制体上,主要用来设置好友头像、昵称和分数。 [代码]const { ccclass, property } = cc._decorator; @ccclass export default class RankItem extends cc.Component { @property(cc.Label) private rankingLabel: cc.Label = null; @property(cc.Sprite) private avatarSprite: cc.Sprite = null; @property(cc.Label) private nicknameLabel: cc.Label = null; @property(cc.Label) private scoreLabel: cc.Label = null; /** * 设置展示的信息 * @param ranking 排名 * @param user 用户数据 */ public set(ranking: number, user: UserGameData) { this.rankingLabel.string = ranking.toString(); this.nicknameLabel.string = user.nickname; this.scoreLabel.string = user.KVDataList[0].value.toString(); this.updateAvatar(user.avatarUrl); } /** * 更新头像 * @param url 头像链接 */ private updateAvatar(url: string) { let image = wx.createImage(); image.onload = () => { let texture = new cc.Texture2D(); texture.initWithElement(image); texture.handleLoadedTexture(); this.avatarSprite.spriteFrame = new cc.SpriteFrame(texture); }; image.src = url; } } [代码] 编写 Rank 组件,包含主要的设置、获取数据和更新好友信息展示的逻辑;在子域中,我们使用 wx.onMessage 来监听主域发来的消息。 [代码]import RankItem from "./RankItem"; const { ccclass, property } = cc._decorator; @ccclass export default class Rank extends cc.Component { @property(cc.Node) private content: cc.Node = null; @property(cc.Prefab) private itemPrefab: cc.Prefab = null; @property(cc.Node) private loading: cc.Node = null; protected onLoad() { if (cc.sys.platform !== cc.sys.WECHAT_GAME_SUB) return; // 监听来自主域的消息 wx.onMessage((msg: any) => this.onMessage(msg)); } /** * 消息回调 * @param msg 消息 */ private onMessage(msg: any) { switch (msg.event) { case 'setScore': this.setScore(msg.score); break; case 'getRank': this.getRank(); break; } } /** * 获取玩家分数 */ private getScore(): Promise<number> { return new Promise(resolve => { console.log('[getScore]'); wx.getUserCloudStorage({ keyList: ['score'], success: (res: UserGameData) => { console.log('[getScore]', 'success', res); resolve(res.KVDataList[0] ? parseInt(res.KVDataList[0].value) : 0); }, fail: () => { console.log('[getScore]', 'fail'); resolve(-1); } }); }); } /** * 设置玩家分数 * @param value 分数 */ private async setScore(value: number) { console.log('[setScore]', value); let oldScore = await this.getScore(); if (oldScore === -1) return; if (value > oldScore) { wx.setUserCloudStorage({ KVDataList: [{ key: 'score', value: value.toString() }], success: () => { console.log('[setScore]', 'success'); }, fail: () => { console.log('[setScore]', 'fail'); } }); } } /** * 获取排行榜 */ private async getRank() { console.log('[getRank]'); // 显示加载动画 this.showLoading(); // 调用微信的函数 await new Promise(resolve => { wx.getFriendCloudStorage({ keyList: ['score'], success: (res: any) => { console.log('[getRank]', 'success', res); // 对数据进行排序 res.data.sort((a: UserGameData, b: UserGameData) => { if (a.KVDataList.length === 0 && b.KVDataList.length === 0) return 0; if (a.KVDataList.length === 0) return 1; if (b.KVDataList.length === 0) return -1; return parseInt(b.KVDataList[0].value) - parseInt(a.KVDataList[0].value); }); // 排序之后进行展示 this.updateRankList(res.data); resolve(); }, fail: (res: any) => { console.log('[getRank]', 'fail'); resolve(); } }); }); // 关闭加载动画 this.hideLoading(); } /** * 更新好友排行 * @param data 数据 */ private updateRankList(data: UserGameData[]) { let count = Math.max(data.length, this.content.childrenCount); for (let i = 0; i < count; i++) { if (data[i] && this.content.children[i]) { // 已存在节点,更新并展示 this.content.children[i].active = true; this.content.children[i].getComponent(RankItem).set(i + 1, data[i]); } else if (data[i] && !this.content.children[i]) { // 节点不足,再实例化一个,更新信息 let node = cc.instantiate(this.itemPrefab); node.setParent(this.content); node.getComponent(RankItem).set(i + 1, data[i]); } else { // 节点多了,关掉吧 this.content.children[i].active = false; } } } /** * 显示加载动画 */ private showLoading() { this.loading.active = true; } /** * 关闭加载动画 */ private hideLoading() { this.loading.active = false; } } [代码] 点击编辑器左上方工具栏的 [ 项目 --> 项目设置 --> 模块设置 ] , 取消勾选我们子域中没有用到的组件并保存 ,这一步的目的是 减少子域的包体大小 。 [图片] 打包运行 首先是我们的 主域 (主项目):打开 构建发布 面板,设置好参数后填写你的微信小游戏 appid ,然后将下方的 开放数据域目录设置为你的子域项目名 ,然后进行构建。 [图片] 然后是 子域 (子项目):同样是打开 构建发布 面板,将 发布平台 设置为 微信小游戏开放数据域 , 发布路径 设置为 **我们主项目的导出目录(一般为 ${ 你的项目目录/build/wechatgame/ }) **,然后进行构建。 [图片] 最后,主项目和子项目都构建完成后,我们用 微信开发者工具运行我们的主项目 ,点击加载排行榜,可以看到我们已经成功加载了好友排行榜啦! 如果你那里没有显示任何东西的话,那说明你还没有上传过分数~ 另外如果微信开发者工具报错“[GameOpenDataContext] 子域只支持使用 2D 渲染模式”,不用担心,这是正常情况,你的代码没问题,不必理会。 [图片] 补充 最后再补充一点,wx 是微信的内置全局变量,而在 Cocos Creator 中不存在 wx 这个全局变量,所以在 VSCode 中调用 wx 的函数会报错。 所以我在主项目和子项目中都添加了一个 wx.d.ts 声明文件,来表明 wx 以及其函数的存在,VSCode 就不会再报错了,而且还有智能提示! 我的开源游戏脚手架 eazax-ccc 就包含此文件,另外我的开源主页中也有单独的 wx.d.ts 仓库,下面是 wx.d.ts 文件的内容: [代码]// 以下为 wx.d.ts 文件的内容 /** * 微信的命名空间 */ declare namespace wx { /** * 打开另一个小程序。 */ export function navigateToMiniProgram(object: object): void; /** * 提前向用户发起授权请求。调用后会立刻弹窗询问用户是否同意授权小程序使用某项功能或获取用户的某些数据,但不会实际调用对应接口。如果用户之前已经同意授权,则不会出现弹窗,直接返回成功。 */ export function authorize(object: object): void; /** * 获取用户信息。 */ export function getUserInfo(object: object): void; /** * 向开放数据域发送消息。 */ export function postMessage(object: object): void; /** * 监听主域发送的消息。 */ export function onMessage(callback: Function): void; /** * 对用户托管数据进行写数据操作。允许同时写多组 KV 数据。 */ export function setUserCloudStorage(object: object): void; /** * 获取当前用户托管数据当中对应 key 的数据。该接口只可在开放数据域下使用。 */ export function getUserCloudStorage(object: object): void; /** * 删除用户托管数据当中对应 key 的数据。 */ export function removeUserCloudStorage(object: object): void; /** * 监听成功修改好友的互动型托管数据事件,该接口在游戏主域使用。 */ export function onInteractiveStorageModified(callback: Function): void; /** * 修改好友的互动型托管数据,该接口只可在开放数据域下使用。 */ export function modifyFriendInteractiveStorage(object: object): void; /** * 获取当前用户互动型托管数据对应 key 的数据。 */ export function getUserInteractiveStorage(object: object): void; /** * 获取可能对游戏感兴趣的未注册的好友名单。每次调用最多可获得 5 个好友,此接口只能在开放数据域中使用。 */ export function getPotentialFriendList(object: object): void; /** * 获取群信息。小游戏通过群分享卡片打开的情况下才可以调用。该接口只可在开放数据域下使用。 */ export function getGroupInfo(object: object): void; /** * 获取群同玩成员的游戏数据。小游戏通过群分享卡片打开的情况下才可以调用。该接口只可在开放数据域下使用。 */ export function getGroupCloudStorage(object: object): void; /** * 拉取当前用户所有同玩好友的托管数据。该接口只可在开放数据域下使用。 */ export function getFriendCloudStorage(object: object): void; /** * 给指定的好友分享游戏信息,该接口只可在开放数据域下使用。接收者打开之后,可以用 wx.modifyFriendInteractiveStorage 传入参数 quiet=true 发起一次无需弹框确认的好友互动。 */ export function shareMessageToFriend(object: object): void; /** * 获取主域和开放数据域共享的 sharedCanvas。只有开放数据域能调用。 */ export function getSharedCanvas(): any; /** * 获取开放数据域。 */ export function getOpenDataContext(): any; /** * 创建一个图片对象。 */ export function createImage(): any; } /** * 托管数据 */ declare type UserGameData = { avatarUrl: string; nickname: string; openid: string; KVDataList: KVData[]; } /** * 托管的 KV 数据 */ declare type KVData = { key: string; value: string; } /** * 用户信息 */ declare type FriendInfo = { avatarUrl: string; nickname: string; openid: string; } [代码] 传送门 开源主页:陈皮皮 Eazax-CCC 游戏开发脚手架 微信好友排行榜通用模板 微信小游戏函数与类型声明(wx.d.ts) 更多分享 多平台通用的屏幕分辨率适配方案 围绕物体旋转的方案以及现成的组件 一个全能的挖孔 Shader 一个开源的自动代码混淆插件 结束语 以上皆为陈皮皮的个人观点,小生不才,文采不佳,如果写得不好还请各位多多包涵。如果有哪些地方说的不对,还请各位指出,希望与大家共同进步。 接下来我会持续分享自己所学的知识与见解,欢迎各位关注本公众号。 我们,下次见! 公众号 文弱书生陈皮皮 Input and output. [图片]
2020-05-15 - popButtons
本月份的组件分享,事件效果由 wxs事件完成,注释在代码片段里有。 按钮可拖动,子菜单上没有点击事件,是根据touchend位置来判断点击的谁。有bug欢迎反馈 代码片段:https://developers.weixin.qq.com/s/9BNYnhmG7tcl 社区这会儿效果图gif上传不了。。不是我的错 图标iconfont下的,没版权问题吧。。
2019-10-21 - Skyline | 快速搞定复杂的分享海报
在小程序中生成海报是一种非常有效的推广方式 用户可以使用小程序的过程中生成小程序海报并分享给他人 通过海报的形式,用户可以直观地了解产品或服务的特点和优势 [图片] 常见绘制海报方式 目前,小程序海报有两种常见的实现方式: · canvas 绘制海报 · 服务端绘制海报 这两种方式各有千秋 canvas 绘制海报使用 canvas 绘制海报主要有以下几个步骤 1、创建 [代码]canvasContext[代码] 2、获取网络图片的本地路径 3、绘制图片、文字等到 [代码]canvas[代码] 4、调用 [代码]wx.canvasToTempFilePath[代码] 导出图片 尽管 canvas 绘制功能强大,但实际使用中,这些操作看似简单,但调试起来却比较麻烦 而且面对一些复杂的排版时,使用 canvas 绘制相较于使用 CSS 绘制来说困难许多 除此之外,canvas 的宽高有最大限制,超出限制则会绘制空白 服务端绘制 小程序也可以通过调用服务端接口,将需要生成海报的数据传递给服务端, 由服务端使用 Canvas API 等第三方库来生成图片。 然而,这种绘制方式需要走网络请求,如果量大会给服务器带来一定的成本压力。 此外,对于复杂排版的实现,使用 Canvas 绘制也有一定的难度。 尽管小程序海报虽然好用,但是当遇到要求比较高的设计稿需要还原海报时,对小程序开发者来说是一个十分让人头疼的问题 考虑到海报在小程序中使用的广泛性,我们把开发者的烦恼交给官方来处理~ 小程序官方推出了 [代码]snapshot[代码] 组件,可以直接将小程序 wxml 导出图片。 snapshot 生成海报 当使用 canvas 或 服务端绘制海报遇到复杂排版时,如 圆角、百分比、自定义字体 等等,实现比较困难。 但是使用 wxml 实现却很简单 👇 下面的例子我们使用 wxml 实现海报 <view class="snapshot-box"> <view class="poster-container"> <view class="poster-header"> <image /> ... </view> <view class="description"> ... </view> <view class="footer"> ... </view> </view> </view> [图片] 接着,我们就可以导出海报啦,使用非常简单: 1、用 [代码]snapshot[代码] 组件包裹海报的 wxml 2、调用 [代码]takeSnapshot[代码] 获取图片数据 3、调用 [代码]fs.writeFileSync[代码] 将海报数据写入本地文件 4、调用 [代码]wx.saveImageToPhotosAlbum[代码] 将海报保存到本地 <snapshot id="view"> <!-- 这里是要海报的 wxml --> </snapshot> <button bindtap="tap">保存海报</button> tap() { this.createSelectorQuery().select("#view") .node().exec(res => { const node = res[0].node // 保存海报 node.takeSnapshot({ type: 'arraybuffer', format: 'png', success: (res) => { const f = `${wx.env.USER_DATA_PATH}/hello.png` const fs = wx.getFileSystemManager(); // 将海报数据写入本地文件 fs.writeFileSync(f, res.data, 'binary') this.setData({ img: f }) // 把海报图片保存到本地 wx.saveImageToPhotosAlbum({ filePath: f }) } }) }) } 最后我们来看看使用 [代码]snapshot[代码] 组件生成海报的效果吧~ [图片] 除了普通尺寸分享海报之外,对于 canvas 无法搞定的超长海报,[代码]snapshot[代码] 后续也会支持超长海报的导出~ [图片] 你的小程序也有海报生成需求吗? 赶紧 mark 下这个 代码片段 来接入使用吧~
2023-09-06