前言—
最近有一款“合成大西瓜”的小遊戲有點火,試玩了一下,玩法比較簡單,實現難度也不大,所以參照遊戲原型自己實現了一下,遊戲開發主要使用了 Phaser 遊戲框架,本文主要分享遊戲功能的具體實現,對框架使用的 API 不會做過多介紹。
玩法分析—
首先簡單介紹下游戲的玩法:控制水果從上方掉落,兩個相同水果會合成一個更大的水果,最終合成一個大西瓜,效果展示:
遊戲的玩法在於合理控制下落的點避免空間的浪費,在頂部有一條“死亡線”,當水果超過這個高度就結束,有點像俄羅斯方塊,每合成一次水果都會得分,看誰能在遊戲結束前獲得更高的分數。
有多少種水果
遊戲總共會出現 11 種水果,經過觀察,前 5 種水果會隨機掉落,後面的水果都是合成纔會出現的
如何計算得分
每次合成新水果都會得分,按順序的話第一種是 1 分,第二種 2 分,第 10 種就是 10 分,最後合成大西瓜後是額外得 100 分:
快速開始—
遊戲的基本玩法都已經清楚了,接下來就是開發了,首先我們通過Github
上clone
一個 phaser3 的腳手架[1]來進行開發,我們首選 Typescript 版本的,對於這種複雜的框架,類型提示真的非常方便。
#! /bin/bash
$git clone [email protected]:photonstorm/phaser3-typescript-project-template.git hexigua
$cd hexigua
$npm install
#啓動
$npm run watch
安裝依賴並啓動後,進入src/game.ts
,把原來的一些示例代碼刪掉,結果如下:
import 'phaser'
export default class Demo extends Phaser.Scene {
constructor () {
super('demo')
}
preload () {
}
create () {
}
}
const config = {
type: Phaser.AUTO,
backgroundColor: '#125555',
width: 800,
height: 600,
scene: Demo
}
const game = new Phaser.Game(config)
preload
和create
都屬於框架的生命週期,preload
主要用於預先下載資源,create
用於創建對象或事件。
修改 config 參數
修改遊戲初始化參數,指定使用 Matter.js 物理引擎,縮放模式通常設置爲等比例縮放模式Phaser.Scale.FIT
,
const config = {
type: Phaser.AUTO,
backgroundColor: '#ffe8a3', // 改爲遊戲的背景顏色
mode: Phaser.Scale.FIT, // 縮放模式
physics: {
default: 'matter', // 使用matterjs物理引擎
matter: {
gravity: {
y: 2
},
debug: true // 開啓調試
}
},
width: window.innerWidth,
height: window.innerHeight,
scene: Demo
}
加載資源
接下在preload
函數中加載準備好的圖片, 前面我已經準備好了 11 中類型水果的圖片,爲了方便開發,分別命名爲 1-11.png
preload () {
// 11種類型水果
for (let i = 1; i <= 11; i++) {
this.load.image(`${i}`, `assets/${i}.png`)
}
// 地板圖片
this.load.image('ground', 'assets/ground.png')
}
新建水果
加載資源後,我們先來創建遊戲中最主要的對象水果,遊戲中水果出現的情況有兩種,一種是在頂部落下,一種是碰撞後生成,除了位置不同,還有狀態和類型也不同,用一個表示如下:
出現位置 | 狀態 | 類型 |
---|---|---|
頂部 | 先靜止點擊後落下 | 前 5 種隨機 |
合成後的位置 | 非靜止 | 上一種+1 |
把不同的部分作爲參數,創建一個createFruite
函數:
/**
* 添加一個水果
* @param x 座標x
* @param y 座標y
* @param key 瓜的類型
* @param isStatic 是否靜止
*/
createFruite (x: number, y: number, isStatic = true, key?: string,) {
if (!key) {
// 頂部落下的瓜前5個隨機
key = `${Phaser.Math.Between(1, 5)}`
}
// 創建
const fruit = this.matter.add.image(x, y, key)
// 設置物理剛體
fruit.setBody({
type: 'circle',
radius: fruit.width / 2
}, {
isStatic,
label: key // 設置label 用於後續碰撞判斷是否同一類型
})
// 添加一個動畫效果
this.tweens.add({
targets: fruit,
scale: {
from: 0,
to: 1
},
ease: 'Back',
easeParams: [3.5],
duration: 200
})
return fruit
}
在create
函數中創建地板和生成水果
create(){
//設置邊界
this.matter.world.setBounds()
//添加地面
const groundSprite = this.add.tileSprite(WINDOW_WIDTH / 2, WINDOW_HEIGHT - 127 / 2, WINDOW_WIDTH, 127, 'ground')
this.matter.add.gameObject(groundSprite, { isStatic: true })
//初始化第一個一個水果
const x = WINDOW_WIDTH / 2
const y = WINDOW_HEIGHT / 10
let fruit = this.createFruite(x, y)
}
綁定點擊屏幕事件
接下來就是添加事件點擊屏幕的時候水果往下掉,並生成一個新的水果,新水果生成的時間點就設在落下後一秒鐘
create(){
...
//綁定pointerdown事件
this.input.on('pointerdown', (point) => {
if (this.enableAdd) {
this.enableAdd = false
//先x軸上移動到手指按下的點
this.tweens.add({
targets: fruit,
x: point.x,
duration: 100,
ease: 'Power1',
onComplete: () => {
//取消靜止狀態,讓物體掉落
fruit.setStatic(false)
//1s後生成新的水果
setTimeout(() => {
fruit = this.createFruite(x, y)
this.enableAdd = true
}, 1000);
}
})
}
}
}
物體碰撞事件
完成水果生成後,下一步就是添加碰撞事件,在phaser
中我們可以使用this.matter.world.on('collisionstart',fn)
來監聽物體的碰撞事件,fn
中會返回兩個相互碰撞的物體對象,我們根據前面設置的label
值就能判斷是否同一組,並進行後續操作
create(){
...
this.matter.world.on('collisionstart', (event, bodyA, bodyB) => {
const notXigua = bodyA.label !== '11' //非大西瓜
const same = bodyA.label === bodyB.label //相同水果
const live = !bodyA.isStatic && !bodyB.isStatic //非靜態
if (notXigua && same && live) {
//設置爲Static,這樣可以調整物體位置,使物體重合
bodyA.isStatic = true
bodyB.isStatic = true
const { x, y } = bodyA.position
const lable = parseInt(bodyA.label) + 1
//添加兩個動畫合併的動畫
this.tweens.add({
targets: bodyB.position,
props: {
x: { value: x, ease: 'Power3' },
y: { value: y, ease: 'Power3' }
},
duration: 150,
onComplete: () => {
// 物體銷燬
bodyA.gameObject.alpha = 0
bodyB.gameObject.alpha = 0
bodyB.destroy()
bodyA.destroy()
//合成新水果
this.createFruite(x, y, false, `${lable}`)
}
})
}
})
}
到這一步我們就基本完成了遊戲的核心部分,先看下效果:
合成後只是簡單的銷燬物體,有時間的話可以加入一些幀動畫之類的效果會更好,這裏就不加了,接下來繼續加上結束判定和得分。
結束判斷
前面提到,當落下的球超過指定的高度遊戲即結束,我們還是使用一個碰撞檢測來實現,創建一個矩形物體作爲我們的“結束線”,當矩形碰到物體的時候即表示空間已經不夠遊戲結束,還有一點需要特殊處理的是當我們點擊水果落下時是會碰到線的,這次碰撞需要過濾掉
create(){
...
//線創建在水果200px下的位置
const endLineSprite = this.add.tileSprite(WINDOW_WIDTH / 2, y + 200, WINDOW_WIDTH, 8, 'endLine' )
//設爲隱藏
endLineSprite.setVisible(false)
//設置物理效果
this.matter.add.gameObject(endLineSprite, {
//靜止
isStatic: true,
//傳感器模式,可以檢測到碰撞,但是不會對物體產品效果
isSensor: true,
//物體碰撞回調,
onCollideCallback: () => {
//落下時碰到線不觸發
if(this.enableAdd){
// 遊戲結束
console.log('end')
}
})
})
}
得分
得分的邏輯其實比較簡單了,在合成成功後加入代碼
let score = parseInt(bodyA.label)
this.score += score
//合成西瓜額外加100分
if (score === 10) {
this.score += 100
}
this.scoreText.setText(this.score)
//
create(){
//創建一個Text
this.scoreText = this.add.text(30, 20, `${this.score}`, { font: '90px Arial Black', color: '#ffe325' }).setStroke('#974c1e', 16)
}
最後—
到這裏遊戲的基礎玩法就開發結束了,藉助 Phaser 框架基本算能快速的開發遊戲的原型,如果你是新手對 H5 遊戲開發感興趣的話,那麼 Phaser 是一個非常容易上手的框架,api 的設計也比較友好,還有大量的 demo 可以學習,或許下一個爆款遊戲就出自於你呢。
本項目源碼[2]已經發布到 github 倉庫,感興趣的可以自行查看
參考文章—
如何隨手合成大西瓜,把把 1000 分?手殘必看的高分攻略來了!
Phaser[3]
註釋
腳手架: https://github.com/photonstorm/phaser3-typescript-project-template
[2]源碼: https://github.com/eijil/hexigua
[3]Phaser: https://phaser.io/
本文分享自微信公衆號 - 凹凸實驗室(AOTULabs)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。