【Phaser3でSvelteゲームアプリを作る③】シューティングゲームの土台となる最初のプロジェクトを作成
※ 当ページには【広告/PR】を含む場合があります。
2024/07/28
目次
- 1. プレーヤーと敵と弾を配置する
- 2. キーボードからプレーヤーを動かす
- 3. プレーヤーから弾を発射させる
- 4. 敵に動きをつける
- 5. 当たり判定①〜敵が弾をあたると消える
- 6. プレーヤーのライフを設定する
- 7. 当たり判定②〜プレーヤーが敵にあたるとダメージを受ける
- 8. まとめ
- 8-1. 付録〜ここまでで作成したコードの完成版
- App.svelte
- scenes/shooting-1.js
- scenes/lives.js
- 8-1. 付録〜ここまでで作成したコードの完成版
プレーヤーと敵と弾を配置する
scenes
shooting-1.js
preload
import Phaser from "phaser";
export default class shooting1Scene extends Phaser.Scene {
constructor() {
super({ key: 'shooting1' });
}
preload() {
this.load.setBaseURL("src/assets");
this.load.image('cat', 'cat.svg');
this.load.image('ball', 'ball.svg');
this.load.image('enemy', 'enemy1.png');
}
}
<script>
import Phaser from "phaser";
//👇追加
import shooting1Scene from "./scenes/shooting-1.js";
const game = new Phaser.Game({
type: Phaser.AUTO,
width: 800,
height: 600,
physics: {
default: 'arcade',
arcade: {
gravity: { x: 0, y: 0 },
debug: true
}
},
input: { keyboard: true },
scene: [shooting1Scene], //👈シーンを追加
});
</script>
キーボードからプレーヤーを動かす
shooting1Scene
create
update
//...中略
create () {
//👇'cat'の画像を(400, 300)にスプライトとして配置する(=デフォルトの配置点は画像の中央)
this._cat = this.add.sprite(400, 300, 'cat');
//👇画像を60px四方にリサイズ
this._cat.setDisplaySize(60, 60);
//👇スプライトをシーンに表示
this.physics.add.existing(this._cat, false);
//👇スプライトがシーンの画面枠外に出ないようにする
this._cat.body.setCollideWorldBounds(true);
//👇特定のキー(矢印キーを含む)のイベントを有効にする
this.cursors = this.input.keyboard.createCursorKeys();
}
update () {
//👇キーが押されていない場合、スプライトの動きを止める
this._cat.body.setVelocity(0);
//👇↑キーが押されたら、上に移動
if (this.cursors.up.isDown) {
this._cat.body.setVelocityY(-300);
}
//👇↓キーが押されたら、下に移動
else if (this.cursors.down.isDown) {
this._cat.body.setVelocityY(300);
}
//👇←キーが押されたら、左に移動
if (this.cursors.left.isDown) {
this._cat.body.setVelocityX(-300);
}
//👇→キーが押されたら、右に移動
else if (this.cursors.right.isDown) {
this._cat.body.setVelocityX(300);
}
}
this._cat = this.add.sprite(400, 300, 'cat');
this
GameObject
add
add
GameObject
spriteメソッド
this._cat
this._cat.setDisplaySize(60, 60);
this._cat.body.setCollideWorldBounds(true);
this._cat.body.setVelocity(0);
this.add
this.physics
this.input
プレーヤーから弾を発射させる
this.physics
this.input
//...中略
create () {
//...中略
//👇弾のスプライトに物理演算できるようにthis.physicsに登録する
this._ball = this.physics.add.sprite(this._cat.x, this._cat.y, 'ball');
//👇一旦弾のスプライトは非表示にしておく
this._ball.disableBody(true, true);
//...中略
//👇キーボードのAキー+Downイベントとして弾を発射させる
this.input.keyboard.on('keydown-A', event => {
//👇イベント発生時に弾のスプライトを表示
this._ball.enableBody(true, this._cat.x + 30, this._cat.y, true, true);
//👇弾に物理的な動きを設定
this.physics.velocityFromRotation(0, 600, this._ball.body.velocity);
});
//👇キーボードのAキーのイベントを有効化
this.input.keyboard.addCapture('A');
}
敵に動きをつける
//...中略
create () {
//...中略
//👇敵スプライトを物理演算対象に登録して、画像サイズを0.3倍に設定
this._enemy = this.physics.add.sprite(0, 0, 'enemy').setScale(0.3);
//👇敵スプライトを配置/Y座標はランダム
this._enemy.setPosition(800, Phaser.Math.Between(50, 550));
//👇敵スプライトに動きを設定
this._enemy.setVelocityX(-60);
//...中略
}
update () {
//...中略
//👇敵が画面左端に着いたら、右端から動きを繰り返す
if (this._enemy.getBounds().right < 0) {
//👇敵スプライトを一旦非表示にする
this._enemy.disableBody(true, true);
//👇敵スプライトを右端に戻して表示する
this._enemy.enableBody(true, 800, Phaser.Math.Between(30, 550), true, true);
//👇敵スプライトに動きを設定
this._enemy.setVelocityX(-60);
}
}
当たり判定①〜敵が弾をあたると消える
//...中略
create () {
//...中略
//👇弾と敵が接触した際の処理を追加
this.physics.add.overlap(this._ball, this._enemy, () => {
//👇弾スプライトを非表示にする
this._ball.disableBody(true, true);
this._enemy.disableBody(true, true);
this._enemy.enableBody(true, 800, Phaser.Math.Between(30, 550), true, true);
this._enemy.setVelocityX(-60);
}, null, this);
}
プレーヤーのライフを設定する
scenes
lives.js
lives.js
import Phaser from "phaser";
export default class livesScene extends Phaser.Scene {
constructor () {
super({ key: 'lives' });
}
//👇シーンの変数を外部から初期化
init (data) {
this.lives = data.life;
}
preload (){
this.load.setBaseURL("src/assets");
this.load.image('life', 'life.svg');
}
create () {
//👇ハートのスプライトを連続複写かつ物理演算にグループとして登録
this._lifeIcon = this.physics.add.group(
{
key: 'life',
repeat: this.lives - 1,
setScale: {x: 0.2, y: 0.2},
setXY: { x: 50, y: 50, stepX: 40 }
}
);
//👇メインのゲームシーンを取得する
const _mainGame = this.scene.get('shooting1');
//👇「damaged」イベントを受信したときの処理を記述
_mainGame.events.on('damaged', () => {
this.lives--;
if (this.lives>= 0) {
//👇スプライトグループ内で対象のハートスプライトだけ非表示
this._lifeIcon.children.entries[this.lives].disableBody(true, true);
}
}, this);
}
}
<script>
import Phaser from "phaser";
import shooting1Scene from "./scenes/shooting-1.js";
//👇追加
import livesScene from "./scenes/lives.js";
const game = new Phaser.Game({
type: Phaser.AUTO,
width: 800,
height: 600,
physics: {
default: 'arcade',
arcade: {
gravity: { x: 0, y: 0 },
debug: true
}
},
input: { keyboard: true },
scene: [
shooting1Scene,
livesScene, //👈シーンを追加
],
});
//👇SceneManagerからのライフ表示シーンの初期化
game.scene.start('liveView', { life: 3 });
</script>
init
init (data) {
this.lives = data.life;
}
init
data
game.scene.start('liveView', { life: 3 });
this.scene
this.scene
liveView
const _mainGame = this.scene.get('shooting1');
EventEmitterでシーン間相互イベントを実装する
<シーン>.events
damaged
_mainGame.events.on('damaged', () => {
this.lives--;
if (this.lives>= 0) {
this._lifeIcon.children.entries[this.lives].disableBody(true, true);
}
}, this);
this.events.emit('damaged');
当たり判定②〜プレーヤーが敵にあたるとダメージを受ける
//...中略
export default class shooting1Scene extends Phaser.Scene {
//👇プレーヤーの初期ライフ数を設定
catlife = 3;
create () {
//...中略
//👇プレーヤーと敵とのコライダー(衝突オブジェクト)を設置
this.physics.add.collider(this._cat, this._enemy, this.hitEnemy, null, this);
//...中略
}
//...中略
//👇新しいクラスメソッドを追加する
hitEnemy(player, enemy) {
//👇シーンイベント・「damaged」を発行
this.events.emit('damaged');
//👇残機が2以上の場合の処理
if (this.catlife > 1) {
enemy.disableBody(true, true);
enemy.enableBody(true, 800, Phaser.Math.Between(30, 550), true, true);
enemy.setVelocityX(-60);
//👇プレーヤーのライフを1つ減らす
this.catlife--;
}
//👇ゲームオーバー時の処理
else {
//👇すべての物理演算を停止(=ゲームの中断)
this.physics.pause();
//👇プレーヤーの色を赤に変更
player.setTint(0xff0000);
}
}
//...以下略
まとめ
付録〜ここまでで作成したコードの完成版
App.svelte
<script>
import Phaser from "phaser";
import shooting1Scene from "./scenes/shooting-1.js";
import livesScene from "./scenes/lives.js";
const game = new Phaser.Game({
type: Phaser.CANVAS,
width: 800,
height: 600,
physics: {
default: 'arcade',
arcade: {
gravity: { x: 0, y: 0 },
debug: true
}
},
//👇ゲーム画面サイズが自動調整されるおまじない
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH
},
input: { keyboard: true },
scene: [shooting1Scene, livesScene],
});
game.scene.start('liveView', { life: 3 });
</script>
scenes/shooting-1.js
import Phaser from "phaser";
export default class shooting1Scene extends Phaser.Scene {
catlife = 3;
constructor () {
super({ key: 'shooting1' });
}
preload (){
this.load.setBaseURL('src/assets');
this.load.image('cat', 'cat.svg');
this.load.image('ball', 'ball.svg');
this.load.image('enemy', 'enemy1.png');
}
create () {
this._cat = this.add.sprite(400, 300, 'cat');
this._cat.setDisplaySize(60, 60);
this.physics.add.existing(this._cat, false);
this._cat.body.setCollideWorldBounds(true);
this.cursors = this.input.keyboard.createCursorKeys();
this._ball = this.physics.add.sprite(this._cat.x, this._cat.y, 'ball');
this._ball.disableBody(true, true);
this._enemy = this.physics.add.sprite(0, 0, 'enemy').setScale(0.3);
this._enemy.setPosition(800, Phaser.Math.Between(50, 550));
this._enemy.setVelocityX(-60);
this.input.keyboard.on('keydown-A', event => {
this._ball.enableBody(true, this._cat.x + 30, this._cat.y, true, true);
this.physics.velocityFromRotation(0, 600, this._ball.body.velocity);
});
this.input.keyboard.addCapture('A');
this.physics.add.overlap(this._ball, this._enemy, () => {
this._ball.disableBody(true, true);
this._enemy.disableBody(true, true);
this._enemy.enableBody(true, 800, Phaser.Math.Between(30, 550), true, true);
this._enemy.setVelocityX(-60);
}, null, this);
this.physics.add.collider(this._cat, this._enemy, this.hitEnemy, null, this);
}
update () {
this._cat.body.setVelocity(0);
if (this.cursors.up.isDown) {
this._cat.body.setVelocityY(-300);
}
else if (this.cursors.down.isDown) {
this._cat.body.setVelocityY(300);
}
if (this.cursors.left.isDown) {
this._cat.body.setVelocityX(-300);
}
else if (this.cursors.right.isDown) {
this._cat.body.setVelocityX(300);
}
if (this._enemy.getBounds().right < 0) {
this._enemy.disableBody(true, true);
this._enemy.enableBody(true, 800, Phaser.Math.Between(30, 550), true, true);
this._enemy.setVelocityX(-60);
}
}
hitEnemy (player, enemy) {
this.events.emit('damaged');
if (this.catlife > 1) {
enemy.disableBody(true, true);
enemy.enableBody(true, 800, Phaser.Math.Between(30, 550), true, true);
enemy.setVelocityX(-60);
this.catlife--;
} else {
this.physics.pause();
player.setTint(0xff0000);
}
}
}
scenes/lives.js
import Phaser from "phaser";
export default class liveViewScene extends Phaser.Scene {
constructor () {
super({ key: 'liveView' });
}
init (data) {
this.lives = data.life;
}
preload (){
this.load.setBaseURL("src/assets");
this.load.image('life', 'life.svg');
}
create () {
this._lifeIcon = this.physics.add.group(
{
key: 'life',
repeat: this.lives - 1,
setScale: {x: 0.2, y: 0.2},
setXY: { x: 50, y: 50, stepX: 40 }
}
);
const _mainGame = this.scene.get('shooting1');
_mainGame.events.on('damaged', () => {
this.lives--;
if (this.lives >= 0) {
this._lifeIcon.children.entries[this.lives].disableBody(true, true);
}
}, this);
}
}
記事を書いた人
ナンデモ系エンジニア
これからの"地方格差"なきプログラミング教育とは何かを考えながら、 地方密着型プログラミング学習関連テーマの記事を不定期で独自にブログ発信しています。
カテゴリー
- 1. プレーヤーと敵と弾を配置する
- 2. キーボードからプレーヤーを動かす
- 3. プレーヤーから弾を発射させる
- 4. 敵に動きをつける
- 5. 当たり判定①〜敵が弾をあたると消える
- 6. プレーヤーのライフを設定する
- 7. 当たり判定②〜プレーヤーが敵にあたるとダメージを受ける
- 8. まとめ
- 8-1. 付録〜ここまでで作成したコードの完成版
- App.svelte
- scenes/shooting-1.js
- scenes/lives.js
- 8-1. 付録〜ここまでで作成したコードの完成版