官方文档
微信小游戏快速上手
egret微信小游戏开发指南
菜鸟|Egret微信小游戏好友排行榜教程
小程序与小游戏获取用户信息接口调整,请开发者注意升级
-
整体的效果如下
微信小游戏整体效果
开放数据域
-
开放数据域的绘制文件中已经拥有一个通过Canvas API绘制的排行榜 ,SharedCanvas 是主域和开放数据域都可以访问的一个离屏画布,原理如下所示。
开放数据域 - egret中的ts类文件是在主域,编译生成微信小游戏文件中会有一个
openDataContext
文件夹里面有一个index.js
文件,这个文件中可以绑定上传积分等信息到微信后台,从微信后台获取用户信息和积分数据,清除微信后台积分数据,以及微信后台获取微信好友排行榜数据;(这个文件中可以获取微信相关数据
) -
index.js
文件还有一个SharedCanvas
,是主域和开放数据域都可以访问的一个离屏画布;主域和开放数据域的分工不同,在主域中创建离屏画布并添加到显示容器中,主域通知共享域去获取微信好友列表信息,然后根据这些信息去绘制排行榜;(主域中创建添加移除销毁离屏画板,并传递分数时间等信息给共享域;共享域接受到信息后经过一系列的数据判断处理后去绘制排行榜
); - 总而言之就微信不会将一些信息给你随便用(主要是好友列表信息),所以只能在该共享域中获取微信好友列表信息;共享域是可以收到主域的信息的,但主域是不可以是收到共享域的信息;所以这个好友排行榜会的绘制只能放在共享域了.
微信登录授权登录,获取微信用户信息,分享小游戏
-
在
egret
中有一个Platform.ts
类,对应编译后微信小游戏中的platform.js
文件;主要用于获取平台数据接口的,包括微信授权登录获取用户信息,分享小游戏给好友,分享小游戏到群; -
Platform.ts
中全部代码//正式版中只有方法声明 declare interface Platform { //获取用户信息 getUserInfo(): Promise<any>; //微信授权登录 login(): Promise<any>; //分享转发,主要用于被动转发 showSharemMenu():Promise<any> //主动转发 shareAppMessage():Promise<any> openDataContext:any } //测试版 class DebugPlatform implements Platform { async getUserInfo() { return { nickName: "username" } } async login() { } async showSharemMenu() { } async shareAppMessage() { } openDataContext } //设置为全局 if (!window.platform) { window.platform = new DebugPlatform(); } declare let platform: Platform; declare interface Window { platform: Platform }
-
platform.js
中全部代码class WxgamePlatform { name = 'wxgame' //登录 login() { return new Promise((resolve, reject) => { wx.login({ success: (res) => { resolve(res) } }) }) } //获取用户信息 getUserInfo() { let windowWidth = wx.getSystemInfoSync().windowWidth; let windowHeight = wx.getSystemInfoSync().windowHeight; return new Promise((resolve, reject) => { let button = wx.createUserInfoButton({ type: 'text', text: '获取用户信息', style: { left: windowWidth/2 - 100, top: windowHeight/2 - 20, width: 200, height: 40, lineHeight: 40, backgroundColor: '#008888', color: '#ffffff', textAlign: 'center', fontSize: 16, borderRadius: 4 } }); //允许按钮 button.onTap((res) => { // button.hide(); var userInfo = res.userInfo; resolve(userInfo); console.log(res); button.destroy(); }); //关闭按钮 button.offTap((res) => { resolve(res); button.destroy(); }); }); } //被动分享 showSharemMenu(){ console.log("微信分享"); return new Promise((resolve,reject) => { //显示当前页面的转发按钮 wx.showShareMenu({ withShareTicket:true, success:(res)=>{ console.log("success", res); }, fail:(res)=>{ console.log("fail", res); }, complete:(res)=>{ console.log("complete",res); } }); //被动转发 wx.onShareAppMessage(() => { return { title: '捷达小飞车', imageUrl: 'openDataContext/assets/icon_first.png' // 图片 URL } }); }); } //主动转发 shareAppMessage() { return new Promise((resolve, reject) => { wx.shareAppMessage({ title: '捷达小飞车', imageUrl: 'openDataContext/assets/icon_first.png' // 图片 URL }) }) } openDataContext = new WxgameOpenDataContext(); } //下面代码自动生成不要动 class WxgameOpenDataContext { createDisplayObject(type, width, height) { const bitmapdata = new egret.BitmapData(sharedCanvas); bitmapdata.$deleteSource = false; const texture = new egret.Texture(); texture._setBitmapData(bitmapdata); const bitmap = new egret.Bitmap(texture); bitmap.width = width; bitmap.height = height; if (egret.Capabilities.renderMode == "webgl") { const renderContext = egret.wxgame.WebGLRenderContext.getInstance(); const context = renderContext.context; ////需要用到最新的微信版本 ////调用其接口WebGLRenderingContext.wxBindCanvasTexture(number texture, Canvas canvas) ////如果没有该接口,会进行如下处理,保证画面渲染正确,但会占用内存。 if (!context.wxBindCanvasTexture) { egret.startTick((timeStarmp) => { egret.WebGLUtils.deleteWebGLTexture(bitmapdata.webGLTexture); bitmapdata.webGLTexture = null; return false; }, this); } } return bitmap; } postMessage(data) { const openDataContext = wx.getOpenDataContext(); openDataContext.postMessage(data); } } window.platform = new WxgamePlatform();
- 小游戏启动时要微信授权登录获取用户信息,以及被动分享,在
Main.ts
中runGame
方法中添加:
private async runGame() { //微信授权登录 await platform.login() //如果已经授权登录就跳过授权登录步骤 if(!egret.localStorage.getItem("nickName")||!egret.localStorage.getItem("avatarUrl")){ await this.loginAndGetUserInfo(); } await this.loadResource(); this.createGameScene(); //游戏页面都加载完成后展示分享按钮 await platform.showSharemMenu(); const result = await RES.getResAsync("description_json") this.startAnimation(result); } //授权登录获取用户信息 private async loginAndGetUserInfo() { const userInfo = await platform.getUserInfo();//微信授权登录 console.log("userInfo",userInfo); if(userInfo){//名称和图片保存在本地 egret.localStorage.setItem("nickName",userInfo.nickName); egret.localStorage.setItem("avatarUrl",userInfo.avatarUrl); } }
微信登录授权获取用户信息效果如下:
微信授权登录获取用户信息
微信被动分享需要注意的是platform.showSharemMenu()
必须在创建游戏场景后调用,否则会卡住;添加后会模拟器出现转发
和取消
选项效果如下:
在这里插入图片描述 - 小游戏启动时要微信授权登录获取用户信息,以及被动分享,在
-
微信主动转发分享:即我们自己添加一个分享按钮点击后分享给好友,代码如下
//转发按钮点击事件 this.forwardBtn.addEventListener(egret.TouchEvent.TOUCH_TAP,()=>{ //主动转发事件 platform.shareAppMessage(); },this);
微信排行榜
- 在共享获取微信相关信息和绘制排行榜,即在
openDataContext
文件夹的index.js
文件中进行操作
微信排行榜数据获取
- 获取和写入用户托管数据到微信后台:即游戏结束时上传分数到微信后台,但要先判断是最大分数时才上传;
wx.getUserCloudStorage
方法获取用户托管信息,wx.setUserCloudStorage
写入用户信息,相关代码,//获取当前用户托管数据当中对应 key 的数据。该接口只可在开放数据域下使用,比较当前分数是否比存储的分数大,如果大就更新数据 function getUserCloudData(data) { nickName = data.nickName; myCurrentScore = data.wxgame.score; console.log("当前分数", myCurrentScore); wx.getUserCloudStorage({ keyList: ["gameScoreData"], success: (re) => { console.log("[wx]success", re); let userData = re.KVDataList; console.log("userData", userData); myShowRank = ShowRanking.ShowLite; if (userData.length > 0) { let value = userData[0].value; let json = JSON.parse(value); console.log("json", json); maxScore = json.wxgame.score ? json.wxgame.score : 0; console.log("maxScore", maxScore); console.log("最大分数", maxScore); if (maxScore < myCurrentScore) {//如果当前分数大于最大分数则更新最大分数 updateUserDataToCloud(data); }else { if(totalGroup.length <= 0){ preloadFriendData(); } else { console.log("有数据并且不用更新数据,就直接绘制"); renderDirty = true;//重绘标记 requestAnimationFrameID = requestAnimationFrame(loop);//每一帧绘制 } } } else { //第一次为空 updateUserDataToCloud(data); } }, fail: (re) => { console.log("[wx]fail", re); }, complete: (re) => { console.log("[wx]complete", re); } }) } //对用户托管数据进行写数据操作。允许同时写多组 KV 数据。上传分数等信息到微信云 function updateUserDataToCloud(data) { console.log("上传分数", data); let userKVData = { key: "gameScoreData", value: JSON.stringify(data) } console.log(userKVData); wx.setUserCloudStorage({ KVDataList: [userKVData], success: (re) => { console.log("[wx]success", re); preloadFriendData(); }, fail: (re) => { console.log("[wx]fail", re); }, complete: (re) => { console.log("[wx]complete", re); } }); }
- 获取好友列表信息
wx.getFriendCloudStorage
;下面是获取用户信息,并对用户图像进行预加载,然后根据分数排序相关代码//获取好友排行榜数据信息 function preloadFriendData() { wx.getFriendCloudStorage({ keyList: ["gameScoreData"], success: (re) => { console.log("[wx]success", re); let dataArr = re.data; var tempArr = []; let imagUrl = []; console.log("dataArr", dataArr); for (var i = 0; i < dataArr.length; i++) { let objc = dataArr[i]; let oj = {}; oj.key = i; oj.name = objc.nickname; oj.url = objc.avatarUrl oj.score = 0; if (objc.KVDataList.length > 0) { let userSetData = objc.KVDataList[0]; let score = JSON.parse(userSetData.value).wxgame.score; console.log("分数", score); oj.score = score; } imagUrl.push(objc.avatarUrl); tempArr.push(oj); } console.log("tempArr", tempArr); totalGroup = null; totalGroup = tempArr; console.log("totalGroup1", totalGroup); if (imagUrl.length > 0) { preloadAvatarUrl(imagUrl); } }, fail: (re) => { console.log("[wx]fail", re); }, complete: (re) => { console.log("[wx]complete", re); } }); } //先预加载图片资源 function preloadAvatarUrl(avatarUrlList) { console.log("imgs", avatarUrlList); let preloaded = 0; let assetsAvatar = []; for (var j = 0; j < avatarUrlList.length; j++) { const img = wx.createImage(); img.onload = () => { preloaded++; if (preloaded == avatarUrlList.length) { console.log("所有头像加载完成"); console.log("图片", assetsAvatar); for (var i = 0; i < totalGroup.length; i++) { let objc = totalGroup[i]; objc.img = assetsAvatar[i]; } let data = totalGroup.sort(createComprisonFunction("score", false)); for (var i = 0; i < totalGroup.length; i++) { let objc = totalGroup[i]; objc.key = i + 1; } totalGroup = data; console.log("totalGroup2", totalGroup); renderDirty = true;//重绘标记 requestAnimationFrameID = requestAnimationFrame(loop);//每一帧绘制 } } img.src = avatarUrlList[j]; assetsAvatar.push(img); } } //对象数组,根据对象的key进行排序 function createComprisonFunction(propertyName, isSequence) { //true为顺序,false为逆序 return function (object1, object2) { var value1 = object1[propertyName]; var value2 = object2[propertyName]; if (isSequence == true) { //顺序排序 if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } } else { if (value1 < value2) { return 1; } else if (value1 > value2) { return -1; } else { return 0; } } } }
微信好友排行榜绘制
- 游戏结束主域创建离屏画布,并添加显示;官方示例该画布占满整个舞台,但其实可以是自定义一个
group
,添加到group
中;但这group
的必须是舞台的宽高的等比例缩放,不然在共响域绘制就没法计算出正确的位置;
添加离屏画布的group
可以看出在exml文件中,虽然我只需要在白色背景处绘制排行榜,但是由于会变形,真正要添加离屏画布的group比较长;
在响应的ts文件中创建和添加离屏画布this.gameOverBitmap = platform.openDataContext.createDisplayObject(null, this.gameOverGroup.width, this.gameOverGroup.height); this.gameOverGroup.addChild(this.gameOverBitmap);
主域名发送消息,共享域接受消息
- 游戏结束主域发送分数等信息给共享域;共享域监听接受到消息后进行一系列的数据处理,排行榜绘制工作;
- 游戏结束够主域创建添加离屏画布,发送分数给子域;
//游戏结束绘制分数等信息 private gameOverGroup: eui.Group; // private gameOVerMask: egret.Shape; private gameOverBitmap: egret.Bitmap; //上传分数到微信 private upLoadMyScore() { egret.log("gameOverGroup",this.gameOverGroup); egret.log("gameOverGroup.width",this.gameOverGroup.width); if(this.gameOverGroup){ //gameOverBitmap 宽高比必须是stage的宽高比,否则变形 this.gameOverBitmap = platform.openDataContext.createDisplayObject(null, this.gameOverGroup.width, this.gameOverGroup.height); this.gameOverGroup.addChild(this.gameOverBitmap); } //发送消息给子域 platform.openDataContext.postMessage({ wxgame: { score:this.score, update_time:new Date().getTime(), }, nickName: egret.localStorage.getItem("nickName"), width:this.gameOverBitmap.width, height:this.gameOverBitmap.height, command: "updateMyScore" }); }
- 共享域名监听接受消息
/** * 增加来自主域的监听函数 */ function addOpenDataContextListener() { console.log('增加监听函数') wx.onMessage((data) => { console.log(data); if (data.command == 'open') {//打开微信好友排行榜 if (!createScene()) return; myShowRank = ShowRanking.ShowAll; if (totalGroup.length > 0) { renderDirty = true;//重绘标记 // requestAnimationFrameID = requestAnimationFrame(loop);//每一帧绘制 } else { preloadFriendData(); } } else if (data.command == 'goBack' && requestAnimationFrameID) { console.log("goBack"); //返回时重新绘制简易排行榜 myShowRank = ShowRanking.ShowLite; renderDirty = true;//重绘标记 } else if (data.command == 'loadRes' && !hasLoadRes) { /** * 加载资源函数 * 只需要加载一次 */ // console.log('加载资源') preloadAssets(); } else if (data.command == "updateMyScore") {//更新用户信息 if (!createScene()) return; console.log("data", data); getUserCloudData(data); } else if (data.command == "loadLastPage") {//加载上一页 if (page > 0) { buttonClick(0); } } else if (data.command == "loadNextPage") {//加载下一页 //在next按钮的范围内 if ((page + 1) * perPageMaxNum < totalGroup.length) { buttonClick(1); } } else if (data.command == "startPlayGame"){ console.log("startPlayGame"); //开始游戏,停止进行循环绘图 cancelAnimationFrame(requestAnimationFrameID); requestAnimationFrameID = null } }); }
效果如下:
微信排行榜
其他注意事项
- 展示排行榜时报错
gameSubContextThirdScriptError Cannot read property 'width' of undefined;at requestAnimationFrame callback function TypeError: Cannot read property 'width' of undefined
时必须在Main.ts
加载资源时通知共享域预加载图片资源,及调用platform.openDataContext.postMessage({command:'loadRes'});
.private async loadResource() { try { const loadingView = new LoadingUI(); this.stage.addChild(loadingView); await RES.loadConfig("resource/default.res.json", "resource/"); await this.loadTheme(); await RES.loadGroup("preload", 0, loadingView);//第三个参数只要实现RES.PromiseTaskReporter协议的onProgress方法既可以 this.stage.removeChild(loadingView); //添加一行代码:加载排行榜资源,否则在展示排行榜时报错gameSubContextThirdScriptError Cannot read property 'width' of undefined;at requestAnimationFrame callback function TypeError: Cannot read property 'width' of undefined platform.openDataContext.postMessage({command:'loadRes'}); } catch (e) { console.error(e); } }
- canvas画布的大小是舞台大小,但实际上是有所缩放,所以我们需要通过比例系数计算真实的大小以方便绘制;
//500x888 //画板区域 //500x604 //绘制区域 //500 x 255 蓝色图片 // let scale = 500/640; let topBackGroundHeight = 255/888*sharedCanvas.height; //设置图尺寸乘以下面宽度放大的倍数就是真实尺寸 let widthScale = sharedCanvas.width / 640; fontScale = sharedCanvas.height/1136;
-
loop
方法是一个递归调用,开启会一直循环调用,只要renderDirty
设置为true
就会绘制图像
所以在退出排行榜重新开始游戏时就停止循环绘制调用/** * 循环函数 * 每帧判断一下是否需要渲染 * 如果被标脏,则重新渲染 */ function loop() { if (renderDirty) { context.setTransform(1, 0, 0, 1, 0, 0); context.clearRect(0, 0, sharedCanvas.width, sharedCanvas.height); if (myShowRank == ShowRanking.ShowLite) { console.log("绘制精简版排行榜"); drawLiteRankPanel(); } else if (myShowRank == ShowRanking.ShowAll) { console.log("绘制全部排行榜"); drawRankPanel(); } renderDirty = false; } // console.log("递归死循环调用"); requestAnimationFrameID = requestAnimationFrame(loop); }
//开始游戏,停止进行循环绘图 cancelAnimationFrame(requestAnimationFrameID); requestAnimationFrameID = null
- 开始绘制调用
requestAnimationFrame(loop)
调用一次即可,renderDirty
设置为true
就会安装canvas去绘制,所以在涉及到绘制界面刷新改变时就将renderDirty
设置为true
即可. - 不管是本地图片资源还是网络图片资源在显示之前需要预加载,否则无法显示;微信提供了相关方法
//创建图片容器 const img = wx.createImage(); //图片加载完成时回调 img.onload = () => { } //本地图片资源通过图片传入图片路径,网络图片资源传入图片url img.src = assetsUrl[asset];
网友评论