phaser3之飞机大战(三)

2,917 阅读5分钟

接上篇phaser3之飞机大战(二) 本篇包含游戏场景的部分逻辑,包括使用对象池重新处理子弹生成逻辑,敌机生成逻辑,敌机与子弹的碰撞检测逻辑。

写在前面

该飞机大战素材来源于phaser小站:
www.phaser-china.com/tutorial-de…
原文使用phaser2进行实现,本文用phaser3将飞机大战重构一遍,在实战中学习一下phaser3的api
源码:github.com/YUPENG12138…

一、使用对象池对子弹生成逻辑进行重构

在上篇中,我们每过500ms都重新生成一个子弹,并且没有对已超出画布的子弹进行清空处理,短时间内看不出来什么问题,但运行久了,子弹较多的时候,会产生明显的卡顿。

我们把this.bullets.children.size打印出来看一下,在已经生成1万个子弹的时候,我们明显看到游戏运行已经非常卡顿了,这个时候我们就需要用到对象池。

这里借用一张phaser小站的图片做一下说明:

通过上面这个图,我们应该能理解到池的服用思想。接下来我们就用池来实现以下子弹的逻辑

// 首先我们创建一个生成子弹的class
const BulletClass = new Phaser.Class({
  Extends: Phaser.GameObjects.Sprite,
  initialize: function Bullet(scene) {
    Phaser.GameObjects.Sprite.call(this, scene, 0, 0, 'mybullet');
  },
  fire: function () {
    this.setActive(true);
    this.setVisible(true);
  },
  update: function () {
    if (this.y < -50) {
      // 这两个方法是用来处理当前sprite的激活状态和显示隐藏状态的
      // api: https://photonstorm.github.io/phaser3-docs/Phaser.GameObjects.Sprite.html#setActive__anchor
      this.setActive(false);
      this.setVisible(false);
    }
  }
});

然后在play场景中的create生命周期里,我们修改一下生成子弹组的逻辑

// 创建一个子弹组
// this.bullets = this.add.group();

// 这里我们传了两个值,classType表示我们使用哪个class来创建group中的sprite,runChildUpdate表示执行子元素中的update函数
// api: https://photonstorm.github.io/phaser3-docs/Phaser.Types.GameObjects.Group.html#.GroupConfig
this.bullets = this.add.group({
  classType: BulletClass,
  runChildUpdate: true,
});

// createMultiple是用来创建指定个数的游戏对象到group中,我们创建5个
// api: https://photonstorm.github.io/phaser3-docs/Phaser.GameObjects.Group.html#createMultiple__anchor
this.bullets.createMultiple({
  classType: BulletClass,
  key: 'mybullet',
  quantity: 5,
  active: false,
  visible: false,
});

play场景中的update生命周期里,我们修改一下刷新子弹的逻辑

// const bullet = this.add.sprite(this.plane.x, this.plane.y - this.plane.height / 2, 'mybullet');
// this.bullets.add(bullet);
// this.beforeTime = time;
// this.physics.add.existing(bullet);
// bullet.body.setVelocity(0, -300);

// getFirstDead方法使用来获取group中第一个active为false的元素
// api: https://photonstorm.github.io/phaser3-docs/Phaser.GameObjects.Group.html#getFirstDead__anchor
const bullet = this.bullets.getFirstDead(false);
if (bullet) {
    bullet.fire();
    bullet.setPosition(this.plane.x, this.plane.y - this.plane.height / 2);
    this.physics.add.existing(bullet);
    bullet.body.setVelocity(0, -300);
    this.beforeTime = time;
}

这样我们就实现了用池做子弹的回收,和分发。我们再从执行层面处理一下这块的逻辑。
首先我们在group中创建5个子弹,
然后每500ms拿一个子弹发射出去,
当子弹的y < -50 时,说明已经超出画布,我们将子弹的active设置为了false,将子弹回收回来,
这样我们就又可以通过getFirstDead拿到回收回来的子弹。
我们再把this.bullets.children.size打印出来看一下

