[前端工坊] 微信小遊戲|萌狗冠軍之路,純乾貨分享!

文章來自微信公衆號:前端工坊(fe_workshop),不按期更新有趣、好玩的前端相關原創技術文章。若是喜歡,請關注公衆號:前端工坊
版權歸公衆號全部,轉載請註明出處。
做者:毛科 劉麒麟

**咱們作了一個小遊戲!快來看一下!純乾貨分享!
閱讀本文須要5分鐘,只需5分鐘,你就會跟我同樣,愛上這款遊戲~**前端

圖片描述

策劃上

這款遊戲具體的玩法是經過點擊屏幕左右區域來控制小狗的前進方向進行跳躍,而階梯是無窮盡的,若遇到障礙物或者踩空、或者小狗腳下的階梯隕落,遊戲失敗;這款遊戲提供指尖娛樂,考驗你們左右手的配合能力,和迅速反應能力,長時間訓練,就能得高分。json

技術上

「使用3D圖形引擎three.js隨機渲染階梯」canvas

階梯由一個個方塊隨機組成「無障礙物的階梯」和「有障礙物的階梯」,無障礙物的階梯隨機組成一條暢通無阻的路徑,有障礙物的階梯隨機生成世界盃32強旗子。小程序

方塊紋理的繪製:
直接看代碼,一個方塊由6個面組成
圖片描述後端

// 建立紋理
new THREE.MeshBasicMaterial({
    map: assets.getTexture("cubeWallpaper"),
    overdraw: true,
})

// 建立6個面
const materials = [
    new THREE.MeshBasicMaterial({
        map: assets.getTexture("cubeWallpaper"),
        overdraw: true,
    }),
    new THREE.MeshBasicMaterial({
        map: assets.getTexture("cubeWallpaper"),
        overdraw: true,
    }),
    new THREE.MeshBasicMaterial({
        map: assets.getTexture("cube"),
        overdraw: true,
    }),
    new THREE.MeshBasicMaterial({
        map: assets.getTexture("cubeWallpaper"),
        overdraw: true,
    }),
    new THREE.MeshBasicMaterial({
        map: assets.getTexture("cobeWallpaper2"),
        overdraw: true,
    }),
    new THREE.MeshBasicMaterial({
        map: assets.getTexture("cubeWallpaper"),
        overdraw: true,
            }),
]
// 建立盒子
const boxmat = new THREE.MeshFaceMaterial(materials)

「使用canvas繪製2D圖形,渲染排行榜」
圖片描述微信小程序

排行榜自己的功能實現並不複雜,主要是由於開放數據域的限制,顯示麻煩一些。
排行榜主要功能進行拆解後主要涉及如下功能點api

  • 上報用戶分數
  • 獲取用戶好友分數
  • 對好友分數進行排序
  • 獲取用戶好友頭像
  • 繪製排行榜
  • 增長前端的分頁功能
  • 獲取當前用戶本身的信息

微信的開放數據域
在微信小程序當中,當咱們須要獲取微信好友的關係鏈時,會受到一些限制。微信爲了保護本身的用戶關係鏈不被惡意獲取盜用,同時又想要創建開放的小程序生態,鞏固自身地位。在對信息的開放與封閉之間,微信的解決辦法是這樣的,經過創建一個開放的數據域,全部和微信好友關係相關的API一概被限制,只能在開放數據域當中使用。且獲取的數據,禁止以任何方式直接傳遞給主域,只能將數據繪製在一個離屏的shareCanvas,而主域經過將這個離屏的shareCavans繪製到上屏canvas上,從而達到展現微信好友關係鏈的數據。promise

在繪製排行榜的過程當中遇到的坑和解決方法:
主域能夠經過wx.postMessage向開放數據域發送消息通訊,開放數據域經過監聽主域的消息來決定什麼時機調用相關API並繪製相關數據。但由於實際的調用API以及繪製頁面和加載微信用戶頭像等等操做都是徹底異步的,而主域並不知道何時開放數據域完成了接口的調用,和圖像的繪製。緩存

