上竞技场就是要决斗阿,不然要干嘛。
来让人物发射武器!血流成河吧!
首先来回顾一下 D27 武器规划。
可以发现同一时间内会出现多个相同的武器,所以这时候要出动 Phaser 的 Group。
Group 可以用来管理重复出现的物体,更容易进行侦测与各类操作,详细说明可以参考以下连结。
可以发现不管是主角武器还是敌人武器,几乎所有性质都相同,所以我们可以先建立 group-weapon.js
做为武器容器,建立角色武器时再将对应的武器注入、建立即可。
src\components\window-app-cat-vs-dog\objects\group-weapon.js
import Phaser from 'phaser';
/**
* @typedef {Object} ConstructorParams
* @property {Object} classType 注入武器物件
* @property {string} key
* @property {number} quantity 物体数量
*/
export default class extends Phaser.Physics.Arcade.Group {
/**
* @param {Phaser.Scene} scene
* @param {ConstructorParams} params
*/
constructor(scene, params) {
super(scene.physics.world, scene);
const { classType, key, quantity = 5 } = params;
this.createMultiple({
classType,
frameQuantity: quantity,
active: false,
visible: false,
key,
});
this.setDepth(1);
// 隐藏所有武器
this.getChildren().forEach((item) => {
item.setScale(0);
});
}
/** 发射武器
* @param {number} x
* @param {number} y
* @param {number} velocity
*/
fire(x, y, velocity) {
const weapon = this.getFirstDead(false);
if (weapon) {
weapon.body.enable = true;
weapon.fire(x, y, velocity);
}
}
}
首先建立主角的武器。
src\components\window-app-cat-vs-dog\objects\sprite-weapon-cat.js
import Phaser from 'phaser';
/**
* @typedef {Object} ConstructorParams
* @property {number} [x]
* @property {number} [y]
*/
export default class extends Phaser.Physics.Arcade.Sprite {
/**
* @param {Phaser.Scene} scene
* @param {ConstructorParams} params
*/
constructor(scene, params) {
const { x = 0, y = 0 } = params;
super(scene, x, y, 'cat-weapon');
this.scene = scene;
}
preUpdate(time, delta) {
super.preUpdate(time, delta);
/** 检查武器是否超出世界边界
* 透过侦测武器是否与世界有碰撞,取反向逻辑
* 没有碰撞,表示物体已经超出边界
*/
const outOfBoundary = !Phaser.Geom.Rectangle.Overlaps(
this.scene.physics.world.bounds,
this.getBounds(),
);
// 隐藏超出边界武器并关闭活动
if (outOfBoundary) {
this.setActive(false)
.setVisible(false);
}
}
/** 发射武器
* @param {number} x
* @param {number} y
* @param {number} velocity
*/
fire(x, y, velocity) {
// 清除所有加速度、速度并设置於指定座标
this.body.reset(x, y);
// 角速度
const angularVelocity = Phaser.Math.Between(-400, 400);
this.setScale(0.3)
.setSize(160, 160)
.setAngularVelocity(angularVelocity)
.setVelocityY(velocity)
.setActive(true)
.setVisible(true);
}
}
回到 scene-main.js
实际建立主角武器。
src\components\window-app-cat-vs-dog\scenes\scene-main.js
import Phaser from 'phaser';
import SpriteCat from '@/components/window-app-cat-vs-dog/objects/sprite-cat';
import SpriteDog from '@/components/window-app-cat-vs-dog/objects/sprite-dog';
import GroupWeapon from '@/components/window-app-cat-vs-dog/objects/group-weapon';
import SpriteWeaponCat from '@/components/window-app-cat-vs-dog/objects/sprite-weapon-cat';
export default class extends Phaser.Scene {
constructor() {
super({ key: 'main' })
}
create() {
// 主角
const catWeapon = new GroupWeapon(this, {
classType: SpriteWeaponCat,
key: 'cat-weapon',
quantity: 1
});
this.cat = new SpriteCat(this);
// ...
}
}
建立主角武器後,利用相同概念,将武器注入主角物件中,前往 sprite-cat.js
加入以下内容。
weapon
变数,储存注入之武器constructor
之 params
参数加入 weapon
joyStick
新增 on('rising')
监听(按钮按下事件),用来呼叫 weapon.fire()
src\components\window-app-cat-vs-dog\objects\sprite-cat.js
/**
* @typedef {import('@/script/electronic-components/joy-stick').default} JoyStick
* @typedef {import('@/components/window-app-cat-vs-dog/objects/group-weapon')} GroupWeapon
*
* @typedef {Object} CatParams
* @property {number} [x]
* @property {number} [y]
* @property {GroupWeapon} weapon
*/
import Phaser from 'phaser';
/** 最大速度 */
const velocityMax = 300;
export default class extends Phaser.Physics.Arcade.Sprite {
weapon = null;
/** 血量 */
health = 5;
/**
* @param {Phaser.Scene} scene
* @param {CatParams} params
*/
constructor(scene, params = {}) {
const {
x = 200, y = 200,
weapon = null,
} = params;
if (!weapon) throw new Error('weapon 为必填参数');
// ...
this.scene = scene;
this.weapon = weapon;
/** @type {JoyStick} */
const joyStick = scene.game.joyStick;
joyStick.on('data', ({ x, y }) => {
// ...
}).on('rising', () => {
// 座标设为与主角相同位置
this.weapon.fire(this.x, this.y, 800);
// 播放主角发射动画
this.play('cat-attack', true);
this.setVelocity(0, 0);
});
// ...
}
// ...
}
回到 scene-main.js
,将武器武器注入主角中。
src\components\window-app-cat-vs-dog\scenes\scene-main.js
import Phaser from 'phaser';
import SpriteCat from '@/components/window-app-cat-vs-dog/objects/sprite-cat';
import SpriteDog from '@/components/window-app-cat-vs-dog/objects/sprite-dog';
import GroupWeapon from '@/components/window-app-cat-vs-dog/objects/group-weapon';
import SpriteWeaponCat from '@/components/window-app-cat-vs-dog/objects/sprite-weapon-cat';
export default class extends Phaser.Scene {
constructor() {
super({ key: 'main' })
}
create() {
// 主角
const catWeapon = new GroupWeapon(this, {
classType: SpriteWeaponCat,
key: 'cat-weapon',
quantity: 1
});
this.cat = new SpriteCat(this, {
weapon: catWeapon,
});
// ...
}
}
试试看按下摇杆按钮有没有成功发射武器吧。
成功发射!但是主角停留在发射动画,回到 sprite-cat.js
调整一下。
src\components\window-app-cat-vs-dog\objects\sprite-cat.js
// ...
export default class extends Phaser.Physics.Arcade.Sprite {
weapon = null;
/** 血量 */
health = 5;
/**
* @param {Phaser.Scene} scene
* @param {CatParams} params
*/
constructor(scene, params = {}) {
// ...
}
preUpdate(time, delta) {
super.preUpdate(time, delta);
// 没有任何动画播放时,播放 cat-work
if (!this.anims.isPlaying) {
this.play('cat-work');
}
}
// ...
}
完成主角发射动画!
接着来让狗狗喷骨头吧。
首先建立敌人武器 sprite-weapon-dog.js
。
import Phaser from 'phaser';
export default class extends Phaser.Physics.Arcade.Sprite {
/**
* @param {Phaser.Scene} scene
* @param {number} x
* @param {number} y
*/
constructor(scene, x = 0, y = 0) {
super(scene, x, y, 'dog-weapon');
this.scene = scene;
}
preUpdate(time, delta) {
super.preUpdate(time, delta);
const outOfBoundary = !Phaser.Geom.Rectangle.Overlaps(
this.scene.physics.world.bounds,
this.getBounds(),
);
if (outOfBoundary) {
this.setActive(false);
this.setVisible(false);
}
}
/** 发射武器
* @param {number} x
* @param {number} y
* @param {number} velocity
*/
fire(x, y, velocity) {
this.body.reset(x, y);
const angularVelocity = Phaser.Math.Between(-400, 400);
this.setScale(0.2)
.setSize(300, 300)
.setAngularVelocity(angularVelocity)
.setVelocityY(velocity)
.setActive(true)
.setVisible(true);
}
}
在 scene-main.js
建立敌人武器并注入敌人物件中。
src\components\window-app-cat-vs-dog\scenes\scene-main.js
// ...
import GroupWeapon from '@/components/window-app-cat-vs-dog/objects/group-weapon';
import SpriteWeaponCat from '@/components/window-app-cat-vs-dog/objects/sprite-weapon-cat';
import SpriteWeaponDog from '@/components/window-app-cat-vs-dog/objects/sprite-weapon-dog';
export default class extends Phaser.Scene {
constructor() {
super({ key: 'main' })
}
create() {
// 主角
// ...
// 敌人
const dogWeapon = new GroupWeapon(this, {
classType: SpriteWeaponDog,
key: 'dog-weapon',
});
this.dog = new SpriteDog(this, {
weapon: dogWeapon,
target: this.cat,
});
// 加入中央河流
// ...
}
}
现在敌人也可以发射武器了,让我们回到 sprite-dog.js
中,让狗狗发射骨头吧!
src\components\window-app-cat-vs-dog\objects\sprite-dog.js
/**
* @typedef {import('@/components/window-app-cat-vs-dog/objects/group-weapon')} GroupWeapon
*
* @typedef {Object} DogParams
* @property {number} [x]
* @property {number} [y]
* @property {Phaser.Physics.Arcade.Sprite} target
* @property {GroupWeapon} weapon
*/
// ...
export default class extends Phaser.Physics.Arcade.Sprite {
/** @type {Phaser.Physics.Arcade.Sprite} */
target = null;
/** @type {GroupWeapon} */
weapon = null;
health = 10;
/**
* @param {Phaser.Scene} scene
* @param {DogParams} params
*/
constructor(scene, params) {
const {
x = 500, y = 600,
weapon = null,
target = null,
} = params;
if (!weapon) throw new Error('weapon 为必填参数');
// ...
this.scene = scene;
this.target = target;
this.weapon = weapon;
this.initAutomata();
}
preUpdate(time, delta) {
super.preUpdate(time, delta);
if (!this.anims.isPlaying) {
this.play('dog-work');
}
}
// ...
initAutomata() {
// 随机发射
this.scene.time.addEvent({
delay: 500,
callbackScope: this,
repeat: -1,
callback: async () => {
await delay(Phaser.Math.Between(0, 200));
this.fire();
},
});
// 追猫
// ...
}
fire() {
this.weapon.fire(this.x, this.y, -500);
this.play('dog-attack', true);
}
}
可以看到狗狗开始很凶残得丢骨头了! ⎝(・ω´・⎝)
鳕鱼:「再来就是人物与武器的激❤烈❤碰撞了!」
电子助教:「就不能用正常一点的方式描述碰撞侦测嘛 ...(´● ω ●`)」
加入人物扣血与胜败部分,先将人物的血量显示出来吧。
src\components\window-app-cat-vs-dog\scenes\scene-main.js
// ...
export default class extends Phaser.Scene {
constructor() {
super({ key: 'main' })
}
create() {
// ...
// 显示生命值
this.catHealthText = this.add.text(20, 20, `猫命:${this.cat.health}`, {
fill: '#000',
fontSize: 14,
});
const sceneHeight = this.game.config.height;
this.dogHealthText = this.add.text(20, sceneHeight - 20, `狗血:${this.dog.health}`, {
fill: '#000',
fontSize: 14,
}).setOrigin(0, 1);
// 加入中央河流
// ...
}
update() {
this.catHealthText.setText(`猫命:${this.cat.health}`);
this.dogHealthText.setText(`狗血:${this.dog.health}`);
}
}
可以看到画面上多了猫命与狗血。
最後就是加入人物与武器的碰撞侦测了。
src\components\window-app-cat-vs-dog\scenes\scene-main.js
// ...
export default class extends Phaser.Scene {
constructor() {
super({ key: 'main' })
}
create() {
// ...
// 加入武器与人物碰撞
this.physics.add.overlap(this.cat, dogWeapon, (cat, weapon) => {
// 隐藏武器
weapon.body.enable = false;
weapon.setActive(false).setVisible(false);
// 主角扣血
this.cat.subHealth();
});
this.physics.add.overlap(this.dog, catWeapon, (dog, weapon) => {
// 隐藏武器
weapon.body.enable = false;
weapon.setActive(false).setVisible(false);
// 敌人扣血
this.dog.subHealth();
});
}
// ...
}
可以看到主角与敌人被击中时都会播放被击中动画,同时减少血量。
但是目前血量归零後,不会有任何变化,所以要怎麽进到结束场景呢?
很简单,由於我们已经在 sprite-cat.js
和 sprite-cat.js
中加入「血量归 0 时,触发 death
事件的程序」。
所以最後只要监听人物的 death
事件即可。
src\components\window-app-cat-vs-dog\scenes\scene-main.js
// ...
export default class extends Phaser.Scene {
constructor() {
super({ key: 'main' })
}
create() {
// ...
// 侦测人物事件
this.dog.once('death', () => {
this.scene.start('over', 'win');
});
this.cat.once('death', () => {
this.scene.start('over', 'lose');
});
}
// ...
}
猫死表示失败,狗死表示游戏获胜。
将结果透过第二个参数传输到下一个场景,就可以在结束场景判断输赢了。
最後我们将结束场景完成吧。
结束场景的程序非常简单,就是根据传来的资料显示对应的结果。
src\components\window-app-cat-vs-dog\scenes\scene-over.js
import Phaser from 'phaser';
export default class extends Phaser.Scene {
constructor() {
super({ key: 'over' })
}
create(result) {
const x = this.game.config.width / 2;
const y = this.game.config.height / 2;
const text = result === 'win' ? '恭喜获胜' : '哭哭,被打败了';
const texture = result === 'win' ? 'cat-attack' : 'cat-beaten';
// 主角
this.cat = this.physics.add.sprite(x, y - 80, texture)
.setScale(0.5);
// 提示文字
this.add.text(x, y + 50, text, {
fill: '#000',
fontSize: 30,
}).setOrigin(0.5);
this.add.text(x, y + 100, '按下摇杆按键重新开始', {
fill: '#000',
fontSize: 18,
}).setOrigin(0.5);
/** @type {JoyStick} */
const joyStick = this.game.joyStick
// 延迟一秒钟後再侦测摇杆按钮,防止一进到场景後误按按钮马上触发
setTimeout(() => {
joyStick.once('toggle', () => {
this.scene.start('main');
});
}, 1000);
}
}
最後让我们实测看看吧!
以上我们完成全部的游戏功能了,最後让我们复原为了方便开发调整的内容吧。
scene-preload.js
下一个场景改回 welcomegame-scene.vue
取消 config.physics.arcade.debug
src\components\window-app-cat-vs-dog\scenes\scene-preload.js
// ...
export default class extends Phaser.Scene {
constructor() {
super({ key: 'preload' })
}
// ...
create() {
// ...
// 前往下一个场景
this.scene.start('welcome');
}
}
src\components\window-app-cat-vs-dog\game-scene.vue <script>
// ...
export default {
name: 'GameScene',
// ...
methods: {
/** 初始化游戏 */
initGame() {
/** @type {Phaser.Types.Core.GameConfig} */
const config = {
// ...
physics: {
default: 'arcade',
arcade: {
// debug: true,
},
},
};
// ...
},
// ...
},
};
来正式玩一场吧!
少少程序码就能完成功能的感觉是不是很棒呢
大家可以挑战看看更进阶的功能,例如:按着开关集气,可以发射出威力更强的武器、敌人不只会追击,还会闪躲子弹等等。
以上程序码已同步至 GitLab,大家可以前往下载:
很感谢大家一参与这场为期 31 天的奇幻旅程。
大家的支持是我完赛的原动力,在此感谢大家。
有缘的话,让我们明年再见罗!✧*。٩(* ˊᗜˋ )ノ٩(ˊᗜˋ*)و✧*。
Hi 本周已开始分领域课程 第一堂课不外乎就是介绍基本HTML语法 每周四都会有个小演习 这次题目是...
前面提到那个e,蕴含着我们与网页互动丰富的资讯。 例如: type:代表事件的类别。 target:...
实作成果: (今过长时间的debug终於成功了 ) 不知不觉就30天了 从一开始的不适应,到现在习惯...
setTimeout setTimeout:定时器,只执行一次,属於非同步,因此就算设定 0 秒执行...
名称的由来 Bluetooth是斯堪地那维亚语言的Blåtand/Blåtann 借10世纪丹麦和...