美文网首页电竞·游戏程序员小程序
小游戏游戏入门到精通OR放弃?

小游戏游戏入门到精通OR放弃?

作者: Javen205 | 来源:发表于2018-10-09 19:08 被阅读23次

    这是介绍的小游戏为QQ轻游戏-玩一玩

    0、体验QQ轻游戏

    • 需要使用Android手机
    • 登录手Q开启厘米秀
    • 侧滑点击人物形象或者选择任意一好友点击「+」滑拔一下找到「厘米秀」
    搜索厘米秀 申请体验资格 开启厘米秀
    搜索厘米秀 申请体验 开启厘米秀
    侧滑 好友点「+」或者直接点击人物 游戏入口页
    侧滑人物 选择好友点击+ 游戏入口页

    1、平台申请账号

    注册很简单,使用已有Q号登录「厘米游戏」开放平台按照流程提交资料审核即可 。开发者接入官方说明文档

    「厘米游戏」 开放平台注册提交资料的同时会注册一个相关联的「QQ服务号」。游戏中显示的用户信息是通过后台静默授权「QQ服务号」后再通过用户相关的接口获得,这点与微信公众号以及微信小游戏类似。

    一句话概括:目前暂未对个人开放,现阶段为邀请码模式。但如果你有好的IP资源或者优秀开发团队是比较好申请的。

    「微信小游戏」 做比较目前来看最大的优势就是

    • 现阶段游戏中集成广告所得广告费用平台不分成
    • 游戏评级高官方可以让游戏上中心化首页推荐位

    上线游戏都需要 「游戏自审自查报告」「计算机软件著作权登记证书」,如需内购需要提供 「广电总局版号批文」 以及 「文化部备案信息」

    2、环境搭建

    QQ玩一玩(轻游戏)开发环境搭建与调试

    如果使用了第三方引擎Mac电脑非必须。

    3、第三方引擎推荐

    第三方引擎的实现方式为基于 brickswebGL 接口进行封装,具有较高的灵活性,但渲染性能会欠缺。 如开发者对性能要求更高,推荐使用bricks引擎的原生渲染

    注意: iOS 在手 Q 770 版本禁用了 webGL,会导致界面卡在 99% 加载界面,开发者忽略 iOS 端表现,关注安卓端表现。

    关于使用什么引擎来开发「轻游戏」或者「H5游戏」都有各自的说法。就像大家讨论Java是世界最好的语言一样。

    世界上没有不出bug的程序,引擎或者IDE都或多或少存在一定的Bug以及局限性。请根据项目需求以及当下的环境酌情选择。

    本文示例使用的游戏引擎为Cocos Creator

    4、QQ轻游戏常用功能介绍

    4.1 获取用户信息
    4.1.1 获取游戏全局变量

    游戏启动后,引擎会为开发者写入名为GameStatusInfo的有关游戏的全局参数(类似于H5中windows对象),从中可获取有关用户标识符(openId)、游戏标识(gameId)、机型等参数。

    详细参数对照表请移步至 官方参考文档-登录与鉴权

    示例参考-- 获取手Q版本跳转其他游戏

    if (cc.sys.platform != cc.sys.QQ_PLAY) {
        self.setTipMsg("请在QQ玩一玩环境下测试");
        return;
    }
    if (BKTools.versionCompare(GameStatusInfo.QQVer, "7.7.0.0")) {
        BKTools.skipGame("2731");
    } else {
        self.setTipMsg("手Q版本过低,请更新");
    }
    

    如何跳转其他游戏完整的代码后面会提到

    4.1.2获取用户昵称
    function getNick(callback) {
        BK.MQQ.Account.getNick(GameStatusInfo.openId, callback);
    }
    
    BKTools.getNick(function(openId, nick) {
        Global.nickName = nick;
    });
    
    4.1.3 获取用户图像
    getHead() {
            let self = this;
            let absolutePath = "GameSandBox://_head/" + GameStatusInfo.openId + ".jpg";
            let isExit = BK.FileUtil.isFileExist(absolutePath);
            cc.log(absolutePath + " is exit :" + isExit);
            //如果指定目录中存在此图像就直接显示否则从网络获取
            if (isExit) {
                cc.loader.load(absolutePath, function (err, texture) {
                    if (err == null) {
                        self.head.getComponent(cc.Sprite).spriteFrame = new cc.SpriteFrame(texture);
                    }
                });
            } else {
                BK.MQQ.Account.getHeadEx(GameStatusInfo.openId, function (oId, imgPath) {
                    cc.log("openId:" + oId + " imgPath:" + imgPath);
                    var image = new Image();
                    image.onload = function () {
                        var tex = new cc.Texture2D();
                        tex.initWithElement(image);
                        tex.handleLoadedTexture();
                        self.head.getComponent(cc.Sprite).spriteFrame = new cc.SpriteFrame(tex);
                    }
                    image.src = imgPath;
                });
            }
        },
    
        // onLoad () {},
        btn(event, data) {
            cc.log("点击了按钮");
            if (cc.sys.platform == cc.sys.QQ_PLAY) {
                this.getHead();
            } else {
                cc.log("请在QQ玩一玩平台中测试");
            }
    
        },
    
    4.2 分享与邀请

    QQ轻游戏分享方法比较多具体实现方式可以官方的分享相关文档。这里介绍常用的一种方式 多渠道分享

    /**
     * 获取分享信息
     * @param {String} localPicPath 
     */
    function getShareInfo(localPicPath) {
        if (!localPicPath) {
            localPicPath = "GameRes://qrcode.png";//游戏资源包根目录存放图片qrcode.png
        }
        let summarys = ["文案1", "游戏太好玩了,玩得停不下来!", "游戏太刺激了,邀请还能领抱枕!", "文案2"];
        let shareInfo = {
            summary: summarys[getRandomInt(0, 3)],
            picUrl: "http://h.hiphotos.baidu.com/image/pic/item/18d8bc3eb13533fa4dd573ada3d3fd1f40345bd6.jpg", //支持HTTPS
            extendInfo: Global.openId,//或者使用GameStatusInfo.openId
            localPicPath: localPicPath, //分享至空间、微信、朋友圈时需要的图。(选填,若无该字段,系统使用游戏对应的二维码)
        };
        return shareInfo;
    }
    
    /**
     * 分享
     * @param {*} shareInfo 
     * @param {*} callback 
     */
    function toShare(shareInfo, callback) {
        if (cc.sys.platform == cc.sys.QQ_PLAY) {
            BK.QQ.share(shareInfo, function (retCode, shareDest, isFirstShare) {
                log("分享结果 retCode:" + retCode + " shareDest:" + shareDest + " isFirstShare:" + isFirstShare);
                if (retCode == 0) {
                    if (callback) {
                        callback(0);
                    }
                    if (shareDest == 0) {
                        //聊天窗
                        log("成功分享至QQ");
                    } else if (shareDest == 1) {
                        //空间
                        log("成功分享至空间");
                    } else if (shareDest == 2) {
                        //微信
                        log("成功分享至微信");
                    } else if (shareDest == 3) {
                        // 朋友圈
                        log("成功分享至朋友圈");
                    }
                } else if (retCode == 1) {
                    if (callback) {
                        callback(-1);
                    }
                    log("分享失败" + retCode);
                } else if (retCode == 2) {
                    if (callback) {
                        callback(-1);
                    }
                    log("分享失败,用户取消分享:" + retCode);
                }
            });
        } else {
            if (callback) {
                callback(0);
            }
        }
    }
    

    但这里有一个问题 点击右上角的「…」选择分享游戏,分享后图片不显示再次调用接口来实现分享时无任何影响 ,要想解决此问题自需要实现生命周期监听并实现onShare 方法

    关于QQ玩一玩的默认分享问题

    4.3 生命周期
    /**
     * 游戏事件以及生命周期
     */
    function addGameEvent() {
        new BK.Game({
            //游戏启动后
            onLoad: function (app) {
                log("BK.Game.onLoad");
            },
            //进入点击最大化后
            onMaximize: function (app) {
                log("BK.Game.onMaxmize");
            },
            //进入点击最小化后
            onMinimize: function (app) {
                log("BK.Game.onMinmize");
            },
            //进入后台后响应
            onEnterBackground: function (app) {
                log("BK.Game.onEnterbackground");
            },
            //回到前台后响应
            onEnterForeground: function (app) {
                log("BK.Game.onEnterforeground");
            },
            //点击“分享游戏”后响应。(可选)
            onShare: function (app) {
                log("BK.Game.onShare");
                return getShareInfo();
            },
            //分享成功
            onShareComplete: function (app, retCode, shareDest, isFirstShare) {
                log("BK.Game.onShareComplete retCode:" + retCode + " shareDest:" + shareDest + " isFirstShare:" + isFirstShare);
            },
            //进入点击关闭响应
            onClose: function (app) {
                log("BK.Game.onClose");
            },
            //网络环境切换事件
            onNetworkChange: function (app, state) {
                log("BK.Game.onNetworkChange:STATE :" + state);
            },
            //全局异常监听
            onException: function () {
                log("BK.Game.onException msg:" + this.errorMessage() + " ,stack:" + this.errorStacktace());
            }
        });
    }
    
    4.4 支付与红包

    支付接入比较简单,目前没有异步通知给开发者的接口,所有的逻辑都由玩一玩后台处理。支付接入步骤

    • 平台上传道具资源(图片、描述、单价等)
    • 道具申请上架
    • 游戏内通过接口获取道具信息(道具ID、名称、图片等)
    • 通过道具ID列表购买道具

    具体流程实现参考官方文档-支付

    据内部消息 发送B2C红 在今年国庆假期间将有一批轻游戏试水,有什么样的创意和玩法可以期待一下。红包总金额最低5W最高20W。

    5、网络通讯

    原生引擎开发指引 中可以了解到。网络方案可以使用原生引擎、或者三方引擎进行界面以及逻辑的搭建。

    官方文档-网络功能

    下面我会介绍

    • BK.HttpUtil:用于短连接
    • XMLHttpRequest:用于短连接
    • WebSocket:用于长连接
    http get/post请求

    BK.HttpUtil

    function BKGet(url, callback, custom) {
        let httpUtil = new BK.HttpUtil(url);
        httpUtil.setHttpMethod("get");
        httpUtil.custom = custom;
        //绑定回调对象
        httpUtil.requestAsync(callback.bind(httpUtil));
    }
    

    如果是POST请求,将httpUtil.setHttpMethod("get");设置为httpUtil.setHttpMethod("post");

    //下载图片并保存在手机中
    BKTools.BKGet("http://h.hiphotos.baidu.com/image/pic/item/18d8bc3eb13533fa4dd573ada3d3fd1f40345bd6.jpg", function (res, code) {
        cc.log("结果:" + code + " 渗透参数:" + this.custom);
        BK.FileUtil.writeBufferToFile("GameSandBox://test/test.jpg", res);
    }, "custom");
    
    //普通的get请求
    BKTools.BKGet("http://www.wanandroid.com/tools/mockapi/3461/Javen", function (res, code) {
    cc.log("结果:" + code + " 渗透参数:" + this.custom);
    if (code == 200) {
        let str = res.readAsString();
        cc.log(str);
        let data = JSON.parse(str);
        if (data.code == 0) {
            self.setTipMsg("网络请求结果 Gitee:" + data.data.name);
        } else {
            self.setTipMsg("网络请求异常:" + data.msg);
        }
    }
    }, "请求返回字符串");
    

    XMLHttpRequest

    /**
     * post 请求
     * @param {*} url 
     * @param {*} data 
     * @param {*} callBack 
     */
    function post(url, data, callBack) {
        log("请求参数:" + data);
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function () {
            let status = xhr.status;
            if (xhr.readyState == 4 && status == 200) {
                var responseBody = xhr.responseText;
                log("响应的结果:" + responseBody);
                callBack(status, JSON.parse(responseBody));
            }
        };
        xhr.open("POST", url, true);
        xhr.send(data);
    }
    
    /**
     * get请求
     * @param {*} url 
     * @param {*} data 
     * @param {*} callBack 
     */
    function get(url, data, callBack) {
        log("请求参数:" + data);
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function () {
            let status = xhr.status;
            if (xhr.readyState == 4 && status == 200) {
                var responseBody = xhr.responseText;
                log("响应的结果:" + responseBody);
                callBack(status, JSON.parse(responseBody));
            }
        };
        xhr.open("GET", url + "?" + encodeURIComponent(data), true);
        xhr.send();
    }
    
    webSocket请求

    如果是QQ玩一玩平台就是使用 BK.WebSocket,其他平台使用 标准的WebSocket

    //Script/common/WebSocket.js 
    /**
     * @author Javen 
     * @copyright 2018-09-22 17:32:21 javendev@126.com 
     * @description webSocket工具组件
     */
    
    let Global = require("Global");
    let BKTools = require("BKTools");
    
    let WS_TYPE = cc.Enum({
        BK_WS: 1,
        WEB_WS: 2,
    });
    
    cc.Class({
        extends: cc.Component,
        // properties: {
    
        // },
    
        // onLoad () {},
    
        start() {
            // this.schedule(function () {
            //     if (this.hasConnected) {
    
            //     }
            // }, 5);
        },
    
        initWebSocket() {
            if (cc.sys.platform == cc.sys.QQ_PLAY) {
                this._ws = new BK.WebSocket("ws://" + Global.WEB_SOCKET.URL);
                this._wsType = WS_TYPE.BK_WS;
            } else {
                this._ws = new WebSocket("ws://" + Global.WEB_SOCKET.URL);
                this._wsType = WS_TYPE.WEB_WS;
            }
            this.addEventListener(this._ws);
        },
    
        addEventListener(ws) {
            let self = this;
            ws.onopen = function (event) {
                self._isConnected = true;
                BKTools.log("onopen....");
            };
            ws.onerror = function (event) {
                self._isConnected = false;
                BKTools.log("onerror....");
            };
            ws.onclose = function (event) {
                self._isConnected = false;
                BKTools.log("onclose....");
            };
            if (self._wsType == WS_TYPE.BK_WS) {
                ws.onMessage = function (ws, event) {
                    if (event.isBinary) {
                        let buf = event.data;
                        //将游标pointer重置为0
                        buf.rewind();
                        let ab = new ArrayBuffer(buf.length);
                        let dv = new DataView(ab);
                        while (!buf.eof) {
                            dv.setUint8(buf.pointer, buf.readUint8Buffer());
                        }
                        self.toHander(ab);
                    } else {
                        BKTools.log("BK.WebSocket data type is not binary");
                    }
                }
            } else {
                ws.onmessage = function (event) {
                    if (event.data instanceof Blob) {
                        let blob = event.data;
                        var reader = new FileReader();
                        reader.readAsArrayBuffer(blob);
                        reader.onload = function (e) {
                            if (e.target.readyState == FileReader.DONE) {
                                let result = reader.result;
                                self.toHander(result);
                            }
                        }
                    } else {
                        BKTools.log("webSocket data type is not blob");
                    }
                };
            }
        },
    
        hasConnected() {
            return this._isConnected;
        },
    
        toHander(buffer) {
            let self = this;
            let cmd = proto.UserCmdOutComonProto.deserializeBinary(buffer);
            switch (cmd.getId()) {
                case proto.UserCmdOutType.RECONNECTION_RESULT:
                    BKTools.log("重连结果....");
                    break;
                case proto.UserCmdOutType.USER_CONNECT_SUCCESS:
                    BKTools.log("客户端连接成功....");
                    break;
                case proto.UserCmdOutType.USER_LOGIN_SUCCESS:
                    BKTools.log("反馈登录消息开始...");
                    break;
                case proto.UserCmdOutType.USER_LOGIN_SUCCESS_OVER:
                    BKTools.log("反馈登录消息结束....");
                    let loginOver = proto.PlayerLoginOverProtoOut.deserializeBinary(buffer);
                    //回调给请求页
                    Global.loginResponse(loginOver);
                    break;
                default:
                    break;
            }
        },
    
        send(bytes) {
            this._ws.send(bytes);
        },
        /**
         * 登录
         */
        toLogin() {
            if (!this.hasConnected()) {
                this.initWebSocket();
                return;
            }
            let login = new proto.UserLoginProto();
            login.setId(proto.UserCmdInType.USER_LOGIN);
            login.setToken(Global.WEB_SOCKET.TOKEN);
            this.send(login.serializeBinary());
        },
        // update (dt) {},
    });
    

    如何使用?

    将webSocket工具组件绑定到常驻节点,在通过cc.find查找常驻节点上的WebSocket组件

    this._webSocket = cc.find("常驻节点名称").getComponent("WebSocket");
    //调用封装的接口
    this._webSocket.toLogin();
    

    6、跳转到其他游戏

    跳转到其他游戏手Q 7.7.0 及以上才支持

    /**
     * 判断手Q版本
     * @param {String} ver1 7.1.1.1
     * @param {String} ver2 6.3.3.3
     */
    function versionCompare(ver1, ver2) {
        ver1 = parseInt(ver1.replace(/\./g, ""));
        ver2 = parseInt(ver2.replace(/\./g, ""));
        if (ver1 >= ver2) {
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * 跳转到其他游戏
     * @param {Number} gameId 
     */
    function skipGame(gameId) {
        BK.QQ.skipGame(gameId, "扩展参数");//游戏启动时可以通过GameStatusInfo.gameParam获取
    }
    

    7、成绩上报与排行榜

    官方文档-成绩上报与排行榜

    最新版本接口示例 胜局积累-大到小

    /**
     * 成绩上报
     * @param {*} isWin 
     * @param {*} callback 
     */
    function uploadScore(isWin, callback) {
        if (cc.sys.platform != cc.sys.QQ_PLAY) {
          if (callback) {
            callback(-1, "此接口只支持QQ玩一玩平台");
          }
          return;
        }
        if (!isWin) {
          isWin = 0;
        } else {
          isWin = 1;
        }
        var data = {
          userData: [{
            openId: GameStatusInfo.openId,
            startMs: Global.startGameTime.toString(),
            endMs: ((new Date()).getTime()).toString(),
            scoreInfo: {
              score: isWin,
            },
          }, ],
          attr: {
            score: {
              type: 'rank',
              order: 3,
            }
          },
        };
        BK.QQ.uploadScoreWithoutRoom(1, data, function (errCode, cmd, data) {
          log("uploadScoreWithoutRoom callback  cmd" + cmd + " errCode:" + errCode + "  data:" + JSON.stringify(data));
          if (callback) {
            callback(errCode, data);
          }
        });
      }
      /**
       * 拉取排行榜数据
       * @param {*} callback 
       */
      function getRankList(callback) {
        if (cc.sys.platform != cc.sys.QQ_PLAY) {
          if (callback) {
            callback(-1, "此接口只支持QQ玩一玩平台");
          }
          return;
        }
        let attr = "score";
        let order = 3;
        let rankType = 0;
        BK.QQ.getRankListWithoutRoom(attr, order, rankType, function (errCode, cmd, data) {
          log("getRankListWithoutRoom callback  cmd" + cmd + " errCode:" + errCode);
          if (errCode != 0) {
            callback(errCode);
            return;
          }
          if (data) {
            let rankList = data.data.ranking_list;
            log("data not null " + rankList.length);
            log(JSON.stringify(data));
            // rankList.forEach(element => {
            //   log("....华丽的分割线....");
            //   log("score:" + element.score);
            //   log("nick:" + element.nick);
            //   log("....华丽的分割线....");
            // });
            if (callback) {
              callback(errCode, rankList);
            }
          }
        });
      }
    

    8、关注公众号

    查询是否关注公众号

    function checkPubAccountState(){
    BK.QQ.checkPubAccountState(Global.PUIN ,function(errCode, cmd, data) {
          BK.Script.log(0,0," callback errCode = "+errCode+ " cmd = "+ cmd + " data = "+ data);
          if(data.is_follow == 1){
             return true;
          }else{
               return false;
          }
      });
    }
    
    

    进入公众号主页

    /**
     * 关注公众号
     */
    function follow() {
      if (cc.sys.platform == cc.sys.QQ_PLAY) {
        BK.QQ.enterPubAccountCard(Global.PUIN);
      }
    }
    

    如何获取 PUIN ?请移步至官方-公众号

    9、广告

    详细介绍请移步至官网-广告接入流程

    简单的封装与使用

    /**
     * 加载视频广告
     */
    function fetchVideoAd(videoType) {
        if (!videoType) {
            videoType = 0;
        }
        log("开始加载视频广告..." + videoType);
        BK.Advertisement.fetchVideoAd(videoType, function (retCode, msg, handle) {
            log("retCode:" + retCode + " msg:" + msg);
            //返回码0表示成功 
            if (retCode == 0) {
                Global.videoHandle = handle;
                //广告监听在业务逻辑中处理
            } else {
                log("拉取视频广告失败error:" + retCode + " msg:" + msg);
            }
        }.bind(this));
        log("加载了视频广告...");
    }
    
    /**
     * 加载条幅广告
     */
    function fetchBannerAd() {
        BK.Advertisement.fetchBannerAd(function (retCode, msg, bannerHandle) {
            log("retCode:" + retCode + " msg:" + msg);
            if (retCode == 0) {
                Global.bannerHandle = bannerHandle;
                bannerHandle.onClickContent(function () {
                    log("用户点击了落地页");
                });
                bannerHandle.onClickClose(function () {
                    log("用户点击了X关闭广告");
                });
            } else {
                log("fetchBannerAd failed. retCode:" + retCode);
            }
        }.bind(this));
    }
    
    function closeBannerAd() {
        log("关闭广告....");
        if (Global.bannerHandle) {
            Global.bannerHandle.close();
            Global.bannerHandle = undefined;
        }
    }
    
    function loadBannerAd() {
        if (cc.sys.platform == cc.sys.QQ_PLAY) {
            log("预加载Banner");
            fetchBannerAd();
        }
    }
    
    function loadVideoAd() {
        if (cc.sys.platform == cc.sys.QQ_PLAY) {
            log("预加载Video");
            fetchVideoAd();
        }
    }
    
    /**
     * @author Javen 
     * @copyright 2018-09-26 15:53:52 javendev@126.com 
     * @description 广告测试
     */
    let BKTools = require("BKTools");
    var Global = require("Global");
    cc.Class({
        extends: cc.Component,
    
        properties: {
    
        },
    
        // onLoad () {},
        btnClick(event, data) {
            BKTools.log("点击了>" + data);
            if (data == 'loadVideo') {
                //如果需要判断是否加载成功可以在封装的函数中添加回调
                BKTools.loadVideoAd();
            } else if (data == 'showVideo') {
                if (Global.videoHandle) {
                    this.jumpVideoAd();
                } else {
                    BKTools.log("无视频广告句柄");
                    BKTools.loadVideoAd();
                }
            } else if (data == 'loadBanner') {
                BKTools.loadBannerAd();
            } else if (data == 'showBanner') {
                if (Global.bannerHandle) {
                    this.showBannerAd();
                } else {
                    BKTools.log("无条幅广告句柄");
                    BKTools.loadBannerAd();
                }
            } else if (data == 'closeBanner') {
                BKTools.closeBannerAd();
            } else if (data == 'back') {
                cc.director.loadScene("welcome");
            }
        },
        jumpVideoAd() {
            let self = this;
            Global.videoHandle.jump();
            Global.videoHandle.setEventCallack(
                function (code, msg) {}.bind(this), //关闭游戏(不再使用不需要监听) 
                function (code, msg) {
                    if (code == 0) {
                        BKTools.log("达到看广告时长要求,可以下发奖励 endVide code:" + code + " msg:" + msg); //达到看广告时长要求,可以下发奖励 
                    } else {
                        BKTools.log("其他异常,比如播放视频是程序返回到后台");
                    }
                }.bind(this),
                function (code, msg) {
                    BKTools.log("关闭视频webview endVide code:" + code + " msg:" + msg); //关闭视频webview
                }.bind(this),
                function (code, msg) {
                    BKTools.log("开始播放视频 startVide code:" + code + " msg:" + msg); //开始播放视频
                }.bind(this));
        },
        showBannerAd() {
            Global.bannerHandle.show(function (succCode, msg, handle) {
                if (succCode == 0) {
                    BKTools.log("banner展示成功 home");
                } else {
                    BKTools.log("banner展示失败home msg:" + msg);
                }
            });
        },
        start() {
    
        },
    
        // update (dt) {},
    });
    

    10、源码

    文中涉及到的代码以及案例已上传至 Gitee-Brickengine_Guide

    到这里就介绍完了,个人能力有限如有错误欢迎指正如有遗漏欢迎补充。如有疑问欢迎留言一起交流讨论。

    相关文章

      网友评论

        本文标题:小游戏游戏入门到精通OR放弃?

        本文链接:https://www.haomeiwen.com/subject/svpyaftx.html