基于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);
            });
    }

/**
     * 向开放域发送消息,并接收开放域返回过来的数据,
     * 可根据发送参数和接收数据在主域这边进行下步处理
     * @param message
     * @param caller
     * @param callback
     */
    function wxPostMessage(message, caller, callback){
        window['wx'].postMessage(message);
        Laya.timer.once(400, this, function (){
            //回调处理
            if (caller == null || caller == undefined) {
                callback(message);
            } else {
                caller.callback(message);
            }
        });
    }

这边主要发送的消息体中携带了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"
}

到这里,微信小游戏排行榜功能就算实现了,到头来发现,其实实现起来并不难,难的是缺乏资料,此文仅用来抛砖引玉,如有问题欢迎提出。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容