接上篇phaser3之飞机大战(三)
本篇为飞机大战的结尾,包含内容:敌机发射子弹、飞机、敌机爆炸效果、积分、结束后重新开始场景、添加奖牌机制、添加音效。
项目线上链接: yupeng.pro/phaser3/pla…
写在前面
该飞机大战素材来源于phaser小站:
www.phaser-china.com/tutorial-de…
原文使用phaser2进行实现,本文用phaser3将飞机大战重构一遍,在实战中学习一下phaser3的api
源码:github.com/YUPENG12138…
一、敌机发射子弹
敌机发射子弹的逻辑和我方飞机发射子弹的逻辑基本相同,我们首先还是要创建一个敌机的子弹组,在敌机需要子弹的时候在组里取。
// 首先添加一个创建子弹的方法
function EnemyBulletClass(gameHeight) {
return new Phaser.Class({
Extends: Phaser.GameObjects.Sprite,
initialize: function Bullet(scene) {
Phaser.GameObjects.Sprite.call(this, scene, 0, 0, 'bullet');
},
update: function () {
if (this.y > gameHeight) {
this.hide();
}
},
fire: function () {
this.setActive(true);
this.setVisible(true);
},
hide: function() {
this.setActive(false);
this.setVisible(false);
}
});
}
// 然后在play场景create生命周期里创建子弹组
// 创建敌机子弹组
const EnemyBullteClass = EnemyBulletClass(this.game.config.height);
this.enemyBullets = this.add.group({
classType: EnemyBullteClass,
runChildUpdate: true,
});
敌机子弹组创建好了,接下来就是需要处理敌机发射子弹的逻辑
// 创建敌机组
['enemy1', 'enemy2', 'enemy3'].forEach((item) => {
// 在创建敌机的方法里 我们新增一个当前子弹组的参数`this.enemyBullets`
const EnemyClass = EnemyFactory(item, this.game.config.height, this.enemyBullets);
this[item] = this.add.group({
classType: EnemyClass,
runChildUpdate: true,
});
});
// 然后我们在EnemyFactory中取接收参数,并处理发射子弹逻辑。
function EnemyFactory(key , gameHeight, enemyBullets) {
return new Phaser.Class({
Extends: Phaser.GameObjects.Sprite,
initialize: function Bullet(scene) {
Phaser.GameObjects.Sprite.call(this, scene, 0, 0, key);
// 初始化这里我们给enemybuttle加几个属性,bulletSpeed子弹速度,life敌机生命,enemyButtelBeforeTime上一个子弹发射的时间,index不同飞机的标识key
// 这里我们根据飞机大小设定,飞机越大发射子弹的时间间隔越小
this.bulletSpeed = 1000 * (4 - key.replace('enemy', ''));
this.life = key.replace('enemy', '');
this.enemyButtelBeforeTime = 0;
this.index = key.replace('enemy', '');
},
update: function () {
// 在update里, 我们同样拿一下当前的时间与上次的时间做一下比对
const time = new Date().getTime();
if (this.y > gameHeight) {
this.hide();
}
// 当时间捡个超过发射速度的时候
if (time - this.enemyButtelBeforeTime >= this.bulletSpeed) {
// 我们在敌机子弹组里取一个子弹出来 这里的逻辑和飞机发射子弹基本一致
const bullet = enemyBullets.getFirstDead(true);
if (bullet) {
bullet.fire();
bullet.setPosition(this.x, this.y + this.height / 2);
this.scene.physics.add.existing(bullet);
// 这里我们设置子弹的飞行速度,飞机越大,子弹的飞行速度越慢
bullet.body.setVelocity(0, 100 * (4 - key.replace('enemy', '')));
this.enemyButtelBeforeTime = time;
}
}
},
show: function () {
this.setActive(true);
this.setVisible(true);
},
hide: function() {
this.setActive(false);
this.setVisible(false);
}
});
}
接下来我们添加一下碰撞检测,包括我方子弹和敌方子弹碰撞,我方飞机和敌方子弹碰撞
// play场景create生命周期里
this.physics.add.overlap(this.bullets, this.enemyBullets, function (bullet, enemyBullet) {
bullet.destroy();
enemyBullet.destroy();
}, null, this);
this.physics.add.overlap(this.enemyBullets, this.plane, function (enemyBullet, plane) {
plane.destroy();
this.gameOver = true;
}, null, this);
// 一些其他的处理
// 引入我的子弹 这里的判断加了this.gameOver 在游戏结束的时,我方飞机就不再发射子弹
if (time - this.myBulletBeforeTime > 200 && !this.gameOver) {
const bullet = this.bullets && this.bullets.getFirstDead(true);
...
}
// 引入敌机 这里我修改了下敌机引入的速度
if (time - this.enemyBeforeTime > 800) {
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);
// 敌机飞行的速度也根据不同的飞机做了限制,越大非得越慢
enemy.body.setVelocity(0, 50 * (4 - enemyIndex));
this.enemyBeforeTime = time;
}
}
敌机发射子弹的效果就做好了,我们看一下效果:
二、我方飞机、敌机爆炸效果
爆炸的效果其实就是一个帧动画效果,且我们不需要这个动画效果有什么移动,碰撞检测之类的原有敌机的逻辑,所以可以在敌机消失的时候,再引入一个精灵专门用来展示帧动画效果,然后在展示完效果之后消失就可以了
// 首先还是先把需要的帧图片引入进来
if (key === 'explode1') {
this.load.spritesheet(key, require(`./${assetsMap[key]}`), {
frameWidth: 20,
frameHeight: 20,
});
continue;
}
if (key === 'explode2') {
this.load.spritesheet(key, require(`./${assetsMap[key]}`), {
frameWidth: 30,
frameHeight: 30,
});
continue;
}
if (key === 'explode3') {
this.load.spritesheet(key, require(`./${assetsMap[key]}`), {
frameWidth: 50,
frameHeight: 50,
});
continue;
}
if (key === 'myexplode') {
this.load.spritesheet(key, require(`./${assetsMap[key]}`), {
frameWidth: 40,
frameHeight: 40,
});
continue;
}
接下来创建飞机爆炸帧动画
// 创建我方飞机爆炸帧动画,我方飞机爆炸循环一次 速度慢点
this.anims.create({
key: 'planeBoom',
frames: this.anims.generateFrameNumbers('myexplode', {
start: 0,
end: 3
}),
frameRate: 2,
repeat: 1
});
// 创建敌机组
['enemy1', 'enemy2', 'enemy3'].forEach((item) => {
const EnemyClass = EnemyFactory(item, this.game.config.height, this.enemyBullets);
this[item] = this.add.group({
classType: EnemyClass,
runChildUpdate: true,
});
const key = item.replace('enemy', '');
// 创建敌机爆炸帧动画 敌方飞机爆炸不循环 速度快点
this.anims.create({
key: `enemyBoom${key}`,
frames: this.anims.generateFrameNumbers(`explode${key}`, {
start: 0,
end: 2
}),
frameRate: 5,
repeat: 0
});
});
下面我们处理飞机爆炸的调用时机
我方飞机在与
// 敌方飞机在与我方子弹碰撞时进行爆炸,在生成敌机时我们给飞机挂载了life生命字段,大飞机3点 小飞机1点
['enemy1', 'enemy2', 'enemy3'].forEach((item) => {
this.physics.add.overlap(this.bullets, this[item], function (bullet, enemy) {
bullet.destroy();
// 检测碰撞后先减生命,发现不够了再让敌机消失,调用爆炸帧动画
enemy.life = enemy.life - 1;
if (enemy.life <= 0) {
enemy.destroy();
const key = item.replace('enemy', '');
// 我们在敌机消失的位置上新增加一个精灵,用来展示帧动画
const enemyFrame = this.add.sprite(enemy.x, enemy.y, `explode${key}`);
enemyFrame.anims.play(`enemyBoom${key}`);
// 这里遇到一个新的api,用来监听动画执行结束
// api: https://photonstorm.github.io/phaser3-docs/Phaser.Animations.Events.html#event:SPRITE_ANIMATION_COMPLETE
// once 表示只执行一次
// api: https://photonstorm.github.io/phaser3-docs/Phaser.Events.EventEmitter.html#once
enemyFrame.once('animationcomplete', function() {
enemyFrame.destroy();
})
}
}, null, this);
});
// 我方飞机爆炸效果
this.physics.add.overlap(this.enemyBullets, this.plane, function (enemyBullet, plane) {
plane.destroy();
this.gameOver = true;
// 在与敌方子弹碰撞时展示
const myPlaneFrame = this.add.sprite(plane.x, plane.y, 'myexplode');
myPlaneFrame.anims.play('planeBoom');
myPlaneFrame.once('animationcomplete', function() {
myPlaneFrame.destroy();
})
}, null, this);
我们来看一下爆炸和敌机生命效果
三、积分
在敌机被击毁时,我们添加分数
// 添加文本 将原来生成的文本存起来,再存一个分数字段
this.scoreText = this.add.text(0, 0, 'Score: 0', { color: '#ff0000', fontSize: '16px' });
this.score = 0;
...
// 在检测敌机碰撞里设置
this.score = +key + this.score;
this.scoreText.setText(`Score: ${this.score}`, this.score);
四、结束之后重新开始场景
这个场景基本和开始场景一致,多一个展示积分的栏位。 代码上的修改可以看这个commit,我就不细说了。 结束之后重新开始场景
主要的逻辑就是在我方飞机爆炸完之后,跳转场景这里有一块要注意,就是怎么在另一个场景拿到上一个场景的数据
// start方法接收第二个参数,可以给下个场景传参 这里要注意,如果只是传一个值,这个值为0的时候,会默认接收一个`{}`,所以我们传值还是以对象的方式去传
this.scene.start('restart', {
score: this.score,
});
// 接收的场景在create生命周期函数里接收传值
create(data) {
...
}
我们来看一下效果:
五、添加奖牌机制
现在飞机大战基本就完成了,但我方飞机只有一点生命,一次只能发射一个子弹,我们可以加入奖牌机制,丰富游戏的玩法。
// 我们还是先创建个生成奖牌的方法
const AwardClass = new Phaser.Class({
Extends: Phaser.GameObjects.Sprite,
initialize: function Award(scene) {
Phaser.GameObjects.Sprite.call(this, scene, 0, 0, 'award');
},
update: function () {
if (this.y < this.scene.game.height) {
this.hide();
}
},
show: function () {
this.setActive(true);
this.setVisible(true);
this.setOrigin(0.5, 0.5);
console.log(this);
this.setPosition(Phaser.Math.Between(0 + this.width, this.scene.game.config.width - this.width), 0);
this.scene.physics.add.existing(this);
this.body.setVelocity(0, 100);
},
hide: function() {
this.setActive(false);
this.setVisible(false);
}
});
...
// 在play场景create中处理
// 引入飞机精灵 默认给生命为1
this.plane = this.add.sprite(this.game.config.width / 2, 100, 'myplane').setInteractive({ draggable: true });
this.plane.life = 1;
// 然后创建奖牌组
this.awards = this.add.group({
classType: AwardClass,
runChildUpdate: true,
});
// 在检测飞机与奖牌碰撞,生命+1 最多不超过3
this.physics.add.overlap(this.plane, this.awards, function (plane, award) {
award.destroy();
if (plane.life < 3) {
plane.life += 1;
}
}, null, this);
// 创建奖牌 每10秒创建一个
// 这里涉及到phaser的时间管理器,我们可以用这个方法去设定每10秒生成一个奖牌,为了方便演示 我们设置成3秒
// api: https://photonstorm.github.io/phaser3-docs/Phaser.Time.Clock.html#addEvent__anchor
this.time.addEvent({
loop: true,
delay: 3000,
callback: () => {
const award = this.awards.getFirstDead(true);
award.show();
}
});
// 飞机碰到敌方子弹时 生命-1
this.physics.add.overlap(this.enemyBullets, this.plane, (enemyBullet, plane) => {
enemyBullet.destroy();
if (plane.life > 1) {
plane.life -= 1;
return;
}
...
}
我们在吃到奖牌的时候不光要加生命,在子弹上也要有些变化,两条生命一次发射3个子弹,三条生命一次发射5个子弹
// 我们把update中生成子弹的逻辑抽出来
// 引入我的子弹
if (time - this.myBulletBeforeTime > 200 && !this.gameOver) {
/*
const bullet = this.bullets && this.bullets.getFirstDead(true);
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.myBulletBeforeTime = time;
}
*/
createBulletByLife.call(this);
this.myBulletBeforeTime = time;
}
// 新增一个通过生命数量创建子弹的方法
function createBulletByLife() {
if (this.plane.life === 2) {
for (let index = 0; index < 3; index++) {
createBullets.call(this, index - 1);
}
} else if (this.plane.life === 3) {
for (let index = 0; index < 5; index++) {
createBullets.call(this, index - 2);
}
} else if (this.plane.life === 1) {
createBullets.call(this, 0);
}
function createBullets(index) {
const bullet = this.bullets && this.bullets.getFirstDead(true);
if (bullet) {
bullet.fire();
bullet.setPosition(this.plane.x, this.plane.y - this.plane.height / 2);
this.physics.add.existing(bullet);
bullet.body.setVelocity(index * 30, -300);
}
}
}
看一下奖牌效果:
六、添加音效
这边介绍一下用到的音效相关的api,项目实现具体可以看commit添加音效
// 首先是音效的引入
// api: https://photonstorm.github.io/phaser3-docs/Phaser.Loader.LoaderPlugin.html#audio__anchor
this.load.audio(key, require(`./${assetsMap[key]}`));
// 添加声音 根据导入的key生成添加音效
// api: https://photonstorm.github.io/phaser3-docs/Phaser.Sound.BaseSoundManager.html#add__anchor
const startAudio = this.sound.add('normalback');
// 音效的播放
// api: https://photonstorm.github.io/phaser3-docs/Phaser.Sound.BaseSound.html#play__anchor
startAudio.play({
loop: true,
volume: 0.1,
})
// 音效停止
// api: https://photonstorm.github.io/phaser3-docs/Phaser.Sound.BaseSound.html#stop__anchor
startAudio.stop();
那么phaser3飞机大战demo就到这里完成了,接下来会分享游戏2048。