```git commit message

feat: Implement Phaser 3.90 framework with player movement, parallax backgrounds, mouse reticle, and sound effects

- Replaced old Player class with new ES6 module-based implementation using A/D keys for movement
- Added parallax background scrolling with multiple layers
- Implemented mouse reticle with glow effect instead of default cursor
- Integrated audio assets for game sounds and background music
- Updated game configuration to 600x800 resolution with FIT scaling
- Enhanced shooting mechanics with angle-based bullet trajectory calculation
- Improved UI elements including score text and game over screen
- Added enemy spawning and collision detection systems
```
This commit is contained in:
Brian Fertig 2025-08-03 12:54:49 -06:00
parent fa73b3c44f
commit 8a7b321872
14 changed files with 193 additions and 105 deletions

View File

@ -0,0 +1,8 @@
---
name: Phaser 3.90 Framework
---
- Follow best practices for Phaser 3.90
- Follow best ES6 practices
- Write class imports using full .js paths, NPM package manager or any other web packager is not installed
- Break things out into classes when it makes sense

BIN
assets/neon-city-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
assets/neon-city-bg2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
assets/player-sprite.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
assets/reticle-full.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

BIN
assets/reticle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

BIN
assets/sounds/main-gun.mp3 Normal file

Binary file not shown.

View File

@ -17,6 +17,7 @@
}
#game-container {
position: relative;
cursor: none;
}
</style>
</head>

BIN
raw/player.psd Normal file

Binary file not shown.

40
src/Player.js Normal file
View File

@ -0,0 +1,40 @@
export class Player {
constructor(scene) {
this.scene = scene;
this.sprite = null;
this.cursors = null;
}
create() {
// Create player ship
this.sprite = this.scene.physics.add.sprite(300, 700, 'player-sprite', 0);
this.sprite.setCollideWorldBounds(true);
// Setup controls
this.cursors = this.scene.input.keyboard.createCursorKeys();
// Replace cursor keys with A/D keys for movement
const aKey = this.scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A);
const dKey = this.scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D);
return { aKey, dKey };
}
update(aKey, dKey) {
// Player movement with A/D keys
if (aKey.isDown) {
this.sprite.setVelocityX(-200);
this.sprite.setFrame(1);
} else if (dKey.isDown) {
this.sprite.setVelocityX(200);
this.sprite.setFrame(2);
} else {
this.sprite.setVelocityX(0);
this.sprite.setFrame(0);
}
}
shoot() {
// Shooting functionality will be handled in GameScene
}
}

View File

@ -1,7 +1,7 @@
export const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
width: 600,
height: 800,
parent: 'game-container',
physics: {
default: 'arcade',
@ -14,5 +14,9 @@ export const config = {
preload: function() {},
create: function() {},
update: function() {}
},
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH
}
};

View File

@ -1,66 +0,0 @@
class Player extends Phaser.GameObjects.Container {
constructor(scene, x, y) {
super(scene, x, y);
// Create the player sprite
this.sprite = scene.add.sprite(0, 0, 'player');
this.add(this.sprite);
// Set up input controls using A/D keys instead of arrow keys
this.keys = scene.input.keyboard.addKeys({
a: Phaser.Input.Keyboard.KeyCodes.A,
d: Phaser.Input.Keyboard.KeyCodes.D
});
// Player properties
this.speed = 200;
this.health = 100;
this.isAlive = true;
// Set initial position
this.setPosition(x, y);
}
update() {
if (!this.isAlive) return;
// Move with A/D keys instead of arrow keys
if (this.keys.a.isDown) {
this.x -= this.speed * this.scene.game.loop.delta / 1000;
}
if (this.keys.d.isDown) {
this.x += this.speed * this.scene.game.loop.delta / 1000;
}
// Keep player within bounds
this.x = Phaser.Math.Clamp(this.x, 25, 775);
// Update position of the container's child sprite
this.sprite.setPosition(0, 0);
}
aimAt(targetX, targetY) {
// Not used in current implementation but kept for potential future use
}
fire() {
if (!this.isAlive) return;
// Fire bullet from player position
const bullet = new Bullet(this.scene, this.x, this.y - 20);
this.scene.bullets.add(bullet);
// Set bullet velocity
bullet.setVelocityY(-300);
}
hit(damage) {
if (!this.isAlive) return;
this.health -= damage;
if (this.health <= 0) {
this.destroy();
}
}
}