解決辦法:當用戶觸發了 點擊排行榜按鈕以後,主域先繪製一個全透明的蒙層阻止用戶繼續操做交互。而後經過postMessage通知開放數據域子域,開放數據域子域接收到消息後,先判斷這是個什麼消息。好比是繪製排行榜ShowRankingList仍是遊戲結束GameOver,上報用戶分數。
若是是繪製排行榜,則先異步加載排行榜的素材資源,而後將黑色背景與排行榜素材先繪製到canvas上,而與此同時主域則每隔0.5秒就獲取一次shareCanvas將紋理繪製到主屏上。在開放數據域繪製完素材以後,會當即經過getFriendCloudStorage獲取微信好友數據,而後經過getTopDataList對數據進行解析處理,由於拿到的數據是沒有排序的,此時還會進行按分數進行排序。代碼以下微信

topDataList = Lodash.sortBy(topDataList, (item) => {
    return -item.score
})

獲得數據以後,先克隆一份,而後用slice切割爲6個元素傳遞給displayTopDataList,這個是上屏顯示用的6個好友數據。而後經過promiseTopDataList,該方法會將這6個元素的頭像進行異步的獲取。

這裏有幾個坑,一次並行請求3個以上的微信頭像時,網絡上很容易出現562錯誤,此時會致使頭像繪製失敗,程序上對頭像繪製失敗進行了容錯,若是獲取不到用戶頭像則顯示一個空白的頭像。但這始終不是個辦法,最後的解決辦法是按順序依次加載完頭像,而後返回。兩種實現代碼以下:
異步獲取全部用戶的頭像

const taskList = []
for (const item of topDataList) {
    taskList.push(this.promiseTopData(item))
}
return Promise.all(taskList)

按步列依次獲取用戶的頭像

return new Promise((resolve, reject) => {
    let index = 0
    let taskList = []
    let task = (index) => {
        const item = topDataList[index]
        this.promiseTopData(item).then((itemReady) => {
            taskList.push(itemReady)
            if (index < topDataList.length - 1) {
                index++
                task(index)
            } else {
                resolve(taskList)
            }
        }).catch((error) => {
            reject(error)
        })
    }
    task(index)
})

當資源準備好以後,調用drawImage方法開始排行榜數據繪製,這個就是普通的2d繪製了,沒有什麼太多的技巧,主要就是有一個初始的initTop值,元素的位置會相對該Top值偏移以此來進行定位。關於頭像畫圓,主要是經過建立一個cavnas,而後把頭像畫上去,再用arc剪裁,而後再把頭像紋理畫到shareCanvas上。

// 頭像剪裁成圓
const avatarCanvas = wx.createCanvas()
avatarCanvas.width = 80
avatarCanvas.height = 80
const avatarContext = avatarCanvas.getContext('2d')

avatarContext.save();
avatarContext.arc(40, 40, 40, 0, Math.PI * 2);
// 從畫布上裁剪出這個圓形
avatarContext.clip();
avatarContext.drawImage(topItem.avatarDOM, 0, 0, 80, 80)
avatarContext.restore();

// 實際的畫
shareContext.drawImage(avatarCanvas, 130, (initTop + (i * 105)))

有什麼方法能夠把開放數據域的數據帶出來?

目前嘗試有,給shareCanvas附加屬性,innerHTML,appendChild,data屬性,均沒有用,在開放數據域是個受限的環境,幾乎絕大部分api都沒法使用,包括localStorage等,也不能經過canvas的getDataURL把數據轉換成Image,之因此這樣的限制,是由於爲了防止部分人經過開放數據域獲取用戶信息以後,再用getDataURL拿到繪製的用戶數據,傳遞給後端API,作OCR圖像識別解析微信用戶關係,因此微信限制shareCanvas輸出成圖像,且限制,必須只能畫在主屏上面。而後再經過人工審覈機制,防範惡意小程序偷數據。

「生成並保存戰績頁」
圖片描述

把用戶玩遊戲的成果,包裝成一個專屬的宣傳卡片,是誘導並激發用戶分享意願的一個強有力的方法。咱們使用canvas結合微信小遊戲開發生態,保存當前用戶暱稱和當場遊戲結果生成不一樣的文案和圖片,完成了從畫布到截屏和存入相冊一系列動做。

微信小遊戲開發生態已經爲你打通了畫布到截屏到存入相冊這一系列動做。

