官方文檔
微信小遊戲快速上手
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
的必須是舞臺的寬高的等比例縮放,不然在共響域繪製就沒法計算出正確的位置;
可以看出在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];