CocosCreator實現微信排行榜
- 2020 年 6 月 15 日
- 筆記
- javascript, 前端
1. 概述
不管是在現實生活還是當今遊戲中,各式各樣的排名層出不窮。如果我們做好一款遊戲,卻沒有實現排行榜,一定是不完美的。排行榜不僅是玩家了解自己實力的途徑,也是遊戲運營刺激用戶留存的一種途徑。在微信小遊戲中普遍存以下兩種排名
- 好友關係排名
- 世界排名
其中好友的排名,需要通過微信子域實現。在子域上下文中,只能調用微信提供相關的api,且數據傳輸只能進不能出。即使在子域中調用雲函數也不行。這個對數據很嚴格,開發略為複雜。但好處也很明顯
- 無需用戶確認授權就可實現排名
- 排名資訊均為自己好友,刺激效果更明顯
儘管這樣,我們還是先實現世界排行。世界排行需要用戶授權。早期只需要調用wx.authorize
就可以實現,現在很不穩定(好像廢棄了)。所以不得不通過生成一個授權按鈕來實現
2. 微信雲開發
微信小遊戲為開發者提供了一部分免費的雲環境。可以實現文件存儲,數據存儲以及通過雲函數實現服務端介面。開通方式也很簡單,這裡不做說明。既然要實現排名,優先選用雲函數來實現對應的api。要實現雲函數,需要在project.config.json
文件中通過屬性cloudfunctionRoot
指定雲函數目錄。由於,是通過cocoscretor開發,每次構建發布都會清空輸出內容。為了解決人肉複製粘貼,我們需要通過訂製小遊戲構建模板
實現微信小遊戲所有程式碼的管理。小遊戲地心俠士構建模板如下
從圖中,可以看到獲取openid、獲取世界排名、保存用戶授權資訊等雲函數都放在cocoscreator程式碼環境中。這樣在開發完成後,通過cocoscreator構建發布,對應的雲函數也會一起打包過去
3. 實現世界排行
3.1 獲取玩家openid
首先在構建模板的cloud-functions
文件件中,使用npm
初始一個名為getOpenId
的node項目。初始好以後,運行npm install wx-server-sdk@latest --save
。這樣就建立好了一個雲函數的基本框架。
我們在index.js
文件,輸入以下程式碼
// author:herbert 464884492 // project:地心俠士 獲取用戶openid const cloud = require('wx-server-sdk') cloud.init() exports.main = async (event, context) => { const wxContext = cloud.getWXContext() return { event, openid: wxContext.OPENID, appid: wxContext.APPID, unionid: wxContext.UNIONID, } }
調用雲函數時,上下文中便可以得到玩家openid和uninid。玩家進入遊戲就先調用此函數,得到玩家的openid用於後邊更新玩家數據和獲取世界排行的條件。
小遊戲端調用雲函數前,需要初始雲環境。因為採用訂製構建模板,所以我們直接在模板的game.js
文件末尾初始我的雲環境
// author:herbert 464884492 // 地心俠士 初始雲環境 .... wxDownloader.init(); window.boot(); //初始化雲調用 this.wx.cloud.init({ traceUser: true, env: 'dxxs-dxxs' }); ...
後續調用雲函數中,第一步都是要獲取openid,這裡定義一個全局變數將其保存起來,調用方法如下
// author:herbert 464884492 // 地心俠士 玩家openid private static openId: string = null; private static initenv() { return new Promise((resolve, reject) => { if (!this.wx) reject(); //直接使用本地快取 if (this.openId != null) resolve(); // 調用雲函數獲取 this.wx.cloud.callFunction({ name: 'getOpenId', complete: res => { this.openId = res.result.openid; resolve(); } }); }); }
3.2 動態生成授權按鈕
先看下地心俠士布局介面
上圖中可以看到,地心俠士虛擬了一個遊戲操作區域。玩家聚焦到世界排行時,需要渲染一個授權按鈕在確定的位置。需求很簡單,可考慮到移動端多解析度,這個操作就變得複雜了。需要做螢幕適配。地心俠士採用自適應寬度的適配策略,配置如下圖
遊戲運行時獲取實際解析度的寬度與設計的寬度相除,變可知道當前寬度變化比列,鍵盤容器九宮格使用了主鍵widget
底部111px,高度161px。確定按鈕寬度105px
微信小遊戲以左上角為原點,通過top
和left
確定位置。然而,cocoscreator以左下角為原點,所以在計算top
值時需要用螢幕寬度 – box上邊緣y坐標。適配程式碼如下
// author:herbert 464884492 // 地心俠士 動態生成透明授權按鈕 initUserInfoButton() { // 獲取設計尺寸 let desingSize: cc.Size = cc.view.getDesignResolutionSize(); // 獲取實際螢幕尺寸 let screenSize: cc.Size = cc.view.getFrameSize(); // 獲取寬度倍率 let widthRate = screenSize.width / desingSize.width; // 獲取當前倍率下九宮格鍵盤實際高度 let halfKcHeight = 161 * widthRate / 2; // 獲取當前倍率下確定按鈕實際寬度 let btnwidth = this.btnKeySuer.width * widthRate; WxCloudFun.createUserinfoButton("", // 確定按鈕中心點對應小遊戲left值 (螢幕寬度-確定按鈕實際寬度)/2 // 定義實際授權按鈕size為105*40,所以還必須加上對應的偏差值 // 以下程式碼中left體現整體適配過程,不考慮中間過程可以直接使用 // (螢幕寬度-授權按鈕)/2 即可得到left值 screenSize.width / 2 - 52.5 * widthRate + (btnwidth - 105) / 2, // Canvas 適配策略是 Fit Width,所以Canvas下邊沿不一定就是螢幕邊緣 // 通過111*widthRate得到具體下沿值,在加上虛擬鍵盤一半高度,可得到中心位置 // 由於微信原點在左上角,需要保持按鈕處於中心位置,坐標還需要上移一半按鈕高度 screenSize.height - (111 * widthRate + halfKcHeight + 20), () => { this.keyCode = cc.macro.KEY.r; this.scheduleOnce(async () => { this.dlgRank.active = true; // 獲取排名數據 await this.getRankInfo(); }, 0); }); }
3.3 獲取用戶頭像昵稱資訊
經過上一步驟的適配操作,只要玩家聚焦到【世界排行】,地心俠士虛擬鍵盤的確定按鈕正上方會覆蓋一個透明的userInfoButton
,玩家點擊確定就會喚起授權對話框,然後在對應的回調函數就可以完成用戶數據保存操作
// author:herbert 464884492 // 地心俠士 獲取玩家基本資訊 public static createUserinfoButton(text: string, left: number, top: number, cb: Function) { this.userInfoButton = this.wx.createUserInfoButton({ type: 'text', text: text, style: { left: left, top: top, height: 40, width: 105, lineHeight: 40, textAlign: 'center', fontSize: 16, backgroundColor: '#ff000000',// 透明 color: '#ffffff', } }); this.userInfoButton.hide(); this.userInfoButton.onTap((res) => { // 將獲取到的用戶數據提交到雲端 this.wx.cloud.callFunction({ name: 'putUserinfo', data: { ...res.userInfo, openid: this.openId } }); this.hideUserInfoButton(); cb.call(); }); }
在程式碼中,除了傳入玩家微信資訊外。我還額外傳遞進入遊戲時就獲取的openid
。正常情況下不需要的,因為,雲函數中天然會告訴你openid。不過,我們在後端使用了got
獲取玩家頭像保存到雲端文件存儲中。引入此包後,後端就獲取不到openid
了,相當奇怪。對應雲平台雲函數程式碼如下
// author:herbert 464884492 // 地心俠士 雲函數保存玩家基本資訊 const cloud = require('wx-server-sdk') const got = require('got') cloud.init() // 雲函數入口函數 exports.main = async(event, context) => { const { nickName, avatarUrl, gender, openid } = event; let wxContext = cloud.getWXContext(); // 如果後端拿不到openid就採用前端傳入的openid wxContext.OPENID = wxContext.OPENID || openid; const log = cloud.logger() log.info({ tip: `正在請求頭像地址[${avatarUrl}]` }) // 獲取頭像數據流 const stream = await got.stream(avatarUrl); let chunks = []; let size = 0; const body = await (async() => { return new Promise((res, reg) => { stream.on('data', chunk => { chunks.push(chunk) size += chunk.length log.info({ tip: `正在讀取圖片流資訊:[${chunk.length}]` }) }) stream.on('end', async() => { const body = Buffer.concat(chunks, size) log.info({ tip: `正在保存頭像文件:[${size}]` }) res(body) }) }) })() //保存頭像到雲存儲 const { fileID } = await cloud.uploadFile({ cloudPath: `avatars/${wxContext.OPENID}.jpg`, fileContent: body }) // 添加或更新玩家資訊到資料庫 const db = cloud.database() const { data } = await db.collection("dxxs").where({ _openid: wxContext.OPENID }).get() const updateData = { fileId: fileID, nickName: nickName, sex: gender == 1 ? '男' : '女', avatarUrl: avatarUrl } if (data.length > 0) { log.info({ tip: `正在修改資料庫資訊:[${size}]` }) await db.collection("dxxs").doc(data[0]._id).update({ data: updateData }) } else { log.info({ tip: `正在添加資料庫資訊:[${size}]` }) await db.collection("dxxs").add({ data: { ...updateData, _openid: openid } }) } return { openid: wxContext.OPENID, appid: wxContext.APPID, unionid: wxContext.UNIONID } }
3.4 獲取排行數據
保存完用戶數據後,通過一個回調函數,實現了玩家排名數據獲取。細心的朋友可以在前邊授權按鈕適配的章節看到await this.getRankInfo();
這句程式碼。後端雲函數就是一個簡單數據查詢。效果圖如下
從上圖可以看到,我實現了三個維度排名,需要在前端需要傳入排名欄位。對應程式碼如下
// author:herbert 464884492 // 地心俠士 獲取排名資訊 public static async getWorldRanking(field: string = "level") { const { result } = await this.wx.cloud.callFunction({ name: 'getWordRanking', data: { order: field } }); return result.ranks; }
雲函數程式碼如下
// author:herbert 464884492 // 地心俠士 雲函數返回排名資訊 const cloud = require('wx-server-sdk') cloud.init() exports.main = async (event, context) => { const wxContext = cloud.getWXContext() const db = cloud.database(); const { order = "level" } = event; const openData = await db.collection("dxxs") .orderBy(order, "asc") .get() const ranks = openData.data.map(item => { return { openid: item._openid, [order]: item[order], nickName: item.nickName, fileId: item.fileId, avatarUrl: item.avatarUrl } }); return { ranks: ranks, openid: wxContext.OPENID, appid: wxContext.APPID, unionid: wxContext.UNIONID } }
4. 總結
- 微信子域數據很嚴格,數據只進不出。調用雲函數也不行
- 雲函數中使用http請求,可能會得不到openid
- 螢幕適配知道定位原則,也可以很簡單
- avatarUrl通過Sprite現實頭像,需要設置安全域名
- 目前部分華為手機分享截屏出現黑屏使用canvas.toTempFilePath就可以解決
這裡有一個CoscosCreator遊戲開發群,歡迎喜歡聊技術的朋友加入
歡迎感興趣的朋友關注我的訂閱號「小院不小」,或點擊下方二維碼關注。我將多年開發中遇到的難點,以及一些有意思的功能,體會都會一一發布到我的訂閱號中