共计 4599 个字符,预计需要花费 12 分钟才能阅读完成。
前言
最近有一款“合成大西瓜”的小游戏有点火,试玩了一下,玩法比较简单,实现难度也不大,所以参照游戏原型本人实现了一下,游戏开发次要应用了 Phaser 游戏框架,本文次要分享游戏性能的具体实现,对框架应用的 API 不会做过多介绍。
玩法剖析
首先简略介绍下游戏的玩法:管制水果从上方掉落,两个雷同水果会合成一个更大的水果,最终合成一个大西瓜,成果展现:
游戏的玩法在于正当管制着落的点防止空间的节约,在顶部有一条“死亡线”,当水果超过这个高度就完结,有点像俄罗斯方块,每合成一次水果都会得分,看谁能在游戏完结前或得更高的分数。
有多少种水果
游戏总共会呈现 11 种水果,通过察看,前 5 种水果会随机掉落,前面的水果都是合成才会呈现的
如何计算得分
每次合成新水果都会得分,按程序的话第一种是 1 分,第二种 2 分,第 10 种就是 10 分,最初合成大西瓜后是额定得 100 分:
疾速开始
游戏的根本玩法都曾经分明了,接下来就是开发了,首先咱们通过 Github
上clone
一个 phaser3 的脚手架来进行开发, 咱们首选 Typescript 版本的,对于这种简单的框架,类型提醒真的十分不便。
#! /bin/bash
$git clone git@github.com: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 能够学习,或者下一个爆款游戏就出自于你呢。
本我的项目源码曾经公布到 github 仓库,感兴趣的能够自行查看
参考文章
如何顺手合成大西瓜,把把 1000 分?手残必看的高分攻略来了!
Phaser
欢送关注凹凸实验室博客:aotu.io
或者关注凹凸实验室公众号(AOTULabs),不定时推送文章。