咱們在 LeanCloud 成立五週年之際,發佈了一款名爲《LeanCloud 週年遊戲》的微信小遊戲。一方面咱們但願經過玩遊戲贏獎品的方式來回饋那些一直關注咱們的用戶,另外一方面咱們還但願經過一個實際的項目來說明白 LeanCloud Play 在遊戲開發方面的專長。html
《LeanCloud 週年遊戲》玩起來很簡單,參與者要在 15 秒內從迅速掉落的蛋糕和炸彈中點中儘量多的蛋糕來得分,蛋糕有好幾種,分值也不同,而誤點到炸彈就要扣分。遊戲一結束參與者能在排行榜中看到本身的名次,咱們給前 50 名都設置了獎品。git
沒玩過的朋友能夠搜索「LeanCloud 週年遊戲」體驗一下。程序員
這個項目開發週期大概爲一週,包含客戶端開發 2 天 + 服務端 1 天 + 調試 2 天。github
接下來我會從客戶端、服務端、做弊檢測三方面來梳理關鍵的技術細節,但願可以爲遊戲開發者或感興趣的朋友提供一些思路。小程序
在開發環境方面,客戶端主要使用了 Cocos Creator 來編輯構建「微信小遊戲」項目,服務端使用了 LeanCloud 的雲存儲、雲引擎和排行榜等服務,這些我都會在後面詳細介紹。微信小程序
先說引擎和編輯器。選取 Cocos Creator 的緣由是當在編輯器中構建不一樣平臺項目時,它的友好程度一直都比較好,並且 LeanCloud 也爲 Cocos Creator 作了適配。咱們遊戲的玩法比較簡單,無需過多解釋,因此接下來我會從客戶端資源、狀態機、暫停、LeanCloud SDK 和微信這些方面來展開描述。api
在遊戲運行過程當中,加載資源、實例化、銷燬 Node 等任何耗時操做均可能形成遊戲卡頓,影響體驗,特別是在低端機器上這種現象會更加明顯。因此咱們應該對資源進行預加載或者預實例化。瀏覽器
對於加載資源,一般是在場景切換時,對舊場景資源進行卸載,並對新場景資源進行預加載。緩存
在 Cocos 中,經過 cc.loader 能夠很方便地對單個資源、資源列表和資源目錄進行加載和緩存。而對於 Node 的實例化和銷燬,則要根據 Node 的生命週期進行區分。若是頻繁生成和銷燬的 Node,咱們能夠在加載階段經過對象池技術預先實例化一部分,這樣當在遊戲過程當中須要實例化 Node 時,就不須要實例化,而是從對象池中獲取;在不須要時,不進行銷燬操做,而是放回至對象池中等待下次使用。如彈幕遊戲中的飛機和子彈等。在咱們的遊戲中,咱們也對生成的蛋糕應用了「對象池」技術來避免遊戲中可能出現的卡頓。慶幸的是,Cocos 已經提供了這項功能。安全
在遊戲運行過程當中,遊戲主體(或角色)都會有不少的狀態,好比英雄的空閒、移動、攻擊、死亡等,所以一般會引入「狀態機」模式對遊戲對象進行設計。咱們爲搶蛋糕遊戲引入了 machina 庫做爲狀態機的框架,將整個遊戲主體劃分爲初始化、準備、進行中、結束四個狀態。
經過狀態機,咱們能夠更加清楚地跟蹤遊戲在過程當中的變化,並能夠經過事件在不一樣的狀態下作出不一樣的處理。
在遊戲過程當中,咱們常常會須要暫停遊戲,好比在搶蛋糕遊戲結束時再也不生成新的蛋糕和位置移動。
不一樣的遊戲引擎的暫停方式有所不一樣。經過 Cocos 的文檔,咱們找到了引擎提供的 cc.director.pause() / cc.director.resume() 接口,可是嘗試以後發現不少侷限性,好比在暫停以後 Widget 適配會不起做用,ScrollView 拖拽不回彈等狀況。
因而咱們決定經過 Component.update(dt) 生命週期和狀態機在遊戲中自行控制 Node 的更新。主要思路是在全局遊戲的 update() 生命週期裏,將更新事件交由狀態機,只有在遊戲進入「進行中」狀態時才處理更新事件,而在其餘狀態下則忽略更新事件。
更新過程爲先獲取場景下的全部 CakeCtrl 對象,調用自定義 onUpdate(dt) 方法進行更新(注意不是 update(dt) 生命週期方法)。
// 遊戲狀態:
play: {
...
update: function (dt) {
const cakeCtrls = this._scene.getComponentsInChildren(CakeCtrl);
cakeCtrls.forEach((cakeCtrl) => {
cakeCtrl.onUpdate(dt);
});
}
...
},
複製代碼
LeanCloud SDK 在大部分平臺都作了適配,能夠很方便地接入 LeanCloud 雲服務。
開發者在使用 Cocos Creator 時通常在瀏覽器進行調試開發,當完成後再發布到微信環境。但不一樣環境下 LeanCloud SDK 略有不一樣,爲了方便使用,你能夠經過封裝來隱藏加載不一樣版本 SDK 的細節。
好比在瀏覽器環境下,加載 leancloud-storage 庫;而發佈在微信小遊戲環境下,則加載 leancloud-storage/dist/av-weapp-min.js 庫。
if (cc.sys.browserType === cc.sys.BROWSER_TYPE_WECHAT_GAME) {
AV = require("leancloud-storage/dist/av-weapp-min.js");
} else {
AV = require("leancloud-storage");
}
複製代碼
另外,若是咱們須要使用微信受權登陸,爲了方便在瀏覽器下調試,咱們也能夠將 login() 封裝成不一樣的實現,統一邏輯層調用。
好比在瀏覽器環境下,使用帳號 + 密碼方式登陸;而在微信小遊戲環境下,使用微信受權登陸。
login() {
return new Promise((resolve, reject) => {
// 微信登陸
if (cc.sys.browserType === cc.sys.BROWSER_TYPE_WECHAT_GAME) {
AV.User.loginWithWeapp()
.then(user => {
...
})
.catch(error => {
reject(error);
});
} else {
// 使用默認帳號登陸,開發調試使用
AV.User.logIn("1if7jp52qx9771hllat1rvfqt", "123")
.then(user => {
...
})
.catch(error => {
reject(error);
});
}
});
},
複製代碼
更多關於 SDK 的文檔,請訪問 LeanCloud 文檔。
由於咱們的遊戲在排行榜中須要獲取玩家的頭像和暱稱,因此須要使用到微信的獲取用戶信息(暱稱、頭像)接口。這裏要吐槽一下,微信新版的 SDK 已經不容許用「彈框受權」來直接獲取信息了,而須要使用「固定類型的」微信小程序按鈕獲取。可是這一機制有對微信舊版 SDK 又不可用,因此咱們須要根據微信版本,肯定經過哪一種機制拿到微信受權。
若是是舊版本的微信,則能夠直接調用獲取用戶信息接口;而若是是新版本的微信,則須要渲染出微信受權按鈕,經過按鈕的點擊事件再獲取。這裏你可能須要面對小程序的渲染和 Cocos 的渲染機制不一致的問題。
因此,這裏還用到了一個小竅門——將微信小程序的受權按鈕設置爲透明,覆蓋到 Cocos 場景中的按鈕之上,當按鈕被點擊時,系統會先將點擊事件傳遞到微信小程序,在微信小程序回調中處理完成以後再交由遊戲中處理。
在服務端開發中,咱們主要使用了 LeanCloud 的雲存儲、雲引擎和排行榜服務。
在存儲方面,主要使用了 3 張表:
爲了保證遊戲安全,只有用戶信息是經過 LeanCloud 存儲 SDK 直接操做的。而遊戲相關的數據,都是經過 LeanCloud SDK 請求到雲引擎中處理後保存的。參考文檔
雲引擎是 LeanCloud 推出的服務端託管平臺。一般比較關鍵的數據,咱們推薦不要使用 SDK 直接操做,而是經過雲引擎進行操做。參考文檔
在搶蛋糕遊戲中,爲了保證遊戲安全,咱們在遊戲結束後並無在客戶端直接經過 LeanCloud SDK 上傳分數到排行榜,而是將遊戲參數發送到雲引擎,經過雲引擎分析後再肯定是否寫入到排行榜。具體流程:
排行榜是 LeanCloud Play 爲遊戲開發者提供的一項新的服務。它除了能提供方便的數據更新接口,還提供了排行榜成績更新、榜單管理等配置。參考文檔
在搶蛋糕遊戲中,除了使用常規的「更新玩家成績」以外,還用到了對做弊玩家進行「榜單移除」的操做。
...
// 提交分數
scoreInLeaderBoard = calcScoreInLeaderBoard(score);
AV.Leaderboard.updateStatistics(currentUser, {
free: scoreInLeaderBoard
})
複製代碼
/**
* 標記用戶做弊
* @param {*} user 用戶
*/
function markUser(user) {
let cheat = user.get("cheat") ? user.get("cheat") : 0;
console.log(`cheat: ${cheat}`);
cheat += 1;
user.set("cheat", cheat);
user.save();
if (cheat > MAX_CHEAT_COUNT) {
// 若是超過最大做弊次數,則清除榜單
AV.Leaderboard.deleteStatistics(user, ["score"])
.then(() => {
console.log(`remove ${user} statistics`);
})
.catch(console.error);
}
}
複製代碼
對於面向程序員製做的遊戲,咱們猜想到你們可能會經過技術手段來獲取更高分數。爲了增長你們破解的趣味性,咱們也提供了一些做弊檢測機制供你們突破——經過運行時做弊檢測和離線數據分析生成了最終的榜單數據。
具體過程:
服務端接收到參數後,對數據進行校驗。
運行時做弊檢測並不足以抵擋住廣大開發者破解的熱情,很快就有用戶梳理清楚了協議參數。因此在運行時檢測後,咱們又默默記下了用戶的參數,用於離線分析。
這類遊戲是沒辦法防住按鍵(觸摸)精靈的。若是經過「圖像識別 + 自動點擊腳本」能夠輕鬆點擊完全部的蛋糕並有效避開炸彈,則能夠經過上述檢測。
有人說若是服務端運算可不能夠,思路是屏幕點擊的座標交由服務端運算,可是對於按鍵精靈類的腳本仍是沒法避免,而且還會增長項目的開發量(服務端要對不一樣分辨率和座標作一些處理)。若是其餘同窗有辦法作更有效的檢測,但願能反饋到 LeanCloud 論壇,你們共同討論。
以上即是咱們使用本身的產品來開發遊戲的心得體會,但願能對你們有所幫助。