H5遊戲開發:指尖大冒險

by Tingglelaoo on 2017-11-28

在今年八月中旬,《指尖大冒險》SNS 遊戲誕生,其具體的玩法是經過點擊屏幕左右區域來控制機器人的前進方向進行跳躍,而階梯是無窮盡的,若遇到障礙物或者是踩空、或者機器人腳下的階磚隕落,那麼遊戲失敗。算法

筆者對遊戲進行了簡化改造,可經過掃下面二維碼進行體驗。數組

demo.png

《指尖大冒險》SNS 遊戲簡化版

該遊戲能夠被劃分爲三個層次,分別爲景物層、階梯層、背景層,以下圖所示。bash

layers.png

《指尖大冒險》遊戲的層次劃分

整個遊戲主要圍繞着這三個層次進行開發:dom

  • 景物層:負責兩側樹葉裝飾的渲染,實現其無限循環滑動的動畫效果。
  • 階梯層:負責階梯和機器人的渲染,實現階梯的隨機生成與自動掉落階磚、機器人的操控。
  • 背景層:負責背景底色的渲染,對用戶點擊事件監聽與響應,把景物層和階梯層聯動起來。

而本文主要來說講如下幾點核心的技術內容:性能

  1. 無限循環滑動的實現
  2. 隨機生成階梯的實現
  3. 自動掉落階磚的實現

下面,本文逐一進行剖析其開發思路與難點。學習

1、無限循環滑動的實現

景物層負責兩側樹葉裝飾的渲染,樹葉分爲左右兩部分,緊貼遊戲容器的兩側。優化

在用戶點擊屏幕操控機器人時,兩側樹葉會隨着機器人前進的動做反向滑動,來營造出遊戲運動的效果。而且,因爲該遊戲是無窮盡的,所以,須要對兩側樹葉實現循環向下滑動的動畫效果。動畫

Leafheight.png

循環場景圖設計要求

對於循環滑動的實現,首先要求設計提供可先後無縫銜接的場景圖,而且建議其場景圖高度或寬度大於遊戲容器的高度或寬度,以減小重複繪製的次數。ui

而後按照如下步驟,咱們就能夠實現循環滑動:this

  • 重複繪製兩次場景圖,分別在定位遊戲容器底部與在相對偏移量爲貼圖高度的上方位置。
  • 在循環的過程當中,兩次貼圖以相同的偏移量向下滑動。
  • 當貼圖遇到剛滑出遊戲容器的循環節點時,則對貼圖位置進行重置。

leafmove.gif

無限循環滑動的實現

用僞代碼描述以下:

12345678910111213141516複製代碼
// 設置循環節點transThreshold = stageHeight;// 獲取滑動後的新位置,transY是滑動偏移量lastPosY1 = leafCon1.y + transY;  lastPosY2 = leafCon2.y + transY;// 分別進行滑動if leafCon1.y >= transThreshold // 若遇到其循環節點,leafCon1重置位置  then leafCon1.y = lastPosY2 - leafHeight;  else leafCon1.y = lastPosY1;if leafCon2.y >= transThreshold // 若遇到其循環節點,leafCon2重置位置  then leafCon2.y = lastPosY1 - leafHeight;  else leafCon2.y = lastPosY2;複製代碼

在實際實現的過程當中,再對位置變化過程加入動畫進行潤色,無限循環滑動的動畫效果就出來了。

2、隨機生成階梯的實現

隨機生成階梯是遊戲的最核心部分。根據遊戲的需求,階梯由「無障礙物的階磚」和「有障礙物的階磚」的組成,而且階梯的生成是隨機性。

無障礙階磚的規律

其中,無障礙階磚組成一條暢通無阻的路徑,雖然整個路徑的走向是隨機性的,可是每一個階磚之間是相對規律的。

由於,在遊戲設定裏,用戶只能經過點擊屏幕的左側或者右側區域來操控機器人的走向,那麼下一個無障礙階磚必然在當前階磚的左上方或者右上方。

stairsguilv.png

無障礙路徑的生成規律

用 0、1 分別表明左上方和右上方,那麼咱們就能夠創建一個無障礙階磚集合對應的數組(下面簡稱無障礙數組),用於記錄無障礙階磚的方向。