我们可以看到这里this.bullets.children.size始终是5,现在即使运行再久也不会有卡顿了。

另一种生成子弹的方式

上面我们固定生成了5个子弹,我们还可以设计成,getFirstDead取不到的时候再生成一个子弹,让子弹的数量根据子弹的速度变化。

// 我们删掉下面这部分代码
/*
this.bullets.createMultiple({
  classType: BulletClass,
  key: 'mybullet',
  quantity: 5,
  active: false,
  visible: false,
});
*/

// 然后把getFirstDead的传参改为true
const bullet = this.bullets.getFirstDead(true);

我们再来看一下this.bullets.children.size的输出:

这里我们可以看到,在子弹数量为1,2的时候,因为取不到可用子弹所以都重新创建了子弹。当子弹为3的时候满足了我们当前速度的子弹需求,就没有再创建。

ps:对于对象池这块的实现,我们在开始的时候使用了class的方式去创建了sprite,是因为我们需要用到update函数去处理子弹超界的行为,我在api中没有查到相应的再group传参创建sprite的时候可以添加update方法的地方,所以采用了官方示例pools中的创建方法,如果有其他的实现方案欢迎留言讨论。

二、敌机生成逻辑,敌机碰撞检测

敌机的生成逻辑同样我们还是采用对象池的方式实现。在play场景中的create里添加。

// 我们有大中小三种飞机,创建一个factory用来返回不同的class
function EnemyFactory(key , gameHeight) {
  return new Phaser.Class({
    Extends: Phaser.GameObjects.Sprite,
    initialize: function Bullet(scene) {
      Phaser.GameObjects.Sprite.call(this, scene, 0, 0, key);
    },
    update: function () {
      if (this.y > gameHeight) {
        this.hide();
      }
    },
    show: function () {
      this.setActive(true);
      this.setVisible(true);
    },
    hide: function() {
      this.setActive(false);
      this.setVisible(false);
    }
  });
}

这里其实就是飞机使用的图片不同,我没有找到可以传多个图片key的方法,所以采用循环的形式去生成3个group。在play场景中的create里添加。

// 创建敌机组
['enemy1', 'enemy2', 'enemy3'].forEach((item) => {
  const EnemyClass = EnemyFactory(item, this.game.config.height);
  this[item] = this.add.group({
    classType: EnemyClass,
    runChildUpdate: true,
  });
});

// 设置敌机生成默认时间为0
this.enemyBeforeTime = 0;

// 设置我的子弹默认时间为0
this.myBulletBeforeTime = 0;

// 这里设置一下敌机组和飞机的碰撞检测
['enemy1', 'enemy2', 'enemy3'].forEach((item) => {
  this.physics.add.overlap(this.bullets, this[item], function (bullet, enemy) {
    bullet.destroy();
    enemy.destroy();
  }, null, this);
});

接下来我们在play场景中的update里添加刷新敌机的逻辑

// 引入敌机
if (time - this.enemyBeforeTime > 300) {

  // Phaser提供的Math对象,Between表示两者之间的整数
  // api: https://photonstorm.github.io/phaser3-docs/Phaser.Math.html
  const enemyIndex = Phaser.Math.Between(1, 3);
  const enemy = this[`enemy${enemyIndex}`].getFirstDead(true);
  if (enemy) {
    enemy.show();
    enemy.setOrigin(0.5, 0.5);
    enemy.setPosition(Phaser.Math.Between(0 + enemy.width, this.game.config.width - enemy.width), 0);
    this.physics.add.existing(enemy);
    enemy.body.setVelocity(0, 200);
    this.enemyBeforeTime = time;
  }
}

这样敌机的生成逻辑就做好了,我们来看下效果:

我们可以看到子弹和敌机都是在重复利用。没有一直在递增。

今天就到这里,主要是对象池的理解和使用。下期会做敌机的子弹发射,及一些细节上的优化。