這裏說的小遊戲是QQ玩一玩,後面會寫微信小遊戲...
0、體驗QQ輕遊戲
- 需要使用Android手機
- 登錄手Q開啓釐米秀
- 側滑點擊人物形象或者選擇任意一好友點擊**「+」滑拔一下找到「釐米秀」**
搜索釐米秀 | 申請體驗資格 | 開啓釐米秀 |
---|---|---|
側滑 | 好友點「+」或者直接點擊人物 | 遊戲入口頁 |
1、平臺申請賬號
註冊很簡單,使用已有Q號登錄「釐米遊戲」開放平臺按照流程提交資料審覈即可 。開發者接入官方說明文檔
「釐米遊戲」 開放平臺註冊提交資料的同時會註冊一個相關聯的**「QQ服務號」**。遊戲中顯示的用戶信息是通過後臺靜默授權「QQ服務號」後再通過用戶相關的接口獲得,這點與微信公衆號以及微信小遊戲類似。
一句話概括:目前暫未對個人開放,現階段爲邀請碼模式。但如果你有好的IP資源或者優秀開發團隊是比較好申請的。
與 「微信小遊戲」 做比較目前來看最大的優勢就是
- 現階段遊戲中集成廣告所得廣告費用平臺不分成
- 遊戲評級高官方可以讓遊戲上中心化首頁推薦位
上線遊戲都需要 「遊戲自審自查報告」、「計算機軟件著作權登記證書」,如需內購需要提供 「廣電總局版號批文」 以及 「文化部備案信息」
2、環境搭建
如果使用了第三方引擎Mac
電腦非必須。
3、第三方引擎推薦
第三方引擎的實現方式爲基於 bricks 的 webGL 接口進行封裝,具有較高的靈活性,但渲染性能會欠缺。 如開發者對性能要求更高,推薦使用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
方法
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 [email protected]
* @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 [email protected]
* @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
完
到這裏就介紹完了,個人能力有限如有錯誤歡迎指正如有遺漏歡迎補充。如有疑問歡迎留言一起交流討論。