而這個數組就是包含 0、1 的隨機數數組。例如,若是生成以下階梯中的無障礙路徑,那麼對應的隨機數數組爲 [0, 0, 1, 1, 0, 0, 0, 1, 1, 1]。

stairArr.png

無障礙路徑對應的 0、1 隨機數

障礙階磚的規律

障礙物階磚也是有規律而言的,若是存在障礙物階磚,那麼它只能出如今當前階磚的下一個無障礙階磚的反方向上。

根據遊戲需求,障礙物階磚不必定在鄰近的位置上,其相對當前階磚的距離是一個階磚的隨機倍數,距離範圍爲 1~3。

barrguilv.png

障礙階磚的生成規律

一樣地,咱們能夠用 0、一、二、3 表明其相對距離倍數,0 表明不存在障礙物階磚,1 表明相對一個階磚的距離,以此類推。

所以,障礙階磚集合對應的數組就是包含 0、一、二、3 的隨機數數組(下面簡稱障礙數組)。例如,若是生成以下圖中的障礙階磚,那麼對應的隨機數數組爲 [0, 1, 1, 2, 0, 1, 3, 1, 0, 1]。

barrArr.png

障礙階磚對應的 0、一、二、3 隨機數

除此以外,根據遊戲需求,障礙物階磚出現的機率是不均等的,不存在的機率爲 50% ,其相對距離越遠機率越小,分別爲 20%、20%、10%。

利用隨機算法生成隨機數組

根據階梯的生成規律,咱們須要創建兩個數組。

對於無障礙數組來講,隨機數 0、1 的出現機率是均等的,那麼咱們只須要利用 Math.random()來實現映射,用僞代碼表示以下:

1234複製代碼
// 生成隨機數i,min <= i < maxfunction getRandomInt(min, max) {   return Math.floor(Math.random() * (max - min) + min);}複製代碼

12345複製代碼
// 生成指定長度的0、1隨機數數組arr = [];for i = 0 to len  arr.push(getRandomInt(0,2));return arr;複製代碼

而對於障礙數組來講,隨機數 0、一、二、3 的出現機率分別爲:P(0)=50%、P(1)=20%、P(2)=20%、P(3)=10%,是不均等機率的,那麼生成無障礙數組的辦法即是不適用的。

那如何實現生成這種知足指定非均等機率分佈的隨機數數組呢?

咱們能夠利用機率分佈轉化的理念,將非均等機率分佈轉化爲均等機率分佈來進行處理,作法以下:

  1. 創建一個長度爲 L 的數組 A ,L 的大小從計算非均等機率的分母的最小公倍數得來。
  2. 根據非均等機率分佈 P 的狀況,對數組空間分配,分配空間長度爲 L * Pi ,用來存儲記號值 i 。
  3. 利用知足均等機率分佈的隨機辦法隨機生成隨機數 s。
  4. 以隨機數 s 做爲數組 A 下標,可獲得知足非均等機率分佈 P 的隨機數 A[s] ——記號值 i。

咱們只要反覆執行步驟 4 ,就可獲得知足上述非均等機率分佈狀況的隨機數數組——障礙數組。

結合障礙數組生成的需求,其實現步驟以下圖所示。

alg1_demo.png

障礙數組值隨機生成過程

用僞代碼表示以下:

1234567891011121314151617181920複製代碼
// 非均等機率分佈PiP = [0.5, 0.2, 0.2, 0.1]; // 獲取最小公倍數L = getLCM(P); // 創建機率轉化數組A = [];l = 0;for i = 0 to P.length  k = L * P[i] + l  while l < k    A[l] = i;    l++;// 獲取均等機率分佈的隨機數s = Math.floor(Math.random() * L);// 返回知足非均等機率分佈的隨機數return A[s];複製代碼

對這種作法進行性能分析,其生成隨機數的時間複雜度爲 O(1) ,可是在初始化數組 A 時可能會出現極端狀況,由於其最小公倍數有可能爲 100、1000 甚至是達到億數量級,致使不管是時間上仍是空間上佔用都極大。

有沒有辦法能夠進行優化這種極端的狀況呢?
通過研究,筆者瞭解到 Alias Method 算法能夠解決這種狀況。

