import Asteroid from '../entities/Asteroid.js'; const W = 1600; const H = 900; export default class MenuScene extends Phaser.Scene { constructor() { super({ key: 'MenuScene' }); } // ─── Lifecycle ──────────────────────────────────────────────────────────── create() { this.cameras.main.setBackgroundColor('#000011'); // Background asteroids (no collision needed – just decoration) this.asteroidsGroup = this.physics.add.group(); this.asteroids = []; for (let i = 0; i < 9; i++) { this._spawnAsteroid(); } // Wandering alien – plain physics sprite, no collision setup this.alienSprite = this.physics.add.sprite( Phaser.Math.Between(300, 1300), Phaser.Math.Between(300, 600), 'alien' ); this.alienSprite.body.setAllowGravity(false); this._setAlienVelocity(); // Periodically change alien direction this.time.addEvent({ delay: 2600, callback: this._setAlienVelocity, callbackScope: this, loop: true }); // UI layers (rendered on top of background entities) this._drawTitle(); this._drawControlsPanel(); this._drawStartButton(); // Apply CRT post-processing (scanlines + screen curvature) if (this.renderer.type === Phaser.WEBGL) { this.cameras.main.setPostPipeline('CRTPipeline'); } // Title music — looping; stop when this scene shuts down this.music = this.sound.add('title', { loop: true, volume: 0.7 }); this.music.play(); this.events.once('shutdown', () => { if (this.music) this.music.stop(); }); } update() { this.asteroids.forEach(a => a.update()); if (this.alienSprite && this.alienSprite.active) { this.physics.world.wrap(this.alienSprite, 40); } } // ─── Background Entities ────────────────────────────────────────────────── _spawnAsteroid() { const x = Phaser.Math.Between(40, W - 40); const y = Phaser.Math.Between(40, H - 40); // Weight toward large to fill the screen nicely const roll = Math.random(); const size = roll < 0.5 ? 'large' : roll < 0.78 ? 'medium' : 'small'; this.asteroids.push(new Asteroid(this, x, y, size, this.asteroidsGroup)); } _setAlienVelocity() { if (!this.alienSprite || !this.alienSprite.active) return; const angle = Math.random() * Math.PI * 2; const speed = 75 + Math.random() * 65; this.alienSprite.setVelocity(Math.cos(angle) * speed, Math.sin(angle) * speed); } // ─── Title ──────────────────────────────────────────────────────────────── // // "3D block letter" look: stack several offset copies of the text in // progressively lighter shades (back → front), finishing with the bright // face on top. Each copy is offset by (i, i) pixels, so the layers // form a visible extrusion going down-right. _drawTitle() { const cx = W / 2; const cy = 155; const titleText = 'AIsteroids 2026'; const style = { fontFamily: '"Courier New", Courier, monospace', fontSize: '98px', fontStyle: 'bold', align: 'center' }; // Extrusion layers – back (darkest, largest offset) to front const extrusionLayers = [ { offset: 9, color: '#2a0c00' }, { offset: 7, color: '#3e1400' }, { offset: 5, color: '#5c2200' }, { offset: 4, color: '#7a3300' }, { offset: 3, color: '#9a4800' }, { offset: 2, color: '#bb6200' }, { offset: 1, color: '#d97e00' }, ]; extrusionLayers.forEach(({ offset, color }) => { this.add.text(cx + offset, cy + offset, titleText, { ...style, color }) .setOrigin(0.5) .setDepth(5); }); // Bright front face const front = this.add.text(cx, cy, titleText, { ...style, color: '#ffee44', stroke: '#ffbb00', strokeThickness: 2 }).setOrigin(0.5).setDepth(6); // Gentle pulse so it feels alive this.tweens.add({ targets: front, alpha: { from: 1, to: 0.75 }, duration: 1900, ease: 'Sine.InOut', yoyo: true, repeat: -1 }); // Thin decorative lines flanking the title const lineY = cy + 68; const gfx = this.add.graphics().setDepth(5); gfx.lineStyle(1, 0x886622, 0.7); gfx.beginPath(); gfx.moveTo(80, lineY); gfx.lineTo(560, lineY); gfx.moveTo(1040, lineY); gfx.lineTo(1520, lineY); gfx.strokePath(); } // ─── Controls Panel ─────────────────────────────────────────────────────── _drawControlsPanel() { const panelX = W / 2; const panelY = 490; const panelW = 820; const panelH = 320; // Semi-transparent backing so text is readable over floating asteroids const gfx = this.add.graphics().setDepth(7); gfx.fillStyle(0x000018, 0.82); gfx.fillRoundedRect(panelX - panelW / 2, panelY - panelH / 2, panelW, panelH, 12); gfx.lineStyle(1, 0x224466, 0.9); gfx.strokeRoundedRect(panelX - panelW / 2, panelY - panelH / 2, panelW, panelH, 12); // Header this.add.text(panelX, panelY - panelH / 2 + 26, '── HOW TO PLAY ──', { fontFamily: 'monospace', fontSize: '20px', color: '#44ddaa', align: 'center' }).setOrigin(0.5, 0).setDepth(8); // Control entries const entries = [ ['Mouse', 'Aim your ship'], ['Hold LMB', 'Thrust / Accelerate'], ['A or SPACE', 'Fire weapon'], ]; const entryBaseY = panelY - panelH / 2 + 68; const keyStyle = { fontFamily: 'monospace', fontSize: '19px', color: '#ffdd66', align: 'right' }; const descStyle = { fontFamily: 'monospace', fontSize: '19px', color: '#aaccff', align: 'left' }; entries.forEach(([key, desc], i) => { const ey = entryBaseY + i * 36; this.add.text(panelX - 20, ey, key, keyStyle).setOrigin(1, 0).setDepth(8); this.add.text(panelX - 10, ey, '→', { ...descStyle, color: '#556677' }).setOrigin(0, 0).setDepth(8); this.add.text(panelX + 10, ey, desc, descStyle).setOrigin(0, 0).setDepth(8); }); // Flavour text const noteStyle = { fontFamily: 'monospace', fontSize: '17px', color: '#778899', align: 'center' }; this.add.text(panelX, panelY + panelH / 2 - 62, 'Destroy all asteroids and alien ships to advance levels.', noteStyle ).setOrigin(0.5, 0).setDepth(8); this.add.text(panelX, panelY + panelH / 2 - 36, 'Beware — aliens will hunt you and fire back!', { ...noteStyle, color: '#cc6655' } ).setOrigin(0.5, 0).setDepth(8); } // ─── Start Button ───────────────────────────────────────────────────────── _drawStartButton() { const cx = W / 2; const btn = this.add.text(cx, 790, '[ START GAME ]', { fontFamily: 'monospace', fontSize: '52px', fontStyle: 'bold', color: '#00ff88', stroke: '#007744', strokeThickness: 4 }).setOrigin(0.5).setDepth(9).setInteractive({ useHandCursor: true }); btn.on('pointerover', () => btn.setStyle({ color: '#ffffff', stroke: '#00cc66' })); btn.on('pointerout', () => btn.setStyle({ color: '#00ff88', stroke: '#007744' })); btn.on('pointerdown', () => this._startGame()); // Also allow Enter key this.input.keyboard.once('keydown-ENTER', () => this._startGame()); // Slow blink so it draws the eye this.tweens.add({ targets: btn, alpha: { from: 1, to: 0.35 }, duration: 700, ease: 'Sine.InOut', yoyo: true, repeat: -1 }); this.add.text(cx, 848, 'or press ENTER', { fontFamily: 'monospace', fontSize: '16px', color: '#445566' }).setOrigin(0.5).setDepth(9); } // ─── Transition ─────────────────────────────────────────────────────────── _startGame() { // Phaser shuts down this scene automatically when the next one starts; // all timers, tweens, and physics objects are cleaned up by the engine. this.scene.start('GameScene'); } }