前言
本次是與騰訊手機充值合作推出的活動,使用者通過氪金充值話費或者分享來獲得更多的投籃機會,根據最終的進球數排名來發放獎品。
使用者可以通過滑動拉出一條輔助線,根據輔助線長度和角度的不同將球投出,由於本次活動的開發週期短,在物理特性實現方面使用了物理引擎,所有本文的分享內容是如何結合物理引擎去實現一款投籃小遊戲,如下圖所示。
準備
此次我使用的遊戲引擎是 LayaAir,你也可以根據你的愛好和實際需求選擇合適的遊戲引擎進行開發,為什麼選擇該引擎進行開發 ,總的來說有以下幾個原因:
- LayaAir 官方文件、API、示例學習詳細、友好,可快速上手
- 除了支援 2D 開發,同時還支援 3D 和 VR 開發,支援 AS、TS、JS 三種語言開發
- 在開發者社群中提出的問題,官方能及時有效的回覆
- 提供 IDE 工具,內建功能有打包 APP、骨骼動畫轉換、圖集打包、SWF轉換、3D 轉換等等
物理引擎方面採用了 Matter.js,籃球、籃網的碰撞彈跳都使用它來實現,當然,還有其他的物理引擎如 planck.js、p2.js 等等,具體沒有太深入的瞭解,Matter.js 相比其他引擎的優勢在於:
- 輕量級,效能不遜色於其他物理引擎
- 官方文件、Demo 例子非常豐富,配色有愛
- API 簡單易用,輕鬆實現彈跳、碰撞、重力、滾動等物理效果
- Github Star 數處於其他物理引擎之上,更新頻率更高
開始
一、初始化遊戲引擎
首先對 LayaAir 遊戲引擎進行初始化設定,Laya.init
建立一個 1334×750 的畫布以 WebGL 模式去渲染,渲染模式下有 WebGL 和 Canvas,使用 WebGL 模式下會出現鋸齒的問題,使用 Config.isAntialias
抗鋸齒可以解決此問題,並且使用引擎中自帶的多種螢幕適配 screenMode
。
如果你使用的遊戲引擎沒有提供螢幕適配,歡迎閱讀另一位同事所寫的文章【H5遊戲開發:橫屏適配】。
1 2 3 4 5 6 7 8 |
... Config.isAntialias = true; // 抗鋸齒 Laya.init(1334, 750, Laya.WebGL); // 初始化一個畫布,使用 WebGL 渲染,不支援時會自動切換為 Canvas Laya.stage.alignV = 'top'; // 適配垂直對齊方式 Laya.stage.alignH = 'middle'; // 適配水平對齊方式 Laya.stage.screenMode = this.Stage.SCREEN_HORIZONTAL; // 始終以橫屏展示 Laya.stage.scaleMode = "fixedwidth"; // 寬度不變,高度根據螢幕比例縮放,還有 noscale、exactfit、showall、noborder、full、fixedheight 等適配模式 ... |
二、初始化物理引擎、加入場景
然後對 Matter.js 物理引擎進行初始化,Matter.Engine
模組包含了建立和處理引擎的方法,由引擎執行這個世界,engine.world
則包含了用於建立和操作世界的方法,所有的物體都需要加入到這個世界中,Matter.Render
是將例項渲染到 Canvas 中的渲染器。
enableSleeping
是開啟剛體處於靜止狀態時切換為睡眠狀態,減少物理運算提升效能,wireframes
關閉用於除錯時的線框模式,再使用 LayaAir 提供的 Laya.loading
、new Sprite
載入、繪製已簡化的場景元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
... this.engine; var world; this.engine = Matter.Engine.create({ enableSleeping: true // 開啟睡眠 }); world = this.engine.world; Matter.Engine.run(this.engine); // Engine 啟動 var render = LayaRender.create({ engine: this.engine, options: { wireframes: false, background: "#000" } }); LayaRender.run(render); // Render 啟動 ... |
1 2 3 4 5 6 7 |
... // 加入背景、籃架、籃框 var bg = new this.Sprite(); Laya.stage.addChild(bg); bg.pos(0, 0); bg.loadImage('images/bg.jpg'); ... |
三、畫出輔助線,計算長度、角度
投球的力度和角度是根據這條輔助線的長短角度去決定的,現在我們加入手勢事件 MOUSE_DOWN
、MOUSE_MOVE
、MOUSE_UP
畫出輔助線,通過這條輔助線起點和終點的 X、Y 座標點再結合兩個公式: getRad
、getDistance
計算出距離和角度。
1 2 3 4 5 6 7 |
... var line = new this.Sprite(); Laya.stage.addChild(line); Laya.stage.on(this.Event.MOUSE_DOWN, this, function(e) { ... }); Laya.stage.on(this.Event.MOUSE_MOVE, this, function(e) { ... }); Laya.stage.on(this.Event.MOUSE_UP, this, function(e) { ... }); ... |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
... getRad: function(x1, y1, x2, y2) { // 返回兩點之間的角度 var x = x2 - x1; var y = y2 - x2; var Hypotenuse = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); var angle = x / Hypotenuse; var rad = Math.acos(angle); if (y2 < y1) { rad = -rad; } return rad; }, getDistance: function(x1, y1, x2, y2) { // 計算兩點間的距離 return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); } ... |
四、生成籃球施加力度
大致初始了一個簡單的場景,只有背景和籃框,接下來是加入投籃。
每次在 MOUSE_UP
事件的時候我們就生成一個圓形的剛體, isStatic: false
我們要移動所以不固定籃球,並且設定 density
密度、restitution
彈性、剛體的背景 sprite
等屬性。
將獲得的兩個值:距離和角度,通過 applyForce
方法給生成的籃球施加一個力,使之投出去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
... addBall: function(x, y) { var ball = Matter.Bodies.circle(500, 254, 28, { // x, y, 半徑 isStatic: false, // 不固定 density: 0.68, // 密度 restitution: 0.8, // 彈性 render: { visible: true, // 開啟渲染 sprite: { texture: 'images/ball.png', // 設定為籃球圖 xOffset: 28, // x 設定為中心點 yOffset: 28 // y 設定為中心點 } } }); } Matter.Body.applyForce(ball, ball.position, { x: x, y: y }); // 施加力 Matter.World.add(this.engine.world, [ball]); // 新增到世界 ... |
五、加入其他剛體、軟體
現在,已經能順利的將籃球投出,現在我們還需要加入一個籃球網、籃框、籃架。
通過 Matter.js 加入一些剛體和軟體並且賦予物理特性 firction
摩擦力、frictionAir
空氣摩擦力等, visible: false
表示是否隱藏,collisionFilter
是過濾碰撞讓籃球網之間不產生碰撞。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
... addBody: function() { var group = Matter.Body.nextGroup(true); var netBody = Matter.Composites.softBody(1067, 164, 6, 4, 0, 0, false, 8.5, { // 籃球網 firction: 1, // 摩擦力 frictionAir: 0.08, // 空氣摩擦力 restitution: 0, // 彈性 render: { visible: false }, collisionFilter: { group: group } }, { render: { lineWidth: 2, strokeStyle: "#fff" } }); netBody.bodies[0].isStatic = netBody.bodies[5].isStatic = true; // 將籃球網固定起來 var backboard = Matter.Bodies.rectangle(1208, 120, 50, 136, { // 籃板剛體 isStatic: true, render: { visible: true } }); var backboardBlock = Matter.Bodies.rectangle(1069, 173, 5, 5, { // 籃框邊緣塊 isStatic: true, render: { visible: true } }); Matter.World.add(this.engine.world, [ // 四周牆壁 ... Matter.Bodies.rectangle(667, 5, 1334, 10, { // x, y, w, h isStatic: true }), ... ]); Matter.World.add(this.engine.world, [netBody, backboard, backboardBlock]); } |
六、判斷進球、監聽睡眠狀態
通過開啟一個 tick
事件不停的監聽球在執行時的位置,當到達某個位置時判定為進球。
另外太多的籃球會影響效能,所以我們使用 sleepStart
事件監聽籃球一段時間不動後,進入睡眠狀態時刪除。
1 2 3 4 5 6 7 8 9 10 11 12 |
... Matter.Events.on(this.engine, 'tick', function() { countDown++; if (ball.position.x > 1054 && ball.position.x < 1175 && ball.position.y > 170 && ball.position.y < 180 && countDown > 2) { countDown = 0; console.log('球進了!'); } }); Matter.Events.on(ball, 'sleepStart', function() { Matter.World.remove(This.engine.world, ball); }); ... |
到此為止,通過藉助物理引擎所提供的碰撞、彈性、摩擦力等特性,一款簡易版的投籃小遊戲就完成了,也推薦大家閱讀另一位同事的文章【H5遊戲開發】推金幣 ,使用了 CreateJS + Matter.js 的方案,相信對你仿 3D 和 Matter.js 的使用上有更深的瞭解。
最後,此次專案中只做了一些小嚐試,Matter.js 能實現的遠不止這些,移步官網發現更多的驚喜吧,文章的完整 Demo 程式碼可【點選這裡】。
如果對「H5遊戲開發」感興趣,歡迎關注我們的專欄。