View File

@ -1,3 +1,5 @@
import { Player } from '../Player.js';
export class GameScene extends Phaser.Scene {
constructor() {
super({ key: 'GameScene' });
@ -6,16 +8,37 @@ export class GameScene extends Phaser.Scene {
this.player = null;
this.enemies = null;
this.bullets = null;
this.cursors = null;
// Game state
this.score = 0;
this.gameOver = false;
// Mouse reticle
this.reticle = null;
}
preload() {
// Load assets here (will be implemented in a later step)
console.log('Loading assets...');
// Load reticle image
this.load.image('reticle', 'assets/reticle.png');
// Load background images
this.load.image('bg-layer1', 'assets/neon-city-bg.png');
//this.load.image('bg-layer2', 'assets/bg-layer2.png');
//this.load.image('bg-layer3', 'assets/bg-layer3.png');
// Load Sprites
this.load.spritesheet('player-sprite', 'assets/player-sprite.png', {
frameWidth: 48,
frameHeight: 48
});
// Load background music and sounds
this.load.audio('bgm', 'assets/music/NeonPulse.mp3');
this.load.audio('main-gun', 'assets/sounds/main-gun.mp3');
this.load.audio('enemy-kill', 'assets/sounds/enemy-kill.mp3');
// Create simple placeholder graphics for now
this.createPlaceholderGraphics();
@ -25,20 +48,20 @@ export class GameScene extends Phaser.Scene {
// Set background color
this.cameras.main.setBackgroundColor('#0a0a2a');
// Create player ship
this.player = this.physics.add.sprite(400, 500, 'player');
this.player.setCollideWorldBounds(true);
// Create parallax backgrounds
this.createBackgrounds();
// Create player instance
this.player = new Player(this);
const { aKey, dKey } = this.player.create();
// Create groups for bullets and enemies
this.bullets = this.physics.add.group();
this.enemies = this.physics.add.group();
// Setup controls
this.cursors = this.input.keyboard.createCursorKeys();
// Setup collision detection
this.physics.add.overlap(this.bullets, this.enemies, this.hitEnemy, null, this);
this.physics.add.overlap(this.player, this.enemies, this.hitPlayer, null, this);
this.physics.add.overlap(this.player.sprite, this.enemies, this.hitPlayer, null, this);
// Start enemy spawning
this.time.addEvent({
@ -50,24 +73,82 @@ export class GameScene extends Phaser.Scene {
// Create UI elements
this.createUI();
// Setup mouse input for shooting and reticle
this.setupMouseInput();
// Play background music
this.bgMusic = this.sound.add('bgm', { loop: true });
this.bgMusic.play();
}
createBackgrounds() {
// Create multiple background layers with different speeds
this.bgLayer1 = this.add.image(280, 30, 'bg-layer1').setOrigin(0.5);
//this.bgLayer2 = this.add.image(400, 300, 'bg-layer2').setOrigin(0.5);
//this.bgLayer3 = this.add.image(400, 300, 'bg-layer3').setOrigin(0.5);
// Set different speeds for parallax effect (smaller = slower)
this.bgSpeed1 = 0.2;
//this.bgSpeed2 = 0.5;
//this.bgSpeed3 = 0.8;
}
update() {
if (this.gameOver) return;
// Player movement
if (this.cursors.left.isDown) {
this.player.setVelocityX(-200);
} else if (this.cursors.right.isDown) {
this.player.setVelocityX(200);
} else {
this.player.setVelocityX(0);
}
// Update background positions for parallax effect
this.updateBackgrounds();
// Get A/D keys for player movement
const aKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A);
const dKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D);
// Shooting
if (Phaser.Input.Keyboard.JustDown(this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE))) {
this.shoot();
// Update player with A/D controls
this.player.update(aKey, dKey);
}
updateBackgrounds() {
// Move background layers at different speeds
this.bgLayer1.y += this.bgSpeed1;
//this.bgLayer2.x -= this.bgSpeed2;
//this.bgLayer3.x -= this.bgSpeed3;
// Reset positions to create continuous scrolling effect
console.log(this.bgLayer1.y);
if (this.bgLayer1.y > 736) {
this.bgLayer1.y = 30; // Width of game + layer width
}
// if (this.bgLayer2.x < -400) {
// this.bgLayer2.x = 1200;
// }
// if (this.bgLayer3.x < -400) {
// this.bgLayer3.x = 1200;
// }
}
setupMouseInput() {
// Create mouse reticle as sprite (replacing the circle)
this.reticle = this.add.sprite(0, 0, 'reticle');
this.reticle.setOrigin(0.5);
this.reticle.preFX.addGlow(0x00ffcc, .7);
// Update reticle position with mouse
this.input.on('pointermove', (pointer) => {
if (this.gameOver) return;
this.reticle.x = pointer.x;
this.reticle.y = pointer.y;
});
// Shooting with left mouse button
this.input.on('pointerdown', (pointer) => {
if (this.gameOver) return;
if (pointer.leftButtonReleased()) {
this.shoot(pointer);
}
});
}
createPlaceholderGraphics() {
@ -76,17 +157,17 @@ export class GameScene extends Phaser.Scene {
canvas.width = 32;
canvas.height = 32;
// Player ship (triangle)
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#00ffcc';
ctx.beginPath();
ctx.moveTo(16, 5);
ctx.lineTo(5, 30);
ctx.lineTo(27, 30);
ctx.closePath();
ctx.fill();
// // Player ship (triangle)
// const ctx = canvas.getContext('2d');
// ctx.fillStyle = '#00ffcc';
// ctx.beginPath();
// ctx.moveTo(16, 5);
// ctx.lineTo(5, 30);
// ctx.lineTo(27, 30);
// ctx.closePath();
// ctx.fill();
this.textures.addCanvas('player', canvas);
// this.textures.addCanvas('player', canvas);
// Enemy (square)
const enemyCanvas = document.createElement('canvas');
@ -116,14 +197,14 @@ export class GameScene extends Phaser.Scene {
createUI() {
// Create score text
this.scoreText = this.add.text(16, 16, 'Score: 0', {
fontSize: '32px',
fontSize: '28px',
fill: '#ffffff',
fontFamily: 'Arial'
});
// Create game title
this.titleText = this.add.text(400, 50, 'Zenith Vector', {
fontSize: '48px',
this.titleText = this.add.text(300, 50, 'Zenith Vector', {
fontSize: '40px',
fill: '#00ffcc',
fontFamily: 'Arial',
align: 'center'
@ -131,13 +212,32 @@ export class GameScene extends Phaser.Scene {
this.titleText.setOrigin(0.5);
}
shoot() {
const bullet = this.bullets.create(this.player.x, this.player.y - 20, 'bullet');
bullet.setVelocityY(-300);
shoot(pointer) {
// Calculate angle between player and mouse
const angle = Phaser.Math.Angle.Between(
this.player.sprite.x,
this.player.sprite.y,
pointer.x,
pointer.y
);
// Create bullet at player position
const bullet = this.bullets.create(this.player.sprite.x, this.player.sprite.y, 'bullet');
// Set velocity based on angle and speed
const speed = 400;
bullet.setVelocityX(Math.cos(angle) * speed);
bullet.setVelocityY(Math.sin(angle) * speed);
// Scale bullet
bullet.setScale(1.5);
// Sound Effects
this.sound.play('main-gun');
}
spawnEnemy() {
// Spawn enemies at random x position within game width (800px)
const x = Phaser.Math.Between(20, 780);
const enemy = this.enemies.create(x, 0, 'enemy');
@ -152,6 +252,7 @@ export class GameScene extends Phaser.Scene {
hitEnemy(bullet, enemy) {
bullet.destroy();
enemy.destroy();
this.sound.play('enemy-kill');
this.score += 10;
this.scoreText.setText('Score: ' + this.score);
@ -164,8 +265,8 @@ export class GameScene extends Phaser.Scene {
this.gameOver = true;
// Show game over text
const gameOverText = this.add.text(400, 300, 'GAME OVER', {
fontSize: '64px',
const gameOverText = this.add.text(300, 400, 'GAME OVER', {
fontSize: '54px',
fill: '#ff3366',
fontFamily: 'Arial'
});