1. 程式人生 > >基於Laya遊戲引擎實現微信小遊戲排行榜

基於Laya遊戲引擎實現微信小遊戲排行榜

我們都知道,微信小遊戲和小程式目前風頭十足,很多公司都逐漸增加了相關業務線來迅速推廣自己的產品和搶佔使用者群。說到微信小遊戲,就不得不提到排行榜這個功能,就目前遊戲行業,似乎都離不開排行榜這個重要功能,使用者很大一部分留存都是依仗這個看似不起眼的模組。那麼,微信小遊戲中具體該如何藉助laya引擎實現排行榜這個功能呢?我們先來看一下最終的效果圖:

image

按照微信官方的說法,如果我們要使用微信官方提供的好友關係鏈的資料,我們就不能直接在專案中繪製排行榜,我們需要藉助於開放域來繪製排行榜:

image

​ 如果想要展示通過關係鏈 API 獲取到的使用者資料,如繪製排行榜等業務場景,需要將排行榜繪製到 sharedCanvas

上,再在主域將 sharedCanvas 渲染上屏。簡單來說,sharedCanvas 是主域和開放資料域都可以訪問的一個離屏畫布。在開放資料域呼叫 wx.getSharedCanvas() 將返回 sharedCanvas。更多相關詳情可以去看看官網的介紹:https://mp.weixin.qq.com/debug/wxagame/dev/tutorial/open-ability/open-data.html

那麼我們來實際動手操作一下吧。

主域繪製

通過效果可以看出來,我們排行榜是一個彈窗形式展示的,由於開放域只負責排行榜UI繪製,所以,除此以外的UI以及互動我們需要在主域繪製和處理。因此,這裡的彈窗 dialog

