522 lines
17 KiB
JavaScript
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();
|
|
}
|
|
});
|
|
}
|
|
} |