Alias Method 算法有一種最優的實現方式,稱爲 Vose’s Alias Method ,其作法簡化描述以下:

  1. 根據機率分佈,以機率做爲高度構造出一個高度爲 1(機率爲1)的矩形。
  2. 根據構造結果,推導出兩個數組 Prob 數組和 Alias 數組。
  3. 在 Prob 數組中隨機取其中一值 Prob[i] ,與隨機生成的隨機小數 k,進行比較大小。
  4. 若 k <= Prob[i] ,那麼輸出符合指望機率分佈的隨機數爲 i,不然輸出的值是 Alias[i] 。


alg3_demo.png

對障礙階磚分佈機率應用 Vose’s Alias Method 算法的數組推導過程

若是有興趣瞭解具體詳細的算法過程與實現原理,能夠閱讀 Keith Schwarz 的文章《Darts, Dice, and Coins》。

根據 Keith Schwarz 對 Vose’s Alias Method 算法的性能分析,該算法在初始化數組時的時間複雜度始終是 O(n) ,並且隨機生成的時間複雜度在 O(1) ,空間複雜度也始終是 O(n) 。

suanfaxingneng.png

兩種作法的性能比較(引用 Keith Schwarz 的分析結果)

兩種作法對比,明顯 Vose’s Alias Method 算法性能更加穩定,更適合非均等機率分佈狀況複雜,遊戲性能要求高的場景。

在 Github 上,@jdiscar 已經對 Vose’s Alias Method 算法進行了很好的實現,你能夠到這裏學習。

最後,筆者仍選擇一開始的作法,而不是 Vose’s Alias Method 算法。由於考慮到在生成障礙數組的遊戲需求場景下,其機率是可控的,它並不須要特別考慮機率分佈極端的可能性,而且其代碼實現難度低、代碼量更少。

根據相對定位肯定階磚位置

利用隨機算法生成無障礙數組和障礙數組後,咱們須要在遊戲容器上進行繪製階梯,所以咱們須要肯定每一塊階磚的位置。

咱們知道,每一塊無障礙階磚必然在上一塊階磚的左上方或者右上方,因此,咱們對無障礙階磚的位置計算時能夠依據上一塊階磚的位置進行肯定。

stairPos.gif

無障礙階磚的位置計算推導

如上圖推算,除去根據設計稿測量肯定第一塊階磚的位置,第n塊的無障礙階磚的位置實際上只須要兩個步驟肯定:

  1. 第 n 塊無障礙階磚的 x 軸位置爲上一塊階磚的 x 軸位置偏移半個階磚的寬度,如果在左上方則向左偏移,反之向右偏移。
  2. 而其 y 位置則是上一塊階磚的 y 軸位置向上偏移一個階磚高度減去 26 像素的高度。

其用僞代碼表示以下:

123456複製代碼
// stairSerialNum表明的是在無障礙數組存儲的隨機方向值direction = stairSerialNum ? 1 : -1;// lastPosX、lastPosY表明上一個無障礙階磚的x、y軸位置tmpStair.x = lastPosX + direction * (stair.width / 2);tmpStair.y = lastPosY - (stair.height - 26);複製代碼

接着,咱們繼續根據障礙階磚的生成規律,進行以下圖所示推算。

barrPos.gif

障礙階磚的位置計算推導

能夠知道,障礙階磚必然在無障礙階磚的反方向上,須要進行反方向偏移。同時,若障礙階磚的位置相距當前階磚爲 n 個階磚位置,那麼 x 軸方向上和 y 軸方向上的偏移量也相應乘以 n 倍。

其用僞代碼表示以下:

12345678910複製代碼
// 在無障礙階磚的反方向oppoDirection = stairSerialNum ? -1 : 1;// barrSerialNum表明的是在障礙數組存儲的隨機相對距離n = barrSerialNum;// x軸方向上和y軸方向上的偏移量相應爲n倍if barrSerialNum !== 0  // 0 表明沒有  tmpBarr.x = firstPosX + oppoDirection * (stair.width / 2) * n,   tmpBarr.y = firstPosY - (stair.height - 26) * n;複製代碼

至此,階梯層完成實現隨機生成階梯。

3、自動掉落階磚的實現

當遊戲開始時,須要啓動一個自動掉落階磚的定時器,定時執行掉落末端階磚的處理,同時在任務中檢查是否有存在屏幕之外的處理,如有則掉落這些階磚。

