H5 遊戲開發:推金幣

發表於2017-11-10

近期參與開發的一款「京東11.11推金幣贏現金」(已下線)小遊戲一經發布上線就在朋友圈引起大量傳播。看到大家玩得不亦樂乎,同時也引發不少網友激烈討論,有的說很帶勁,有的大呼被套路被耍猴(無奈臉),這都與我的預期相去甚遠。在相關業務資料呈呈上漲過程中,曾一度被微信「有關部門」盯上並要求做出調整,真是受寵若驚。接下來就跟大家分享下開發這款遊戲的心路歷程。

背景介紹

一年一度的雙十一狂歡購物節即將拉開序幕,H5 互動類小遊戲作為京東微信手Q營銷特色玩法,在今年預熱期的第一波造勢中,勢必要玩點新花樣,主要肩負著社交傳播和發券的目的。推金幣以傳統街機推幣機為原型,結合手機強大的能力和生態衍生出可玩性很高的玩法。

前期預研

在體驗過 AppStore 上好幾款推金幣遊戲 App 後,發現遊戲核心模型還是挺簡單的,不過 H5 版本的實現在網上很少見。由於團隊一直在做 2D 類互動小遊戲,在 3D 方向暫時沒有實際的專案輸出,然後結合此次遊戲的特點,一開始想挑戰用 3D 來實現,並以此專案為突破口,跟設計師進行深度合作,抹平開發過程的各種障礙。

推金幣 App

由於時間緊迫,需要在短時間內敲定方案可行性,否則專案延期人頭不保。在快速嘗試了 Three.js + Ammo.js 方案後,發現不盡人意,最終因為各方面原因放棄了 3D 方案,主要是不可控因素太多:時間上、設計及技術經驗上、移動端 WebGL 效能表現上,主要還是業務上需要對遊戲有絕對的控制,加上是第一次接手複雜的小遊戲,擔心專案無法正常上線,有點保守,此方案遂卒。

如果讀者有興趣的話可以嘗試下 3D 實現,在建模方面,首推 Three.js ,入手非常簡單,文件和案例也非常詳實。當然入門的話必推這篇 Three.js入門指南,另外同事分享的這篇 Three.js 現學現賣 也可以看看,這裡奉上粗糙的 推金幣 3D 版 Demo

技術選型

放棄了 3D 方案,在 2D 技術選型上就很從容了,最終確定用 CreateJS + Matter.js 組合作為渲染引擎和物理引擎,理由如下:

  • CreateJS 在團隊內用得比較多,有一定的沉澱,加上有老司機帶路,一個字「穩」;
  • Matter.js 身材纖細、文件友好,也有同事試玩過,完成需求綽綽有餘。

技術實現

因為是 2D 版本,所以不需要建各種模型和貼圖,整個遊戲場景通過 canvas 繪製,覆蓋在背景圖上,然後再做下機型適配問題,遊戲主場景就處理得差不多了,其他跟 3D 思路差不多,核心元素包含障礙物、推板、金幣、獎品和技能,接下來就分別介紹它們的實現思路。

障礙物

通過審稿確定金幣以及獎品的活動區域,然後把活動區域之外的區域都作為障礙物,用來限制金幣的移動範圍,防止金幣碰撞時超出邊界。這裡可以用 Matter.js 的 Bodies.fromVertices 方法,通過傳入邊界各轉角的頂點座標一次性繪製出形狀不規則的障礙物。 不過 Matter.js 在渲染不規則形狀時存在問題,需要引入 poly-decomp 做相容處理。

障礙物

推板

  • 建立:CreateJS 根據推板圖片建立 Bitmap 物件比較簡單,就不詳細講解了。這裡著重講下推板剛體的建立,主要是跟推板 Bitmap 資訊進行同步。因為推板視覺上表現為梯形,所以這裡用的梯形剛體,實際上方形也可以,只要能跟周圍障礙物形成封閉區域,防止出現縫隙卡住金幣即可,建立的剛體直接掛載到推板物件上,方便後續隨時提取(金幣的處理也是一樣),程式碼大致如下:
  • 伸縮:由於推板會沿著視線方向前後移動,為了達到近大遠小效果,所以需要在推板伸長和收縮過程中進行縮放處理,這樣也可以跟兩側的障礙物邊沿進行貼合,讓場景看起來更具真實感(偽 3D),當然金幣和獎品也需要進行同樣的處理。由於推板是自驅動做前後伸縮移動,所以需要對推板及其對應的剛體進行位置同步,這樣才會與金幣剛體產生碰撞達到推動金幣的效果。同時在外部改變(伸長技能)推板最大長度時,也需要讓推板保持均勻的縮放比而不至於突然放大/縮小,所以整個推板程式碼邏輯包含方向控制、長度控制、速度控制、縮放控制和同步控制,程式碼大致如下:
  • 遮罩:推板伸縮實際上是通過改變座標來達到位置上的變化,這樣存在一個問題,就是在其伸縮時必然會導致縮排的部分「溢位」邊界而不是被遮擋。

推板溢位

所以需要做遮擋處理,這裡用 CreateJS 的 mask 遮罩屬性可以很好的做「溢位」裁剪:

最終效果如下:

正常表現

金幣

