Zenith-Vector/src/scenes/GameScene.js

522 lines
17 KiB
JavaScript

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();
}
});
}
}