H5 遊戲開發:指尖大冒險

發表於2017-11-29

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

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

 

demo.png

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

該遊戲可以被劃分為三個層次,分別為景物層、階梯層、背景層,如下圖所示。

 

layers.png

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

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

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

而本文主要來講講以下幾點核心的技術內容:

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

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

一、無限迴圈滑動的實現

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

在使用者點選螢幕操控機器人時,兩側樹葉會隨著機器人前進的動作反向滑動,來營造出遊戲運動的效果。並且,由於該遊戲是無窮盡的,因此,需要對兩側樹葉實現迴圈向下滑動的動畫效果。

 

Leafheight.png

迴圈場景圖設計要求

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

然後按照以下步驟,我們就可以實現迴圈滑動:

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

 

leafmove.gif

無限迴圈滑動的實現

用虛擬碼描述如下:

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

二、隨機生成階梯的實現

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

無障礙階磚的規律

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

因為,在遊戲設定裡,使用者只能通過點選螢幕的左側或者右側區域來操控機器人的走向,那麼下一個無障礙階磚必然在當前階磚的左上方或者右上方。

 

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、1、2、3 代表其相對距離倍數,0 代表不存在障礙物階磚,1 代表相對一個階磚的距離,以此類推。

因此,障礙階磚集合對應的陣列就是包含 0、1、2、3 的隨機數陣列(下面簡稱障礙陣列)。例如,如果生成如下圖中的障礙階磚,那麼對應的隨機數陣列為 [0, 1, 1, 2, 0, 1, 3, 1, 0, 1]。

 

barrArr.png

障礙階磚對應的 0、1、2、3 隨機數

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

利用隨機演算法生成隨機陣列

根據階梯的生成規律,我們需要建立兩個陣列。

對於無障礙陣列來說,隨機數 0、1 的出現概率是均等的,那麼我們只需要利用 Math.random()來實現對映,用虛擬碼表示如下:

而對於障礙陣列來說,隨機數 0、1、2、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

障礙陣列值隨機生成過程

用虛擬碼表示如下:

對這種做法進行效能分析,其生成隨機數的時間複雜度為 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

 

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 畫素的高度。

其用虛擬碼表示如下:

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

 

barrPos.gif

障礙階磚的位置計算推導

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

其用虛擬碼表示如下:

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

三、自動掉落階磚的實現

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

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

而其處理的難點在於:

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

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

對於第一個問題,我們理所當然地想到從底層邏輯上的無障礙陣列和障礙陣列入手:判斷障礙階磚是否相鄰,可以通過同一個下標位置上的障礙陣列值是否為1,若為1那麼該障礙階磚與當前末端路徑的階磚相鄰。

但是,以此來判斷遠處的障礙階磚是否是在同一 y 軸方向上則變得很麻煩,需要對陣列進行多次遍歷迭代來推算。

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

 

autodrop1.png

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

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

處理的實現用虛擬碼表示如下:

掉落螢幕以外的階磚

那對於第二個問題——判斷階磚是否在螢幕以外,是不是也可以通過比較階磚的 y 軸位置值與螢幕底部y軸位置值的大小來解決呢?

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

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

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

 

autodrop2.png

掉落螢幕以外的階磚

所以,我們把思路從視覺渲染層面再轉回底層邏輯層面,通過檢測無障礙陣列的長度是否大於 9 進行處理即可,用虛擬碼表示如下:

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

後言

為什麼筆者要選擇這幾點核心內容來剖析呢?
因為這是我們經常在遊戲開發中經常會遇到的問題:

  • 怎樣處理遊戲背景迴圈?
  • 有 N 類物件,設第 i 類物件的出現概率為 P(X=i) ,如何實現產生滿足這樣概率分佈的隨機變數 X ?

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

這是本文希望能夠給大家在遊戲開發方面帶來一些啟發與思考的所在。最後,還是老話,行文倉促,若錯漏之處還望指正,若有更好的想法,歡迎留言交流討論!

另外,本文同時釋出在「H5遊戲開發」專欄,如果你對該方面的系列文章感興趣,歡迎關注我們的專欄。

參考資料

相關文章