需要在主域繪製,然後將對應的排行榜需要顯示的位置資訊和長寬對映到開放域,具體程式碼如下:


    /**
     * 顯示排行榜資料
     */
    function onRankInfoLoad(){
        console.log("檢視排行榜~");
        var dialog = new RankDialogUI();
        showShareCanvas();
        // 解決顯示物件和滑鼠錯位而導致的排行榜滑動無效問題
        var globalPosition = dialog.ranking_list.localToGlobal(new
Laya.Point()); var originMatrix = Laya.stage._canvasTransform; var mat = new Laya.Matrix(originMatrix.a, 0, 0, originMatrix.d, globalPosition.x * originMatrix.a, globalPosition.y * originMatrix.d); wxPostMessage({ command: 0, text: "設定開放域canvas大小", canvasData: { width: rankViewWidth * mat.a, height: rankViewHeight * mat.d, matrix: mat }, isLoad: false }, null, function (message) { console.log("再次往開放域發請求"); window['wx'].postMessage({ command: 1, text: '開放域載入資源', }); }); Laya.stage.addChild(dialog); dialog.ranking_list.visible = false; dialog.popup(); Laya.timer.once(400,this,function(){ wxPostMessage({ command: 3, text: "獲取排行榜資料~" }, null, function (message) { console.log("獲取排行榜的回撥~"); }); }); dialog.btn_rank_dialog_share.on(Laya.Event.CLICK, this, onGameRankShare); dialog.btn_rank_dialog_back.on(Laya.Event.CLICK, this, onDialogClose); function onDialogClose(){ wxPostMessage({ command: 4, text: "關閉排行榜~" }, null, function (message) { console.log("關閉排行榜的回撥~"); }); dialog.close(); } function onGameRankShare(){ console.log("分享排行榜~"); window['wx'].showShareMenu({ withShareTicket:false, success:function(res){ console.log("開啟轉發成功~"); }, fail:function(res){ console.log("開啟轉發失敗~"); }, complete:function(res){ } }); window['wx'].onShareAppMessage(function () { return { title: '我在飛機大戰遊戲中排名又上升了,快來挑戰我吧~' } }) window['wx'].shareAppMessage({ title: '我在飛機大戰遊戲中排名又上升了,快來挑戰我吧~', imageUrl: canvas.toTempFilePathSync({ x: (screenWidth - rankViewWidth)/2 - 10, y: (screenHeight - rankViewHeight)/2+80, width: (rankViewWidth - 30)*4, height: (rankViewHeight - 40)*4, destWidth: 500, destHeight: 600 }) }); } } /** * 設定共享Canvas */ function showShareCanvas(){ window['sharedCanvas'].width = rankViewWidth; window['sharedCanvas'].height = rankViewHeight; //主域顯示開放域內容??? //window['sharedCanvas'].sharedCanvas = window['wx'].getOpenDataContext().canvas; Laya.timer.once(1000, this, function () { var sprite = new Laya.Sprite(); sprite.zOrder = 1008; sprite.pos(0, 0); var texture = new Laya.Texture(window['sharedCanvas']); texture.bitmap.alwaysChange = true;//小程式使用,非常費 sprite.graphics.drawTexture(texture, (screenWidth - rankViewWidth)/2, (screenHeight - rankViewHeight)/2, texture.width, texture.height); Laya.stage.addChild(sprite); }); }

這邊主要傳送的訊息體中攜帶了command欄位,用於在開放域執行不同的功能程式碼,這邊大體分為:資源載入命令、初始化排行榜大小命令、獲取關係鏈(排行榜)資料命令以及關閉排行榜的命令,大家可以根據業務具體需要適當增減。

開放域繪製

根據官方的說明,開放資料域 是一個封閉、獨立的 JavaScript 作用域。要讓程式碼執行在開放資料域,需要在 game.json 中新增配置項 openDataContext 指定開放資料域的程式碼目錄。新增該配置項表示小遊戲啟用了開放資料域,這將會導致一些 限制。這些限制主要包含:

  • 無法設定sharedCanvas的寬高
  • 只能使用有限的介面(如逐幀動畫、Timer、觸控事件以及獲取和設定關係鏈資料等介面)

下面我們一步步來在開放域繪製排行榜。

  1. 首先,需要新建一個專案作為開放域,這個專案目錄是與主域專案平行的(主域就是未做排行榜之前的專案目錄),比如我的專案目錄是這樣的:

    image

  2. 接著,我們需要在開放域中接收主域傳送的訊息並處理不同的功能命令,UI就不放了,看看具體程式碼吧:

    /**
        * 監聽從主域發過來的訊息
        */
       function wxOnMessage(){
           if(window['wx'] != undefined){
               window['wx'].onMessage(function (message){
                   dispatchMessage(message);
               });
           }else{
               console.log("微信介面無法使用~");
           }
       }
       /**
        * 處理訊息
        * @param {*} message 
        */
       function dispatchMessage(message){
           switch(message.command){
               //設定開放域畫布大小
               case 0:
                   sample.setCanvasSize(message.canvasData);
                   break;
               //載入資源
               case 1:
                   sample.loadResource();
                   break;
               //寫入排行榜資料
               case 2:
                   sample.writeRankingData(message.rankingData);
                   break;
               //獲取微信排行榜資料
               case 3:
                   sample.getRankingData();
                   break;
               //關閉排行榜
               case 4:
                   sample.closeRankingDialog();
                   break;
               default:
    
                   console.log(JSON.stringify(message));
           }
       }
       /**
        * 設定開放域畫布大小
        * @param {*} size 
        */
        _proto.setCanvasSize = function(size){
           console.log("設定開放域canvas大小~");
           window['sharedCanvas'].width = size.width;
           window['sharedCanvas'].height = size.height;
           //Laya.stage.width = size.width;
           //Laya.stage.width = size.height;
           /**
            * 將主域的canvasTransform對映到開放域
            */
           if(size.matrix!=null){
               console.log("收到主域的同步canvasTransform了~");
           }
           var mainMatrix = size.matrix;
           var openMatrix = new Laya.Matrix();
           openMatrix.a = mainMatrix.a;
           openMatrix.b = mainMatrix.b;
           openMatrix.c = mainMatrix.c;
           openMatrix.d = mainMatrix.d;
           openMatrix.tx = mainMatrix.tx;
           openMatrix.ty = mainMatrix.ty;
           //重置矩陣
           Laya.stage._canvasTransform = openMatrix;
           //監聽舞臺的滑鼠移動事件
           Laya.stage.mouseEnabled = true;
       }
    
       /**
        * 使用者自己的排名
        */
       var myRanking = -1;
       /**
        * 獲取微信排行榜資料
        */
       _proto.getRankingData = function(){
           window['wx'].getUserInfo({
               openIdList: ['selfOpenId'],
               success: (userRes) => {
                   console.log('success', userRes.data);
                   //索引代表各個好友0為自己
                   let userData = userRes.data[0];
                   console.log("取資訊索引0" + userData.nickName);
                   //取出所有好友資料
                   window['wx'].getFriendCloudStorage({
                       keyList: [
                           //'擊殺排行',
                           '第1關',
                           '第2關',
                           '第3關'
                       ],
                       success: res => {
                           console.log("wx.getFriendCloudStorage success", res);
                           let data = res.data;
                           /*data.sort((a, b) => {
                               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 b.KVDataList[0].value - a.KVDataList[0].value;
                           });*/
                           for (let i = 0; i < data.length; i++) {
                               var playerInfo = data[i];
                               var currentPlayer = res.data[i].nickname;
                               console.log("當前排行玩家暱稱為=>"+res.data[i].nickname);
                               var kvList = playerInfo.KVDataList;
                               var scoreSum = 0;
                               if(kvList.length>0){
                                   for(var j = 0;j<kvList.length;j++){
                                       if(kvList[j].key != null){
                                           //將value轉化為int再累加
                                           scoreSum+=Number(kvList[j].value);
                                       }
                                   }
                               }
    
                               if (data[i].avatarUrl == userData.avatarUrl) {
                                   //獲取群好友的時候,沒有自己的名字??
                                   data[i].nickName = userData.nickName;
                                   myRanking = i+1;
                                   console.log("此ID為自己,當前排名第"+myRanking);
    
                               }
                               //填充總分資訊
                               sortData.push({
                                   nickName: currentPlayer, 
                                   avatarUrl: data[i].avatarUrl, 
                                   totalScore: scoreSum 
                               });
                           }
                           sortData.sort((a, b) => {
                               var score1 = Number(a.totalScore);
                               var score2 = Number(b.totalScore);
                               if (score1 > score2) {
                                   return -1;
                               }else if(score1 < score2){
                                   return 1;
                               }else{
                                   return 0;
                               }
                           });
                           showRankingDialog();
    
    
                       },
                       fail: res => {
                           console.log("拉取好友資訊失敗", res);
                       },
                   });
               },
               fail: (res) => {
                   console.log("拉取個人資訊失敗")
               }
           });
       }
       /**
        * 載入資源
        */
       _proto.loadResource = function(){
           Laya.loader.load(["comp/bg_line.png","comp/ranking1.png","comp/ranking2.png",
           "comp/ranking3.png","comp/userholder_img.png"], Laya.Handler.create(null,function(){
               console.log("開放域資源載入完畢~");
               sample.rankView = new RankingViewUI();
               Laya.stage.addChild(sample.rankView);
           }));
    
       }
    
       /**
        * 寫入排行榜資料
        */
       _proto.writeRankingData = function(rankingData){
           console.log("寫入排行榜資料~");
           //KVDataList代表排行資料,可以為多個,多個代表多個排行
           //key-排行型別,value-排行分數
           window['wx'].setUserCloudStorage({
               KVDataList: [
                   //{ key: '擊殺排行', value: "" + 1 },
                   { key: '第'+rankingData.fightLevel+'關', value: rankingData.fightScore+"" },//需要改成動態的值
               ],
               success: function (res) {
                   console.log('setUserCloudStorage', 'success', res)
               },
               fail: function (res) {
                   console.log('setUserCloudStorage', 'fail')
               }
           });
       }
       var sortData = [];
       /**
        * 渲染排行榜列表
        */
       function showRankingDialog(){
           console.log("拿到好友排行榜資訊", sortData);
           sample.rankView.ranking_list.vScrollBarSkin = "";
           sample.rankView.ranking_list.array = sortData;
           sample.rankView.ranking_list.renderHandler = new Laya.Handler(this, onRender);
           sample.rankView.ranking_list.selectHandler = new Laya.Handler(this, onSelect);
    
       }
    
       var lastRenderIndex = -1;
       function onRender(cell, index){
           if (index == lastRenderIndex) {
               return;
           }
           lastRenderIndex = index;
           //根據子節點的name獲取子節點物件
           var name = cell.getChildByName("item_rank_name");
           var ranking = cell.getChildByName("item_rank_text");
           var userlogo = cell.getChildByName("item_rank_logo");
           var score = cell.getChildByName("item_rank_score");
        var rank_icon = cell.getChildByName("item_rank_icon");
    
           name.text = sortData[index].nickName;
           console.log("渲染排行榜當前的使用者名稱為="+sortData[index].nickName+",渲染索引:"+lastRenderIndex);
           ranking.text = (index+1)+"";
           userlogo.skin = sortData[index].avatarUrl;
           score.text = sortData[index].totalScore+"分";
           if(lastRenderIndex === 0 || lastRenderIndex === 1 || lastRenderIndex === 2){
            ranking.visible = false;
            rank_icon.visible = true;
            rank_icon.skin = "comp/ranking"+(lastRenderIndex+1)+".png";
        }else{
            rank_icon.visible = false;
               ranking.visible = true;
           }
    
       }
    
       function onSelect(index){
           console.log("當前選擇的索引是:"+index);
    
       }
    
       /**
        * 關閉排行榜
        */
       _proto.closeRankingDialog = function(){
           //dialog.close();
        console.log("關閉排行榜~");
           sortData = [];
           lastRenderIndex = -1;
           sample.rankView.removeChildren();
           sample.rankView = null;
    
       }
    
       _proto.start = function(){
           console.log("開始接收主域的訊息~");
           wxOnMessage();
       }
    }
    

    可以看到,我們這裡通過 wx.onMessage 方法來獲取主域傳送的資料,然後藉助 dispatchMessage 方法作訊息的分發處理。值得注意的是,我們在設定開放域canvas大小的時候,需要重置座標矩陣,將主域的排行榜顯示位置對映到開放域中來,否則會發生滑動無效的問題。我這裡設定的排行榜資料有三條,大家可以根據具體需求來傳入,獲取到排行榜資料後,需要對它進行排序處理並展示,這裡直接藉助於laya中的 List 控制元件展示就可以了,對於它用法不熟悉的可以去laya官網瞭解一下:https://ldc.layabox.com/doc/?nav=zh-js-6-0-0

  3. 開放域圖片載入問題:
    用過laya遊戲引擎的都知道,我們一般用官方推薦的打包圖集的方式來載入遊戲中的圖片資源,這種方式在主域中是可行的,然而,在開放域中卻不能成功載入圖片資源。因此,開放域中,我們不需要將圖片資源打包成圖集,只要像下面這樣直接載入圖片即可:

 /**
     * 載入資源
     */
    _proto.loadResource = function(){
        Laya.loader.load(["comp/bg_line.png","comp/ranking1.png","comp/ranking2.png",
        "comp/ranking3.png","comp/userholder_img.png"], Laya.Handler.create(null,function(){
            console.log("開放域資源載入完畢~");
            sample.rankView = new RankingViewUI();
            Laya.stage.addChild(sample.rankView);
        }));

    }

然後,我們還需要將對應的圖片資料夾拷貝到wx_publish目錄下,否則會提示找不到圖片資源。

合併主域和開放域

主域和開放域功能程式碼實現了之後,我們就需要打包成微信小遊戲專案了。首先,我們需要先將主域專案釋出成微信小遊戲,釋出目錄直接為專案的根目錄,如上圖的 wx_publish 目錄。然後,在該目錄下建立src/myOpenDataContext目錄。接著,我們需要將開放域專案也釋出成微信小遊戲,目錄可以選擇桌面,名稱為 wx_open,釋出成功後,進入該目錄,將code.js、weapp-adapter.js以及index.js檔案複製到 wx_publish/src/myOpenDataContext 目錄下,並在 game.json 檔案中增加開放域對映目錄:


{
  "deviceOrientation": "portrait",
  "showStatusBar": "false",
  "networkTimeout": {
    "request": 10000,
    "connectSocket": 10000,
    "uploadFile": 10000,
    "downloadFile": 10000
  },
  "openDataContext": "src/myOpenDataContext"
}

到這裡,微信小遊戲排行榜功能就算實現了,到頭來發現,其實實現起來並不難,難的是缺乏資料,此文僅用來拋磚引玉,如有問題歡迎提出。