前段時間為了推廣一個新的公眾號,開發了一款名為「投籃達人」(已下線)的小遊戲,釋出上線之後得到了不錯的效果,成功為公眾號吸引了很多粉絲。下面就來跟大家分享下開發這款遊戲的歷程。
需求分析
遊戲本身其實很簡單,只是一個投籃遊戲,實現籃球的投射,籃筐運動,籃球與籃筐撞擊,遊戲結束後的排行榜記錄以及公眾號的識別。但由於遊戲是2D遊戲,卻有一個仿3D效果(籃球投出後離我們越來越遠以及穿透籃網),同時要讓籃球與籃筐進行碰撞檢測且模擬一個流暢自然的反彈效果,所以需要不斷填坑。
技術選型
考慮到開發速度,找一個成熟且文件齊全的HTML5 遊戲開發引擎就十分重要了,在國內大多數人用的都是Cocos2d-x-js 或Egret,phaser 或 Hilo 這些缺乏比較順手的開發工具就跳過了,Cocos 在網頁上的效能不太理想,所以最後選擇了Egret 2D
至於物理引擎方面,Egret 是推薦使用P2 引擎的,因為其效能相較於Box 2D 或者Matter.js 都好,不過缺點是缺乏中文文件,目前我也在進行翻譯中,地址戳 ? P2 中文文件
實現思路
偽3D 運動
- 近大遠小的顯示
以籃球初始位置(或底部)為原點確立一個視覺焦點,在籃球運動的過程中根據籃球在Y軸上的位置計算其縮放比例,計算公式如下:
``scale = (curY - startY) / ( endY - startY);``複製程式碼
另外,在物理世界中,任何剛體都是形狀和大小都保持不變的物理模型,也就意味著,我們能讓籃球的貼圖的縮放,但無法讓籃球的剛體進行縮放。
為了讓籃球和籃筐撞擊時,籃球的大小是準確的,我們可以根據上圖的縮放比例公式,計算籃球抵達籃筐(籃筐位置確定,比例必然是固定的)時的比例,在初始位置讓籃球的貼圖和剛體大小保持這一比例,當籃球運動到籃筐時大小就正好是其剛體的實際大小。
籃球與籃筐的互動
由於籃球上升時,在2D 的物理世界中,籃球和籃筐實際上是同一平面,球和籃筐的剛體預設會產生碰撞,同時,籃球的縱深需要在上升時大於籃筐而下落時小於籃筐;
為了解決第一個問題,可以利用P2 提供的碰撞分組來避免碰撞。
在P2 中,剛體需要一個或多個Shape 決定剛體的外形,而Shape 又具有collisionGroup 和 collisionMask 兩個屬性,前者決定了Shape 的碰撞分組,後者決定了Shape 會與哪些碰撞分組發生碰撞。需要注意的是,collisionGroup 的取值是 Math.pow(2,0) 到 Math.pow(2,32), 具體參考(Shape 的碰撞分組);
// 建立籃球剛體的形狀 ballShape = new p2.Circle({radius: GlobalData.ballRadius / factor}); // 設定籃球的碰撞分組 ballShape.collisionGroup = this.FLYBALL; // 這樣設定 collisionMask 籃球就能與籃筐互動,注意多個分組分隔的是 | 不是 || ballShape.collisionMask = this.BASKET | this.GROUND;複製程式碼
因此,在籃球上升時,設定其碰撞分組為FLYBALL,其collisionMask 為地面,此時籃球只會和地面碰撞,不與籃筐碰撞,當下落時,將其分組設定為DROPBALL,collisionMask 為地面和籃筐,此時籃球就會和籃筐發生碰撞了。
為了解決第二個問題,思路類似,上升時將籃球的上升設定最高,下落時與籃筐交換縱深值即可;
二維碼識別
在微信網頁中,經常需要提供長按二維碼識別功能,但二維碼需要是一張實際的圖片,內嵌於Canvas 的二維碼是不支援長按識別的,為了讓微信能夠識別到圖片,只需要在Canvas 上面覆蓋一張圖片即可。示意圖如下:
在設定圖片時,大小尺寸需要根據瀏覽器尺寸和Canvas 的實際尺寸進行計算縮放,才能讓圖片看起來像是和Canvas 一起的;
文字複製
遊戲需求中,需要使用者點選按鈕複製一段文字傳送給公眾號,但由於瀏覽器對文字複製的許可權不一樣,市面上的解決方案是使用ZeroClipBoard, 但開發時沒考慮到,使用的是 document.execCommand(‘copy’),而由於相容性,進行了fallback,當瀏覽器不支援時提示使用者長按選擇文字進行復制,這時也由於Canvas 不支援文字選擇,需要將文字跟二維碼一樣處理,放置於Canvas 之上;
效能優化
渲染優化
Egret每重新整理一幀的時候,會執行四步操作。在製作遊戲的時候應該清晰的記住四步操作都在幹什麼。
- 我們執行了一次EnterFrame,此時,引擎會執行遊戲中的邏輯。並且拋 EnterFrame事件。
- 執行一個clear。將上一幀的畫面全部擦除。
- Egret核心會遍歷你遊戲場景中的所有DisplayObject,並重新計算所有顯示物件的transform
- 如果你接觸過canvas,那麼你瞭解,這一步會將所有的影象全部draw到畫布中。
瞭解了Egret 的渲染機制之後,我們就知道該從哪裡下手了。
首先,由於每次都要遍歷DisplayObject, 對於不可見的物件都要進行清除,而建立物件也需要花費效能開銷,所以我們可以建立回收池對不可見的物件進行回收,這樣就可以減小一部分開銷;
其次,Canvas 檢測觸控點的方式是遍歷所有點來檢測點選的是哪個點,我們設定Touch 層的時候,儘可能的往上挪,而不是設定在物件上,這樣就可以減少遍歷所產生的開銷。
另外在遊戲開發過程中我手頭只有iOS 裝置,而測試過程時發現在安卓機上存在掉幀嚴重的現象;在將畫布尺寸從640 1136 縮小至480 800 後,卡頓狀況明顯好轉,猜測是由於畫布太多會造成渲染的開銷也變大,所以可以將畫布在可接受範圍內設定小一些,由瀏覽器縮放畫布,從而減少渲染開銷。
髒矩形渲染
對於複雜UI介面的情況,全屏重新整理演算法會每秒60次不停地重新整理所有UI物件,髒矩形渲染能夠檢測改變的物件,只渲染改變的物件。在有髒矩形渲染的情況下,哪改變繪製哪,極端情況直接跳過繪製,在複雜UI介面的情況非常容易達到滿幀。
Egret 預設開啟了髒矩形渲染,但當所有顯示物件都在不停的動,那這種情況下效能反而不好。此時我們需要通過關閉髒矩形渲染
this.stage.dirtyRegionPolicy = "off";
WebGL渲染
WebGL 通過增加 OpenGL ES 2.0 的一個 JavaScript 繫結,可以為 HTML5 Canvas 提供硬體 3D 加速渲染。 Egret Engine2D 提供了 WebGL 渲染模式。只需開啟 WebGL 渲染,就能獲得硬體加速。
Egret 在開啟 WebGL 渲染模式下,如果瀏覽器不支援將自動切換到Canvas 渲染模式下。