diff --git a/assets/sounds/thruster.mp3 b/assets/sounds/thruster.mp3 new file mode 100644 index 0000000..6a4d023 Binary files /dev/null and b/assets/sounds/thruster.mp3 differ diff --git a/src/Player.js b/src/Player.js index ff6db0a..de2c143 100644 --- a/src/Player.js +++ b/src/Player.js @@ -3,6 +3,9 @@ export class Player { this.scene = scene; this.sprite = null; this.cursors = null; + this.thruster = null; + this.trailParticles = []; + this.trailCounter = 0; } create() { @@ -13,15 +16,53 @@ export class Player { // Setup controls this.cursors = this.scene.input.keyboard.createCursorKeys(); - // Replace cursor keys with A/D keys for movement + // Replace cursor keys with A/D/W/S 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); + const wKey = this.scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.W); + const sKey = this.scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S); - return { aKey, dKey }; + // Create thruster effect + this.createThruster(); + + return { aKey, dKey, wKey, sKey }; } - update(aKey, dKey) { - // Player movement with A/D keys + createThruster() { + // Create a simple thruster graphic using canvas + const canvas = document.createElement('canvas'); + canvas.width = 32; + canvas.height = 32; + + const ctx = canvas.getContext('2d'); + + // Draw thruster flame effect (yellow/orange gradient) + const gradient = ctx.createRadialGradient(16, 16, 0, 16, 16, 16); + gradient.addColorStop(0, '#ffff00'); + gradient.addColorStop(0.5, '#ff9900'); + gradient.addColorStop(1, '#ff3300'); + + ctx.beginPath(); + ctx.arc(16, 16, 12, 0, Math.PI * 2); + ctx.fillStyle = gradient; + ctx.fill(); + + // Add some flickering effect with a second inner circle + ctx.beginPath(); + ctx.arc(16, 16, 8, 0, Math.PI * 2); + ctx.fillStyle = '#ffffff'; + ctx.fill(); + + this.scene.textures.addCanvas('thruster', canvas); + + // Create thruster sprite (initially hidden) + this.thruster = this.scene.add.sprite(0, 0, 'thruster'); + this.thruster.setOrigin(0.5); + this.thruster.setVisible(false); + } + + update(aKey, dKey, wKey, sKey) { + // Player movement with A/D keys for horizontal if (aKey.isDown) { this.sprite.setVelocityX(-200); this.sprite.setFrame(1); @@ -32,6 +73,104 @@ export class Player { this.sprite.setVelocityX(0); this.sprite.setFrame(0); } + + // Player movement with W/S keys for vertical + if (wKey.isDown) { + // Limit upward movement to 200px from bottom of screen (500px y position) + if (this.sprite.y > 500) { + this.sprite.setVelocityY(-200); + } else { + this.sprite.setVelocityY(0); + } + } else if (sKey.isDown) { + // Limit downward movement to 700px from top of screen + if (this.sprite.y < 700) { + this.sprite.setVelocityY(200); + } else { + this.sprite.setVelocityY(0); + } + } else { + this.sprite.setVelocityY(0); + } + + // Handle thruster effect when player is accelerating upward + if (wKey.isDown) { + // Show and position the thruster + if (!this.thruster.visible) { + this.thruster.setVisible(true); + // Play sound and store the instance + this.thrusterSoundInstance = this.scene.sound.add('thruster'); + this.thrusterSoundInstance.play(); + } + + // Position the thruster behind the player (below the ship) + this.thruster.x = this.sprite.x; + this.thruster.y = this.sprite.y + 25; // Position below player + + // Add some flickering animation effect + const scale = 0.8 + Math.sin(this.scene.time.now * 0.01) * 0.2; + this.thruster.setScale(scale); + + // Create trail particles when moving upward + if (this.sprite.y > 500) { + this.createTrail(); + } + } else { + // Hide the thruster when W key is not pressed + if (this.thruster.visible) { + this.thruster.setVisible(false); + // Stop the thruster sound when thrusting stops + if (this.thrusterSoundInstance) { + this.thrusterSoundInstance.stop(); + this.thrusterSoundInstance = null; + } + } + } + + // Update trail particles + this.updateTrails(); + } + + createTrail() { + // Only create a new trail particle every few frames to avoid too many particles + this.trailCounter++; + if (this.trailCounter < 3) return; + + this.trailCounter = 0; + + // Create a trail particle at player position + const trailParticle = this.scene.add.sprite(this.sprite.x, this.sprite.y + 30, 'thruster'); + trailParticle.setOrigin(0.5); + + // Make it smaller and more transparent for the trail effect + trailParticle.setScale(0.4); + trailParticle.setAlpha(0.7); + + // Store particle with its fade speed + this.trailParticles.push({ + sprite: trailParticle, + alphaSpeed: 0.01, + scaleSpeed: -0.005 + }); + } + + updateTrails() { + // Update and remove old trail particles + for (let i = this.trailParticles.length - 1; i >= 0; i--) { + const particle = this.trailParticles[i]; + + // Gradually fade out the particle + particle.sprite.setAlpha(particle.sprite.alpha - particle.alphaSpeed); + + // Gradually shrink the particle + particle.sprite.setScale(particle.sprite.scale + particle.scaleSpeed); + + // Remove particles that are fully faded or too small + if (particle.sprite.alpha <= 0 || particle.sprite.scale <= 0) { + particle.sprite.destroy(); + this.trailParticles.splice(i, 1); + } + } } shoot() { diff --git a/src/scenes/GameScene.js b/src/scenes/GameScene.js index e26dcb6..84cb80a 100644 --- a/src/scenes/GameScene.js +++ b/src/scenes/GameScene.js @@ -22,6 +22,25 @@ export class GameScene extends Phaser.Scene { // Wave system this.waveTimer = 0; this.currentWave = 0; + + // Wave configuration - easier to manage and extend + this.waveConfig = [ + { + waveNumber: 0, + time: 0, + enemyTypes: [0] // Only basic enemies in wave 1 + }, + { + waveNumber: 1, + time: 20000, + enemyTypes: [1] // Enemies that can shoot in wave 2 + }, + { + waveNumber: 2, + time: 40000, + enemyTypes: [2] // Special enemies in wave 3 + } + ]; } preload() { @@ -55,6 +74,7 @@ export class GameScene extends Phaser.Scene { this.load.audio('player-death', 'assets/sounds/player-death.mp3'); this.load.audio('enemy-shoot', 'assets/sounds/enemy-shoot.mp3'); this.load.audio('next-wave', 'assets/sounds/next-wave.mp3'); + this.load.audio('thruster', 'assets/sounds/thruster.mp3'); // Load Fonts this.load.font('Space', 'assets/space-age.otf'); @@ -124,29 +144,47 @@ export class GameScene extends Phaser.Scene { // Update wave timer this.waveTimer += delta; - // Check for wave changes every 30 seconds (30,000 milliseconds) - if (this.waveTimer >= 30000 && this.currentWave === 0) { - this.startWave(1); - } else if (this.waveTimer >= 60000 && this.currentWave === 1) { - this.startWave(2); - } + // Check for wave changes using the configuration array + this.checkWaveChanges(); - // Get A/D keys for player movement + // Get keyboard 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); + const wKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.W); + const sKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S); - // Update player with A/D controls - this.player.update(aKey, dKey); + // Update player with movement controls + this.player.update(aKey, dKey, wKey, sKey); // Check if enemies are off-screen and destroy them this.checkEnemiesOffScreen(); } updateBackgrounds() { - // Move background layers at different speeds - this.bgLayer1.y += this.bgSpeed1; - this.bgLayer2.y += this.bgSpeed2; - this.bgLayer3.y += this.bgSpeed3; + // Calculate speed multiplier based on player position + // When player is at 700px (bottom), use normal speed + // When player is at 500px (top), move faster (2x) + const maxSpeed = 5.0; // Maximum speed multiplier + const minSpeed = 1.0; // Minimum speed multiplier + + let speedMultiplier = minSpeed; + + if (this.player && this.player.sprite) { + const playerY = this.player.sprite.y; + + // Map Y position to speed multiplier (700px = normal, 500px = max) + if (playerY < 700) { + speedMultiplier = minSpeed + ((700 - playerY) / (700 - 500)) * (maxSpeed - minSpeed); + } + + // Ensure we don't go below minimum + speedMultiplier = Math.max(minSpeed, speedMultiplier); + } + + // Move background layers at different speeds with the calculated multiplier + this.bgLayer1.y += this.bgSpeed1 * speedMultiplier; + this.bgLayer2.y += this.bgSpeed2 * speedMultiplier; + this.bgLayer3.y += this.bgSpeed3 * speedMultiplier; // Reset positions to create continuous scrolling effect if (this.bgLayer1.y > 736) { @@ -160,6 +198,19 @@ export class GameScene extends Phaser.Scene { } } + checkWaveChanges() { + // Iterate through wave configurations to find which wave should start + for (let i = 0; i < this.waveConfig.length; i++) { + const wave = this.waveConfig[i]; + + // If we haven't started this wave yet and the timer has passed + if (this.waveTimer >= wave.time && this.currentWave === wave.waveNumber - 1) { + this.startWave(wave.waveNumber); + break; // Only start one wave per update cycle + } + } + } + setupMouseInput() { // Create mouse reticle as sprite (replacing the circle) this.reticle = this.add.sprite(0, 0, 'reticle'); @@ -244,7 +295,7 @@ export class GameScene extends Phaser.Scene { // align: 'center' // }); // this.titleText.setOrigin(0.5); - this.titleText = this.add.image(300, 50, 'logo').setScale(.1).setOrigin(0.5); + this.titleText = this.add.image(500, 50, 'logo').setScale(.3).setOrigin(0.5).postFX.addGlow(); } shoot(pointer) { @@ -374,6 +425,12 @@ export class GameScene extends Phaser.Scene { }); gameOverText.setOrigin(0.5); + this.tweens.add({ + targets: this.bgMusic, + volume: 0, + duration: 5000, + }); + // Return to menu after delay this.time.delayedCall(5000, () => { // Fade camera out before returning to menu diff --git a/src/scenes/MenuScene.js b/src/scenes/MenuScene.js index e219b30..0beef5f 100644 --- a/src/scenes/MenuScene.js +++ b/src/scenes/MenuScene.js @@ -54,7 +54,7 @@ export class MenuScene extends Phaser.Scene { }) // Controls display with typing animation - const controlsText = 'Controls: A and D to go Left and Right. Mouse to Aim and Fire'; + const controlsText = 'Controls: W,A,S,D to Move. Mouse to Aim and Fire'; this.controlsText = this.add.text(300, 500, '', { fontFamily: 'Coder, Arial', fontSize: '16px',