在本遊戲中使用了 toTempFilePathsaveImageToPhotosAlbum 方法。這二者方法都掛載在wx對象下。

toTempFilePath, 將當前 Canvas 保存爲一個臨時文件,並生成相應的臨時文件路徑。

saveImageToPhotosAlbum,保存圖片到系統相冊。
核心代碼:

//  loadCanvas 爲咱們專門爲渲染戰績卡片而初始化的一個 2d canvas。

loadCanvas.toTempFilePath({
    fileType: 'jpg',
    success: function(res) {
        wx.saveImageToPhotosAlbum({
            filePath: res.tempFilePath,
            success: function() {
            }
        })
    }
})

注意事項:

saveImageToPhotosAlbum會彈出相冊受權彈框,記得處理失敗回調哦。

若是裏面要引用用戶相關信息,記得也要處理用戶不受權的狀況。

關於生成到相冊的圖片清晰度問題,經驗是,再用canvas繪製背景圖的時候,別用高清圖,都會被wx壓縮的。這個本身去壓縮紋理素材,這樣既不會觸發微信的壓縮,又比微信壓縮的效果好。

「遊戲聲音開關的控制」

音頻建立

let audio = new Audio(`${path}`);
    //path 爲音頻文件地址

音頻播放

audio.play();

音頻中止

audio.pause();

小遊戲有背景音樂/小狗撞擊障礙物失敗音樂/小狗踩空音樂/階梯隕落音樂/遊戲升級音樂5種不一樣類型的音頻文件。咱們的作法是統一將音頻文件集中預加載,並緩存到一個音頻庫的對象中,後續按需調用。

音頻庫對象中,咱們定義了, begin, change,off,on等方法,在全局須要的地方作統一調度,這樣可使得,全部音頻管理來自於一個music center,不會由於邏輯而紊亂。

在music center中,change,on,off均作了變量鎖,使得了全域的操控觸發,惟一相應。

for (let t of soundassets) {
    let audio = new Audio(assetpath + t.url);

    audio.autoplay = t.setting.autoplay;
    audio.loop = t.setting.loop;

    audio.addEventListener("load", () => {
        soundonload(audio, t);
        loaded++;
        onProgress(t.url, loaded, itesmtotal);
        if (loaded === itesmtotal) {
            onLoad();
        }
    });
    audio.addEventListener("error", (e) => {
        onError(assetpath + t.url);
    });
    itesmtotal++;
}

「遊戲難度方面」

遊戲的初版本,咱們的遊戲難度是每一層階梯以1.3s的速度隕落,每完成50s加大一倍難度的策略;小夥伴們反應遊戲可貴分低以後,咱們迅速的調整遊戲策略,把階梯隕落速度下降,而且改成每100s加大一倍難度的玩法。

BitmapFont遊戲數字的繪製
圖片描述

在遊戲當中常常有須要繪製一些圖形的文字數字等,這個時候可使用BitmapFont,也就是準備好文字與數字的紋理,並用TexturePack組成一張大圖及記錄圖片尺寸位置信息的font.json。在代碼當中加載這兩個資源,而後就能直接使用了。

在咱們的遊戲當中也涉及到須要繪製數字圖形分數的部分,首先是製做了一個用於處理分數的Score類,該類負責對分數進行記錄,清零以及管理和數字圖形的渲染。

當玩家分數變化時,咱們會調用render進行分數的繪製,先經過string pad,將數字型的分數補零爲5位數字字符串

_pad(num, n) {
    var len = num.toString().length;
    while (len < n) {
        num = "0" + num;
        len++;
    }
    return num;
}

打散字符串,並進行循環,更新對應位數的紋理

// 獲得當前數字的紋理
const texture =this.resources.getTexture(texturePath);
// 替換對應位置的material的紋理爲分數的紋理
this.score[`n${i}`].material.map = texture;

其它一些方法,reset用於分數清零,init負責對分數部件的及初始位數初始化

reset() {
    this.render(0)
}

遊戲體驗,請掃小程序碼:
若是你有更有趣的想法,歡迎留言區討論~
圖片描述

更多小遊戲,請關注微信公衆號:前端工坊
後續,咱們會推出更多純乾貨技術分享
圖片描述

相關文章
相關標籤/搜索