Rexplugin2
Bullet
import * as Phaser from 'phaser';
export class Bullet extends Phaser.Physics.Arcade.Sprite
{
private player: any;
constructor(scene: Phaser.Scene, x: number, y: number) {
// コンストラクタで親クラス (Phaser.Physics.Arcade.Sprite) を呼び出す
super(scene, x, y, "playbullet");
scene.physics.add.existing(this);
this.setActive(false);
this.setVisible(false);
scene.time.delayedCall(3000, this.destroyBullet, [], this);
}
private destroyBullet(): void {
this.destroy();
this.setVisible(false);//bulletをシーンから削除し、メモリを解放する
}
// シーンに追加されたときに呼び出される初期化メソッド
fire(x: number, y: number, speed: number) {
const body = this.body as Phaser.Physics.Arcade.Body;
body.reset(this.x, this.y); // 弾丸の位置をリセット
this.setPosition(x, y);
this.setActive(true); // アクティブにする
this.setVisible(true); // 表示する
this.setVelocityX(-speed); // 上方向に移動(速度は適宜調整)
}
// 更新処理 (シーンのupdateから呼ばれる)
update(time: number, delta: number) {
if ( this.x < -50 || this.x > 950*3 + 50) {
this.setActive(false);
this.setVisible(false);
}
}
}
/////////////////////
Enemy
import * as Phaser from 'phaser';
export class Enemy extends Phaser.Physics.Arcade.Sprite {
constructor(scene: Phaser.Scene, x: number, y: number) {
super(scene, x, y, 'enemy'); // 'enemy'は事前にロードされた画像のキー
scene.add.existing(this);
scene.physics.add.existing(this);
this.setCollideWorldBounds(true).setDepth(2);
this.setBounce(0.1).setScale(1.8);
this.createAnimations(scene);
}
createAnimations(scene: Phaser.Scene) {
scene.anims.create({
key: "left2", frameRate: 2, repeat: -1,
frames: this.anims.generateFrameNumbers('enemy', {start: 4, end: 5}),
});
scene.anims.create({
key: "right2", frameRate: 2, repeat: -1,
frames: this.anims.generateFrameNumbers('enemy', {start: 6, end: 7}),
});
}
update() {
}
}
////////////////////
MyPlayer
import * as Phaser from 'phaser';
export class MyPlayer extends Phaser.Physics.Arcade.Sprite {
private speed: number = 200;
private readonly animKeyRun: string = 'run';
private readonly animKeyLeft: string = 'left';
private readonly animKeyIdle: string = 'idle';
private readonly animKeyRight: string = 'right';
private joystick: any;
constructor(scene: Phaser.Scene, x: number, y: number, texture: string) {
super(scene, x, y, texture);
// シーンに追加
scene.add.existing(this);
scene.physics.add.existing(this);
this.setBounce(0.2).setScale(0.5).setDepth(2);
// 衝突設定(必要に応じて)
this.setCollideWorldBounds(true);
this.createAnimations(scene);
this.setFlipX(false);
}
createAnimations(scene: Phaser.Scene) {
this.anims.create({
key: this.animKeyRun,
frames: this.anims.generateFrameNumbers('player', {
start: 5,
end: 8
}),
frameRate: 10,
repeat: -1
});
this.anims.create({
key: this.animKeyRight,
frames: [{key: 'player', frame: 5}],
frameRate: 20
});
this.anims.create({
key: this.animKeyLeft,
frames: [{key: 'player', frame: 2}],
frameRate: 20
});
}
// ジョイスティックの状態を受け取って更新
update(joystick: any): void {
const cursorKeys = joystick.createCursorKeys();
// 速度の初期化
this.setVelocityX(0);
// 左右移動の判定
if (cursorKeys.left.isDown) {
this.setVelocityX(-this.speed);
this.setFlipX(true); // 左を向く
this.anims.play(this.animKeyRun, true);// アニメーションがある場合
} else if (cursorKeys.right.isDown) {
this.setVelocityX(this.speed);
this.setFlipX(false); // 右を向く
this.anims.play(this.animKeyRun, true);
} else {
this.anims.play(this.animKeyRight, true); // 停止時
}
if ( cursorKeys.up.isDown) {
this.setVelocityY(-200);
if (this.y < 70) {
this.setVelocityY(0);
}
}
}
}
//////////////////
ScoreText
import * as Phaser from 'phaser';
export class ScoreText extends Phaser.GameObjects.Text {
private score: number = 0;
constructor(scene: Phaser.Scene, x: number, y: number,
style: Phaser.Types.GameObjects.Text.TextStyle) {
// 親クラス (Phaser.GameObjects.Text) のコンストラクタを呼び出す
super(scene, x, y, 'Score:0', style);
// このテキストオブジェクトをシーンの表示リストに追加する
scene.add.existing(this);
}
// スコアを加算して表示を更新するメソッド
public addScore(points: number): void {
this.score += points;
this.setText('Score:'+ this.score);
// this.setText(`Score:${this.score}`);
}
// スコアをリセットするメソッド
public resetScore(): void {
this.score = 0;
this.updateScoreText();
}
// 現在のスコアを取得するメソッド
public getScore(): number {
return this.score;
}
private updateScoreText(): void {
this.setText(`Score: ${this.score}`);
}
}
/////////////////
GameScene
import * as Phaser from 'phaser';
import {MyPlayer} from "./MyPlayer";
import {Bullet} from "./Bullet";
import {ScoreText} from "./ScoreText";
import {Enemy} from "./Enemy";
export class GameScene extends Phaser.Scene {
private joystick: any;
private player!: MyPlayer;
private rexVirtualJoystick: any;
private yuka?: Phaser.GameObjects.Rectangle;
private bullets!: Phaser.Physics.Arcade.Group;
private bullet: any;
private coins!: any;
private scoreText!: ScoreText;
private coin?: Phaser.GameObjects.GameObject;
private platforms!: Phaser.Physics.Arcade.StaticGroup;
private hitSound!: Phaser.Sound.BaseSound;
private enemy!: Enemy;
private enemies!: Phaser.GameObjects.Group;
private isEnemyActive: boolean = false;
private playerbullets!: Phaser.Physics.Arcade.Group;
private shootEvent!: Phaser.Time.TimerEvent;
private hearts: Phaser.GameObjects.Image[] = [];
private initialHealth: number = 10;
private currentHealth = 10;
private maxHealth = 10;
constructor() {
super({key: 'GameScene'});
}
preload() {
this.load.image('myblock', 'assets/block.png');
this.load.spritesheet('player', 'assets/mplayer.png', {
frameWidth: 127, frameHeight: 168
});
this.load.image("ground", "assets/platform.png");
this.load.image("bullet", "assets/laser.png");
this.load.image('shoot', 'assets/shoot.png');
this.load.spritesheet('explosion', 'assets/explosion.png', {
frameWidth: 64,
frameHeight: 64,
endFrame: 23
});
this.load.spritesheet('enemy', 'assets/enemy.png', {frameWidth: 32, frameHeight: 32});
this.load.image('playbullet', 'assets/bullet.png');
this.load.spritesheet('coin', 'assets/coin.png',
{frameWidth: 32, frameHeight: 32});
this.load.audio('coinSound', 'assets/coin.mp3');
this.load.image('heart', 'assets/heart.png');
}
create(): void {
this.add.image(700, 150, 'myblock').setScale(1.5).setOrigin(0);
this.add.image(700 * 2, 200, 'myblock').setScale(1.5).setOrigin(0);
this.add.image(700 * 3, 250, 'myblock').setScale(1.5).setOrigin(0);
// ハート画像を配置
for (let i = 0; i < this.maxHealth; i++) {
const heart = this.add.image(30 + i * 29, 19, 'heart').setScale(0.1).setScrollFactor(0);
this.hearts.push(heart);
}
this.cameras.main.setBounds(0, 0, 950 * 3, 550);
this.physics.world.setBounds(0, 0, 950 * 3, 550);
this.platforms = this.physics.add.staticGroup();
for (let i = 0; i < 3; i++) {
const x = Phaser.Math.Between(200, 30);
const y = Phaser.Math.Between(20, -20);
// const platform = this.physics.add.sprite(x, y, 'ground');
const platform = this.physics.add.sprite(0, 0, 'ground');
platform.setX(400 + (i * 750) + x);
platform.setY(150 + (i * 23) + y),
platform.setImmovable(true);
this.platforms.add(platform);
}
this.tweens.add({
targets: this.platforms.getChildren(), // グループ内の全オブジェクトを対象にする
x: '+=100', // 現在のy座標から100ピクセル下に移動
duration: 1000, // 1秒かけて移動
ease: 'Sine.easeInOut',
yoyo: true, // 元に戻る
repeat: -1, // 無限ループ
delay: 0, // 必要であれば遅延を設定
stagger: 500 // 順番に動かしたい場合は100msの遅延を設定
});
this.yuka = this.add.rectangle(0, 350, 950 * 3, 200, 0x00FFFF).setOrigin(0);
this.physics.add.existing(this.yuka, true);
// this.physics.add.staticGroup(this.yuka);
this.player = new MyPlayer(this, 300, 280, 'player');
this.cameras.main.startFollow(this.player);
this.player.setCollideWorldBounds(true);
this.cameras.main.startFollow(this.player);
// オプション: 追従時のオフセットや慣性を設定することも可能
this.cameras.main.setFollowOffset(-10, 5);
// this.cameras.main.setLerp(0.005, 0.005);
this.physics.add.existing(this.player);
this.physics.add.collider(this.player, this.yuka);
// コインを管理する物理グループの作成
this.coins = this.physics.add.group();
this.physics.add.existing(this.coins);
this.physics.add.collider(this.coins, this.yuka);
this.physics.add.collider(this.player, this.platforms);
// スコアテキストのスタイル設定
const style: Phaser.Types.GameObjects.Text.TextStyle = {
fontSize: '32px',
color: '#fff',
// fontStyle: 'bold',
fontFamily: 'Courier'
};
this.scoreText = new ScoreText(this, 16, 36, style);
this.scoreText.setScrollFactor(0);
this.hitSound = this.sound.add('coinSound', {volume: 0.7, delay: 0});
this.anims.create({
key: 'explode', // アニメーションの識別キー
frames: this.anims.generateFrameNumbers('explosion', {
start: 0, // 開始フレーム番号
end: 23 // 終了フレーム番号
}),
frameRate: 20, // 1秒あたりのフレーム数
repeat: 0, // 繰り返し回数 (0で1回のみ再生、-1で無限ループ)
hideOnComplete: true // アニメーション完了時にスプライトを非表示にする
});
this.playerbullets = this.physics.add.group({
classType: Bullet,
allowGravity: false,// 使用するカスタムクラスを指定
maxSize: 50, // プールできる最大数
runChildUpdate: true // updateメソッドを自動的に実行させる
}) as Phaser.Physics.Arcade.Group;
this.shootEvent = this.time.addEvent({
delay: 1500,
callback: this.enemyShoot,
callbackScope: this,
loop: true
});
this.bullets = this.physics.add.group({
defaultKey: 'bullet',
maxSize: 70,
runChildUpdate: true
});
this.anims.create({
key: 'rotate',
frames: this.anims.generateFrameNumbers('coin', {start: 0, end: 7}),
frameRate: 10,
repeat: -1
});
for (let i = 1; i < 15; i++) {
const randomX = Phaser.Math.Between(0, 945 * 3);
this.coin = this.physics.add.sprite(randomX, 0, 'coin').setOrigin(1).setScale(2.2).anims.play('rotate');
// this.coin.setOffset(0, -10);
(this.coin.body as Phaser.Physics.Arcade.Body).setOffset(0, -5);
this.physics.add.collider(this.coin, this.yuka);
this.physics.add.existing(this.coin);
this.physics.add.collider(this.coin, this.platforms);
this.physics.add.collider(this.playerbullets, this.coin, (bullet, coin) => {
(coin as Phaser.Physics.Arcade.Sprite).disableBody(true, true);
const pos = coin as Phaser.Physics.Arcade.Sprite;
if (this.player.flipX) {
this.createExplosion(pos.x - 50, pos.y);
} else {
this.createExplosion(pos.x + 50, pos.y);
}
this.hitSound.play();
this.scoreText.addScore(10);
// this.score += 10;
// this.scoreText.setText('Score: ' + this.score);
bullet.destroy();
});
}
this.joystick = (this.plugins.get('rexVirtualJoystick') as any).add(this, {
x: 150,
y: 470,
radius: 100,
base: this.add.circle(0, 0, 100, 0x0000FF).setAlpha(0.1),
thumb: this.add.circle(0, 0, 50, 0xFFA500).setAlpha(0.5),
})
.on('update', this.update, this);
this.enemies = this.physics.add.group({runChildUpdate: true});
const x = Phaser.Math.Between(800, 900);
// const y = Phaser.Math.Between(310, 316);
this.enemy = new Enemy(this, x, 320);
this.enemies.add(this.enemy);
this.enemy.setCollideWorldBounds(true);
this.physics.add.collider(this.enemy, this.yuka);
this.enemy.anims.play('left2', true);
this.enemy.setVelocityX(-150); // 最初は右へ
this.isEnemyActive = false;
this.enemy.setVisible(false);
(this.enemy.body as Phaser.Physics.Arcade.Body).setEnable(false);
this.input.addPointer(2);
const jumpButton = this.add.image(850, 480, 'shoot').setScale(0.4).setAlpha(0.5);
jumpButton.setInteractive().setScrollFactor(0);
jumpButton.on("pointerdown", () => {
this.shoot();
});
jumpButton.on("pointerup", () => {
// this.shoot();
});
this.physics.add.collider(this.playerbullets, [this.enemies, this.bullets], (bullet, enemy) => {
(enemy as Phaser.Physics.Arcade.Sprite).disableBody(true, true);
const star = bullet as Phaser.Physics.Arcade.Sprite;
star.disableBody(true, true);
if (this.enemies.countActive(true) === 0) {
// this.spawnNewStars(3);
}
// 1秒後に新しい位置に再出現
this.time.delayedCall(2000, () => {
// this.spawnEnemy(16);
this.spawnEnemy(6);
});
});
this.physics.add.collider(this.player, this.bullets, (myplayer, myenemy) => {
// this.hp =100;
const tar = myplayer as Phaser.Physics.Arcade.Sprite;
const myeny = myenemy as Phaser.Physics.Arcade.Sprite;
myeny.disableBody(true, true);
tar.setTint(0xff0000);
this.time.addEvent({
delay: 200,
callback: () => {
tar.setTint(0xffffff);
}
});
this.damage();
});
}
damage() {
if (this.currentHealth > 0) {
this.currentHealth -= 1;
// 配列の最後のハートを空のハートに切り替えるか、破棄する
const heartToRemove = this.hearts.pop();
if (heartToRemove) {
heartToRemove.destroy(); // 画像を破棄
}
if (this.currentHealth === 0) {
this.add.text(460, 140, 'GAME OVER',
{fontSize: '84px', color: '#ff0000'}).setOrigin(0.5).setScrollFactor(0);
this.physics.pause();
this.player.setVisible(false);
this.time.delayedCall(3000, () => {
this.initialHealth = 10;
this.scene.start('GameScene');
});
// this.player.setVisible(false);
}
}
}
private showGameOverText() {
this.add.text(950 / 2, 550 / 2 - 100, 'Game over!\nClick to Restart',
{fontSize: '64px', color: '#ff0000'}).setOrigin(0.5).setAlign('center');
this.input.once('pointerdown', () => {
// this.updateEnemyBehavior();
this.scene.restart();
});
}
private createExplosion(x: number, y: number) {
const explosion = this.add.sprite(x, y, 'explosion');
explosion.play('explode');
explosion.on('animationcomplete', () => {
explosion.destroy(); // アニメが終わったら消す
});
}
private spawnEnemy(count: number) {
for (let i = 0; i < count; i++) {
const newX = Phaser.Math.Between(800, 1800);
const newY = Phaser.Math.Between(314, 313);
if (!this.enemy) {
// this.enemy = this.physics.add.sprite(newX, newY, 'enemy');
this.enemy = new Enemy(this, newX, newY);
} else {
this.enemy.enableBody(true, newX, newY, true, true);
}
}
}
shoot() {
// プールから利用可能な弾丸を取得する
// const bulletY = this.player.y - (this.player.height / 2) + 10;
const bullet = this.playerbullets.get(this.player.x, this.player.y).setScale(0.1) as Bullet | null;
if (bullet) {
if (this.player.flipX) {
bullet.fire(this.player.x + 9, this.player.y + 11, 800);
} else {
bullet.fire(this.player.x + 9, this.player.y + 11, -800);
}
}
}
private enemyShoot() {
if (!this.isEnemyActive || !(this.enemy.body as Phaser.Physics.Arcade.Body).enable) return;
const bullet = this.bullets.get(this.enemy.x, this.enemy.y);
if (bullet) {
bullet.setActive(true).setVisible(true);
bullet.body.allowGravity = false;
bullet.enableBody = true;
bullet.setScale(0.6);
this.time.delayedCall(2200, () => {
bullet.destroy();
bullet.setVisible(false);
});
// プレイヤーへの角度を計算
const angle = Phaser.Math.Angle.Between(this.enemy.x, this.enemy.y, this.player.x, this.player.y);
if (this.player.x < this.enemy.x) {
this.enemy.setFlipX(false);
} else {
this.enemy.setFlipX(true);
}
this.physics.velocityFromRotation(angle, 500, bullet.body.velocity);
}
}
fireShot() {
const bulletCount = 5; // 発射する数
const spreadAngle = Phaser.Math.DegToRad(15); // 広がる範囲の合計(30度)
const startAngle = this.player.rotation - spreadAngle / 2; // 開始角度
const angleStep = spreadAngle / (bulletCount - 1); // 弾ごとの角度の差
let bulletSpeed = 400; // 弾の速度
for (let i = 0; i < bulletCount; i++) {
// 1. 各弾の角度を計算
const angle = startAngle + (angleStep * i);
// 2. 弾(Sprite)を生成
const bullet = this.playerbullets.get(this.player.x, this.player.y).setScale(0.1);
if (bullet) {
bullet.setActive(true);
bullet.setVisible(true);
if (bullet) {
if (this.player.flipX) {
// bullet.fire(this.player.x, this.player.y + 5, 800);
this.physics.velocityFromRotation(angle, -bulletSpeed, bullet.body.velocity);
} else {
this.physics.velocityFromRotation(angle, bulletSpeed, bullet.body.velocity);
// bullet.fire(this.player.x, this.player.y + 5, -800);
}
}
}
}
}
private updateEnemyBehavior() {
const distance = Phaser.Math.Distance.Between(
this.player.x, this.player.y,
this.enemy.x, this.enemy.y
);
// 1. 出現距離チェック
if (!this.isEnemyActive && distance < 300) {
this.isEnemyActive = true;
this.enemy.setVisible(true);
(this.enemy.body as Phaser.Physics.Arcade.Body).setEnable(true);
}
if (!this.isEnemyActive) return;
// 2. 停止距離チェック&移動
if (distance > 300) {
// プレイヤーに向かって移動
this.physics.moveToObject(this.enemy, this.player, 190);
if (this.player.y < this.enemy.y) {
this.enemy.setVelocityY(0);
}
} else {
this.enemy.setVelocity(0);
}
}
update() {
this.updateEnemyBehavior();
if (this.player) {
this.player.update(this.joystick);
}
}
}
/////////////////
main.ts
import * as Phaser from 'phaser';
import VirtualJoystickPlugin from 'phaser4-rex-plugins/plugins/virtualjoystick-plugin.js';
import { GameScene } from './scenes/GameScene';
const config: Phaser.Types.Core.GameConfig = {
type: Phaser.AUTO,
width: 950,
height: 550,
backgroundColor: '#cccccc',
// antialias: false,
disableContextMenu: true,
banner: false,
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH },
parent: 'app',
physics: {
default: 'arcade',
arcade: {
gravity: { x: 0, y: 400 },
debug: false,
},
},
plugins: {
global: [{
key: 'rexVirtualJoystick',
plugin: VirtualJoystickPlugin,
start: true
},
]
},
scene: [GameScene],
};
window.addEventListener('load', () => {
new Phaser.Game(config);
});