接上篇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;
}
}
这样敌机的生成逻辑就做好了,我们来看下效果:
今天就到这里,主要是对象池的理解和使用。下期会做敌机的子弹发射,及一些细节上的优化。