使用 Phaser3+Matter.js 实现 合成大西瓜 游戏

发布于 2025-01-23 01:30:43 字数 8133 浏览 0 评论 0

最近有一款 合成大西瓜 的小游戏有点火,试玩了一下,玩法比较简单,实现难度也不大,所以参照游戏原型自己实现了一下,游戏开发主要使用了 Phaser 游戏框架,本文主要分享游戏功能的具体实现,对框架使用的 API 不会做过多介绍。

玩法分析

首先简单介绍下游戏的玩法:控制水果从上方掉落,两个相同水果会合成一个更大的水果,最终合成一个大西瓜,效果展示:

游戏的玩法在于合理控制下落的点避免空间的浪费,在顶部有一条“死亡线”,当水果超过这个高度就结束,有点像俄罗斯方块,每合成一次水果都会得分,看谁能在游戏结束前或得更高的分数。

有多少种水果

游戏总共会出现 11 种水果,经过观察,前 5 种水果会随机掉落,后面的水果都是合成才会出现的

如何计算得分

每次合成新水果都会得分,按顺序的话第一种是 1 分,第二种 2 分,第 10 种就是 10 分,最后合成大西瓜后是额外得 100 分:

快速开始

游戏的基本玩法都已经清楚了,接下来就是开发了,首先我们通过 Githubclone 一个 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)

preloadcreate 都属于框架的生命周期, 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 仓库,感兴趣的可以自行查看。

参考文章

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

零時差

暂无简介

文章
评论
27 人气
更多

推荐作者

白云不回头

文章 0 评论 0

糖粟与秋泊

文章 0 评论 0

洋豆豆

文章 0 评论 0

泛滥成性

文章 0 评论 0

mb_2YvjCLvt

文章 0 评论 0

夜光

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文