因此,除了機器人碰障礙物、走錯方向踩空致使遊戲失敗外,若機器人腳下的階磚隕落也將致使遊戲失敗。

而其處理的難點在於:

  1. 如何判斷障礙階磚是相鄰的或者是在同一 y 軸方向上呢?
  2. 如何判斷階磚在屏幕之外呢?

掉落相鄰及同一y軸方向上的障礙階磚

對於第一個問題,咱們理所固然地想到從底層邏輯上的無障礙數組和障礙數組入手:判斷障礙階磚是否相鄰,能夠經過同一個下標位置上的障礙數組值是否爲1,若爲1那麼該障礙階磚與當前末端路徑的階磚相鄰。

可是,以此來判斷遠處的障礙階磚是不是在同一 y 軸方向上則變得很麻煩,須要對數組進行屢次遍歷迭代來推算。

而通過對渲染後的階梯層觀察,咱們能夠直接經過 y 軸位置是否相等來解決,以下圖所示。

autodrop1.png

掉落相鄰及同一 y 軸方向上的障礙階磚

由於不論是來自相鄰的,仍是同一 y 軸方向上的無障礙階磚,它們的 y 軸位置值與末端的階磚是必然相等的,由於在生成的時候使用的是同一個計算公式。

處理的實現用僞代碼表示以下:

12345678910111213複製代碼
// 記錄被掉落階磚的y軸位置值thisStairY = stair.y; // 掉落該無障礙階磚stairCon.removeChild(stair);// 掉落同一個y軸位置的障礙階磚barrArr = barrCon.children;for i in barrArr  barr = barrArr[i],  thisBarrY = barr.y;  if barr.y >= thisStairY // 在同一個y軸位置或者低於    barrCon.removeChild(barr);複製代碼

掉落屏幕之外的階磚

那對於第二個問題——判斷階磚是否在屏幕之外,是否是也能夠經過比較階磚的 y 軸位置值與屏幕底部y軸位置值的大小來解決呢?

不是的,經過 y 軸位置來判斷反而變得更加複雜。

由於在遊戲中,階梯會在機器人前進完成後會有回移的處理,以保證階梯始終在屏幕中心呈現給用戶。這會致使階磚的 y 軸位置會發生動態變化,對判斷形成影響。

可是咱們根據設計稿得出,一屏幕內最多能容納的無障礙階磚是 9 個,那麼只要把第 10 個之外的無障礙階磚及其相鄰的、同一 y 軸方向上的障礙階磚一併移除就能夠了。

autodrop2.png

掉落屏幕之外的階磚

因此,咱們把思路從視覺渲染層面再轉回底層邏輯層面,經過檢測無障礙數組的長度是否大於 9 進行處理便可,用僞代碼表示以下:

1234567891011複製代碼
// 掉落無障礙階磚stair = stairArr.shift();stair && _dropStair(stair);// 階梯存在數量超過9個以上的部分進行批量掉落if stairArr.length >= 9  num = stairArr.length - 9,  arr = stairArr.splice(0, num);  for i = 0 to arr.length    _dropStair(arr[i]);}複製代碼

至此,兩個難點都得以解決。

後言

爲何筆者要選擇這幾點核心內容來剖析呢?
由於這是咱們常常在遊戲開發中常常會遇到的問題:

  • 怎樣處理遊戲背景循環?
  • 有 N 類物件,設第 i 類物件的出現機率爲 P(X=i) ,如何實現產生知足這樣機率分佈的隨機變量 X ?

並且,對於階梯自動掉落的技術點開發解決,也可以讓咱們認識到,遊戲開發問題的解決能夠從視覺層面以及邏輯底層兩方面考慮,學會轉一個角度思考,從而將問題解決簡單化。

這是本文但願可以給你們在遊戲開發方面帶來一些啓發與思考的所在。最後,仍是老話,行文倉促,若錯漏之處還望指正,如有更好的想法,歡迎留言交流討論!

另外,本文同時發佈在「H5遊戲開發」專欄,若是你對該方面的系列文章感興趣,歡迎關注咱們的專欄。

參考資料

  • 《Darts, Dice, and Coins》
感謝您的閱讀,本文由 凹凸實驗室 版權全部。如若轉載,請註明出處:凹凸實驗室(https://aotu.io/notes/2017/11/28/h5_game_jumping/)
相關文章
相關標籤/搜索