按正常思路,應該在點選螢幕時就在出幣口建立金幣剛體,讓其在重力作用下自然掉落和回彈。但是在除錯過程中發現,金幣掉落後跟檯面上其他金幣產生碰撞會導致亂飛現象,甚至會卡到障礙物裡面去(原因暫未知),後面改成用 TweenJS 的 Ease.bounceOut 來實現金幣掉落動畫,讓金幣掉落變得更可控,同時儘量接近自然掉落效果。這樣金幣從建立到消失過程就被拆分成了三個階段:

  • 第一階段

點選螢幕從左右移動的出幣口建立金幣,然後掉落到檯面。需要注意的是,由於建立金幣時是通過 appendChild 方式加入到舞臺的,這樣金幣會非常有規律的在 z 軸方向上疊加,看起來非常怪異,所以需要隨機設定金幣的 z-index,讓金幣疊加更自然,虛擬碼如下:

  • 第二階段

由於金幣已經不需要重力場,所以需要設定物理世界的重力為 0,這樣金幣不會因為自身重量(需要設定重量來控制碰撞時移動的速度)做自由落體運動,安安靜靜的平躺在臺面上,等待跟推板、其他金幣和障礙物之間產生碰撞:

由於遊戲主要邏輯都集中這個階段,所以處理起來會稍微複雜些。真實情況下如果金幣掉落並附著在推板上後,會跟隨推板的伸縮而被帶動,最終在推板縮排到最短時被背後的牆壁阻擋而擠下推板,此過程看起來簡單但實現起來會非常耗時,最後因為時間上緊迫的這裡也做了簡化處理,就是不管推板是伸長還是縮排,都讓推板上的金幣向前「滑行」儘快脫離推板。一旦金幣離開推板則立即為其建立同步的剛體,為後續的碰撞做準備,這樣就完成了金幣的碰撞處理。

  • 第三階段

隨著金幣不斷的投放、碰撞和移動,最終金幣會從檯面的下邊沿掉落並消失,此階段的處理同第一階段,這裡就不重複了。

獎品

由於獎品需要根據業務情況進行控制,所以把它跟金幣進行了分離不做碰撞處理(內心是拒絕的),所以產生了「螃蟹步」現象,這裡就不做過多介紹了。

技能設計

寫好遊戲主邏輯之後,技能就屬於錦上添花的事情了,不過讓遊戲更具可玩性,想想金幣嘩啦啦往下掉的感覺還是很棒的。

抖動:這裡取了個巧,是給舞臺容器新增了 CSS3 實現的抖動效果,然後在抖動時間內讓所有的金幣的 y 座標累加固定值產生整體慢慢前移效果,由於安卓下支援系統震動 API,所以加了個彩蛋讓遊戲體驗更真實。

CSS3 抖動實現主要是參考了 csshake 這個樣式,非常有意思的一組抖動動畫集合。

JS 抖動 API

伸長:伸長處理也很簡單,通過改變推板移動的最大 y 座標值讓金幣產生更大的移動距離,不過細節上有幾點需要注意的地方,在推板最大 y 座標值改變之後需要保持移動速度不變,不然就會產生「瞬移」(不平滑)問題。

除錯方法

由於用了物理引擎,當在建立剛體時需要跟 CreateJS 圖形保持一致,這裡可以利用 Matter.js 自帶的 Render 為物理場景獨立建立一個透明的渲染層,然後覆蓋在 CreateJS 場景之上,這裡貼出大致程式碼:

設定剛體的 render 屬性為半透明色塊,方便觀察和除錯,這裡以推板為例:

效果如下,除錯起來還是很方便的:

除錯模式

效能/體驗優化

控制物件數量

隨著遊戲的持續檯面上累積的金幣數量會不斷增加,金幣之間的碰撞計算量也會陡增,必然會導致手機卡頓和發熱。這時就需要控制金幣的重疊度,而金幣之間重疊的區域大小是由金幣剛體的尺寸大小決定的,通過適當的調整剛體半徑讓金幣分佈得比較均勻,這樣可以有效控制金幣數量,提升遊戲效能。

安卓卡頓

一開始是給推板一個固定的速度進行伸縮處理,發現在 iOS 上表現流暢,但是在部分安卓機上卻顯得差強人意。由於部分安卓機型 FPS 比較低,導致推板在單位時間內位移比較小,表現出來就顯得卡頓不流暢。後面讓推板位移根據重新整理時間差進行遞增/減,保證不同幀頻機型下都能保持一致的位移,程式碼大致如下:

物件回收

這也是遊戲開發中常用的優化手段,通過回收從邊界消失的物件,讓物件得以複用,防止因頻繁建立物件而產生大量的記憶體消耗。

事件銷燬

由於金幣和獎品生命週期內使用了 Tween,當他們從螢幕上消失後記得移除掉:

至此,推金幣各個關鍵環節都有講到了,最後附上一張實際遊戲效果:

結語

感謝各位耐心讀完,希望能有所收穫,有考慮不足的地方歡迎留言指出。

相關資源

Three.js 官網

Three.js入門指南

Three.js 現學現賣

Matter.js 官網

Matter.js 2D 物理引擎試玩報告

感謝您的閱讀,本文由 凹凸實驗室 版權所有。如若轉載,請註明出處:凹凸實驗室(https://aotu.io/notes/2017/11/06/coindozer/
上次更新:2017-11-08 19:29:54

相關文章