import { Player } from '../Player.js'; export class GameScene extends Phaser.Scene { constructor() { super({ key: 'GameScene' }); // Game objects references this.player = null; this.enemies = null; this.bullets = null; // Enemy bullets group this.enemyBullets = null; // Game state this.score = 0; this.gameOver = false; // Mouse reticle this.reticle = null; // 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() { // 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/neon-city-dark-clouds2.png'); this.load.image('bg-layer3', 'assets/neon-city-dark-clouds.png'); this.load.image('logo', 'assets/logo.png'); // Load Sprites this.load.spritesheet('player-sprite', 'assets/player-sprite.png', { frameWidth: 48, frameHeight: 48 }); this.load.spritesheet('enemy-sprite', 'assets/enemy-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'); this.load.audio('enemy-shoot', 'assets/sounds/enemy-shoot.mp3'); 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'); // Create simple placeholder graphics for now this.createPlaceholderGraphics(); } create() { // Set background color this.cameras.main.setBackgroundColor('#0a0a2a'); // 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.enemyBullets = this.physics.add.group(); this.enemies = this.physics.add.group(); // Setup collision detection this.physics.add.overlap(this.bullets, this.enemies, this.hitEnemy, null, this); this.physics.add.overlap(this.player.sprite, this.enemies, this.hitPlayer, null, this); this.physics.add.overlap(this.player.sprite, this.enemyBullets, this.hitPlayer, null, this); // Start enemy spawning this.time.addEvent({ delay: 1000, callback: this.spawnEnemy, callbackScope: this, loop: true }); // 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(280, 30, 'bg-layer2').setOrigin(0.5); this.bgLayer3 = this.add.image(280, 30, '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(time, delta) { if (this.gameOver) return; // Update background positions for parallax effect this.updateBackgrounds(); // Update wave timer this.waveTimer += delta; // Check for wave changes using the configuration array this.checkWaveChanges(); // 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 movement controls this.player.update(aKey, dKey, wKey, sKey); // Check if enemies are off-screen and destroy them this.checkEnemiesOffScreen(); } updateBackgrounds() { // 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) { this.bgLayer1.y = 0; } if (this.bgLayer2.y > 1536) { this.bgLayer2.y = 0; } if (this.bgLayer3.y > 1536) { this.bgLayer3.y = 0; } } 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'); 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() { // Create simple placeholder graphics for player, bullets and enemies const canvas = document.createElement('canvas'); canvas.width = 32; canvas.height = 32; // Enemy (square) const enemyCanvas = document.createElement('canvas'); enemyCanvas.width = 24; enemyCanvas.height = 24; const eCtx = enemyCanvas.getContext('2d'); eCtx.fillStyle = '#ff3366'; eCtx.fillRect(0, 0, 24, 24); this.textures.addCanvas('enemy', enemyCanvas); // Bullet (small circle) const bulletCanvas = document.createElement('canvas'); bulletCanvas.width = 8; bulletCanvas.height = 8; const bCtx = bulletCanvas.getContext('2d'); bCtx.fillStyle = '#ffff00'; bCtx.beginPath(); bCtx.arc(4, 4, 3, 0, Math.PI * 2); bCtx.fill(); this.textures.addCanvas('bullet', bulletCanvas); // Enemy Bullet (small circle) const enemyBulletCanvas = document.createElement('canvas'); enemyBulletCanvas.width = 8; enemyBulletCanvas.height = 8; const ebCtx = enemyBulletCanvas.getContext('2d'); ebCtx.fillStyle = '#00ffcc'; ebCtx.beginPath(); ebCtx.arc(4, 4, 3, 0, Math.PI * 2); ebCtx.fill(); this.textures.addCanvas('enemyBullet', enemyBulletCanvas); } createUI() { // Create score text this.scoreText = this.add.text(16, 16, 'Score: 0', { fontSize: '28px', fill: '#ffffff', fontFamily: 'Space, Arial' }); // Create game title // this.titleText = this.add.text(300, 50, 'Zenith Vector', { // fontSize: '40px', // fill: '#00ffcc', // fontFamily: 'Arial', // align: 'center' // }); // this.titleText.setOrigin(0.5); this.titleText = this.add.image(500, 50, 'logo').setScale(.3).setOrigin(0.5).postFX.addGlow(); } 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, 580); // Calculate texture range with minimum of 0 const minTexture = Math.max(0, this.currentWave - 2); const maxTexture = this.currentWave; // Ensure we don't get negative values const texture = Phaser.Math.Between(minTexture, maxTexture); const enemy = this.enemies.create(x, 0, 'enemy-sprite', texture); // Randomize enemy speed const speed = Phaser.Math.Between(50, 150); enemy.setVelocityY(speed); // Add some rotation for visual effect //enemy.setRotation(Phaser.Math.FloatBetween(-0.2, 0.2)); // Apply wave-specific properties this.applyWaveProperties(enemy, texture); } applyWaveProperties(enemy, texture) { // Set the current wave number on the enemy enemy.waveNumber = this.currentWave; // Wave 1: Basic enemies (no shooting) if (this.currentWave === 0) { enemy.canShoot = false; if (enemy.shootTimer) { enemy.shootTimer.remove(); enemy.shootTimer = null; } } // Wave 2: Enemies that can shoot else if (this.currentWave >= 1 && texture > 0) { enemy.canShoot = true; // Only set up shooting timer if it doesn't exist yet if (!enemy.shootTimer) { enemy.shootTimer = this.time.addEvent({ delay: Phaser.Math.Between(1000, 3000), callback: () => this.enemyShoot(enemy, texture), callbackScope: this, loop: true }); } } } hitEnemy(bullet, enemy) { bullet.destroy(); enemy.destroy(); this.sound.play('enemy-kill'); this.score += 10; this.scoreText.setText('Score: ' + this.score); } hitPlayer(player, enemy) { // Create explosion at player's position const explosion = this.add.sprite(this.player.sprite.x, this.player.sprite.y, 'player-sprite', 3); const explosion2 = this.add.sprite(this.player.sprite.x, this.player.sprite.y, 'player-sprite', 3); // Add rotation and scaling animation this.tweens.add({ targets: explosion, scale: 3, // Scale up to 3x rotation: Math.PI * 2, // Full rotation alpha: 0.5, // Fade out slightly duration: 1000, ease: 'Power2', onComplete: () => { explosion.destroy(); } }); this.tweens.add({ targets: explosion2, scale: 3, // Scale up to 3x rotation: -Math.PI * 2, // Full rotation alpha: 0.5, // Fade out slightly duration: 1000, ease: 'Power2', onComplete: () => { explosion2.destroy(); } }); player.destroy(); enemy.destroy(); this.sound.play('player-death'); this.gameOver = true; // Show game over text const gameOverText = this.add.text(300, 400, 'GAME OVER', { fontSize: '54px', fill: '#ff3366', fontFamily: 'Space, Arial' }); 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 this.tweens.add({ targets: this.cameras.main, alpha: 0, // Fade to transparent duration: 1000, // 1 second fade ease: 'Linear', onComplete: () => { this.bgMusic.stop(); this.scene.start('MenuScene'); } }); }); } startWave(waveNumber) { this.currentWave = waveNumber; this.sound.play('next-wave'); // Add wave indicator text const waveText = this.add.text(300, 150, `WAVE ${waveNumber + 1}`, { fontSize: '48px', fill: '#ffff00', fontFamily: 'Space, Arial' }); waveText.setOrigin(0.5); // Add scaling animation this.tweens.add({ targets: waveText, scale: 2, // Grow to double size duration: 1000, ease: 'Elastic.out', // Elastic easing for bouncy effect yoyo: true, // Scale back and forth repeat: 1 // Do the animation twice (total of 3 states) }); // Remove text after delay this.time.delayedCall(2000, () => { waveText.destroy(); }); // Apply wave properties to all existing enemies this.enemies.children.iterate((enemy) => { this.applyWaveProperties(enemy); }); } enemyShoot(enemy, texture) { // Only shoot if enemy can shoot and isn't destroyed if (!enemy.canShoot || !enemy.active) return; console.log(texture); // Check if this is wave 3 and enemy has texture 2 (special shooting) if (this.currentWave >= 2 && enemy.waveNumber >= 2 && texture >= 2) { // Shoot toward player const angle = Phaser.Math.Angle.Between( enemy.x, enemy.y, this.player.sprite.x, this.player.sprite.y ); const bullet = this.enemyBullets.create(enemy.x, enemy.y, 'enemyBullet'); const speed = 200; bullet.setVelocityX(Math.cos(angle) * speed); bullet.setVelocityY(Math.sin(angle) * speed); bullet.setScale(1.5); } else { // Regular straight-down shooting const bullet = this.enemyBullets.create(enemy.x, enemy.y, 'enemyBullet'); bullet.setVelocityY(300); // Move straight down at 200px/s bullet.setScale(1.5); } // Sound effect for enemy shooting this.sound.play('enemy-shoot'); } checkEnemiesOffScreen() { // Destroy enemies that have gone below the screen this.enemies.children.iterate((enemy) => { if (enemy && enemy.y > 800) { // Adjust 600 to your game height as needed enemy.destroy(); } }); } }