Compare commits
2 Commits
1af359e09f
...
eb01c3d89c
| Author | SHA1 | Date |
|---|---|---|
|
|
eb01c3d89c | |
|
|
7b56d5b46a |
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 244 KiB |
|
After Width: | Height: | Size: 288 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
Before Width: | Height: | Size: 2.4 MiB After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 679 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 4.4 KiB |
|
|
@ -30,12 +30,6 @@
|
|||
<script src="phaser.min.js"></script>
|
||||
|
||||
<!-- Import game modules using ES6 modules -->
|
||||
<script type="module">
|
||||
import { config } from './src/config.js';
|
||||
import { GameScene } from './src/scenes/GameScene.js';
|
||||
|
||||
const game = new Phaser.Game(config);
|
||||
game.scene.add('GameScene', GameScene, true);
|
||||
</script>
|
||||
<script type="module" src="./src/config.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
147
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() {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import { MenuScene } from './scenes/MenuScene.js';
|
||||
import { GameScene } from './scenes/GameScene.js';
|
||||
|
||||
export const config = {
|
||||
type: Phaser.AUTO,
|
||||
width: 600,
|
||||
|
|
@ -10,13 +13,14 @@ export const config = {
|
|||
debug: false
|
||||
}
|
||||
},
|
||||
scene: {
|
||||
preload: function() {},
|
||||
create: function() {},
|
||||
update: function() {}
|
||||
},
|
||||
scene: [
|
||||
MenuScene,
|
||||
GameScene,
|
||||
],
|
||||
scale: {
|
||||
mode: Phaser.Scale.FIT,
|
||||
autoCenter: Phaser.Scale.CENTER_BOTH
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const game = new Phaser.Game(config);
|
||||
|
|
@ -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() {
|
||||
|
|
@ -33,8 +52,9 @@ export class GameScene extends Phaser.Scene {
|
|||
|
||||
// Load background images
|
||||
this.load.image('bg-layer1', 'assets/neon-city-bg.png');
|
||||
//this.load.image('bg-layer2', 'assets/bg-layer2.png');
|
||||
//this.load.image('bg-layer3', 'assets/bg-layer3.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', {
|
||||
|
|
@ -54,6 +74,10 @@ 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');
|
||||
|
||||
// Create simple placeholder graphics for now
|
||||
this.createPlaceholderGraphics();
|
||||
|
|
@ -102,13 +126,13 @@ export class GameScene extends Phaser.Scene {
|
|||
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(400, 300, 'bg-layer2').setOrigin(0.5);
|
||||
//this.bgLayer3 = this.add.image(400, 300, 'bg-layer3').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;
|
||||
this.bgSpeed2 = 0.5;
|
||||
this.bgSpeed3 = 0.8;
|
||||
}
|
||||
|
||||
update(time, delta) {
|
||||
|
|
@ -120,40 +144,71 @@ 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.x -= this.bgSpeed2;
|
||||
//this.bgLayer3.x -= 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) {
|
||||
this.bgLayer1.y = 30; // Width of game + layer width
|
||||
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
|
||||
}
|
||||
}
|
||||
// if (this.bgLayer2.x < -400) {
|
||||
// this.bgLayer2.x = 1200;
|
||||
// }
|
||||
// if (this.bgLayer3.x < -400) {
|
||||
// this.bgLayer3.x = 1200;
|
||||
// }
|
||||
}
|
||||
|
||||
setupMouseInput() {
|
||||
|
|
@ -229,17 +284,18 @@ export class GameScene extends Phaser.Scene {
|
|||
this.scoreText = this.add.text(16, 16, 'Score: 0', {
|
||||
fontSize: '28px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'Arial'
|
||||
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.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) {
|
||||
|
|
@ -365,14 +421,30 @@ export class GameScene extends Phaser.Scene {
|
|||
const gameOverText = this.add.text(300, 400, 'GAME OVER', {
|
||||
fontSize: '54px',
|
||||
fill: '#ff3366',
|
||||
fontFamily: 'Arial'
|
||||
fontFamily: 'Space, Arial'
|
||||
});
|
||||
gameOverText.setOrigin(0.5);
|
||||
|
||||
// Restart game after delay
|
||||
// this.time.delayedCall(2000, () => {
|
||||
// this.scene.restart();
|
||||
// });
|
||||
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) {
|
||||
|
|
@ -381,9 +453,9 @@ export class GameScene extends Phaser.Scene {
|
|||
|
||||
// Add wave indicator text
|
||||
const waveText = this.add.text(300, 150, `WAVE ${waveNumber + 1}`, {
|
||||
fontSize: '48px',
|
||||
fontSize: '48px',
|
||||
fill: '#ffff00',
|
||||
fontFamily: 'Arial'
|
||||
fontFamily: 'Space, Arial'
|
||||
});
|
||||
waveText.setOrigin(0.5);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,299 @@
|
|||
export class MenuScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super('MenuScene');
|
||||
}
|
||||
|
||||
preload() {
|
||||
this.load.image('menu-logo','assets/logo.png');
|
||||
this.load.image('menu-ship','assets/menu-ship.png');
|
||||
this.load.image('menu-hunter','assets/menu-hunter.png');
|
||||
this.load.image('menu-alien','assets/menu-alien.png');
|
||||
this.load.image('reticle', 'assets/reticle.png');
|
||||
this.load.audio('menu-bgMusic', 'assets/music/NeonVelocity.mp3');
|
||||
|
||||
// Load custom font
|
||||
this.load.font('Space', 'assets/space-age.otf');
|
||||
this.load.font('coder', 'assets/ClassCoder.ttf');
|
||||
}
|
||||
|
||||
create() {
|
||||
// Create menu background and title
|
||||
this.add.rectangle(300, 400, 600, 800, 0x111111);
|
||||
|
||||
// Create starfield effect
|
||||
this.createStarfield();
|
||||
|
||||
const logo = this.add.image(300, 250, 'menu-logo').setOrigin(0.5).setScale(0.5).setAlpha(0.7);
|
||||
const menuHunter = this.add.image(450, 700, 'menu-hunter').setScale(0.6).setOrigin(0.5).setAlpha(0);
|
||||
const menuShip = this.add.image(900, -300, 'menu-ship').setOrigin(0.5).setScale(.6);
|
||||
const menuAlien = this.add.image(150, 700, 'menu-alien').setScale(0.6).setOrigin(0.5).setAlpha(0);
|
||||
|
||||
this.tweens.add({
|
||||
targets: menuShip,
|
||||
x: 500,
|
||||
y: 100,
|
||||
duration: 2000,
|
||||
ease: 'Cubic',
|
||||
delay: 3000
|
||||
});
|
||||
|
||||
this.tweens.add({
|
||||
targets: logo,
|
||||
alpha: 1,
|
||||
scale: 1,
|
||||
duration: 5000,
|
||||
ease: 'Bounce',
|
||||
});
|
||||
|
||||
this.tweens.add({
|
||||
targets: [menuHunter, menuAlien],
|
||||
alpha: 1,
|
||||
duration: 3000,
|
||||
delay: 4000,
|
||||
ease: 'Bounce'
|
||||
})
|
||||
|
||||
// Controls display with typing animation
|
||||
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',
|
||||
fill: '#00ff00'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
// Typing animation
|
||||
this.time.delayedCall(1000, () => {
|
||||
let i = 0;
|
||||
const typingTimer = this.time.addEvent({
|
||||
delay: 50, // Characters per second
|
||||
callback: () => {
|
||||
if (i < controlsText.length) {
|
||||
this.controlsText.text += controlsText.charAt(i);
|
||||
i++;
|
||||
} else {
|
||||
typingTimer.remove();
|
||||
}
|
||||
},
|
||||
callbackScope: this,
|
||||
loop: true
|
||||
});
|
||||
});
|
||||
|
||||
// Menu button
|
||||
this.startButton = this.add.text(300, 550, ' START GAME ', {
|
||||
fontFamily: 'Space, Arial',
|
||||
fontSize: '48px',
|
||||
fill: '#fff'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
// Add color cycling effect for synthwave colors
|
||||
const synthwaveColors = ['#ffffff', '#ff00ff', '#00ffff', '#ffff00', '#ff00cc'];
|
||||
let colorIndex = 0;
|
||||
|
||||
this.time.addEvent({
|
||||
delay: 1500,
|
||||
callback: () => {
|
||||
colorIndex = (colorIndex + 1) % synthwaveColors.length;
|
||||
this.startButton.setFill(synthwaveColors[colorIndex]);
|
||||
},
|
||||
loop: true
|
||||
});
|
||||
|
||||
this.tweens.add({
|
||||
targets: this.startButton,
|
||||
scale: 1.2,
|
||||
duration: 1500,
|
||||
repeat: -1,
|
||||
ease: 'Sine',
|
||||
yoyo: true,
|
||||
});
|
||||
|
||||
this.startButton.setInteractive();
|
||||
this.startButton.on('pointerdown', () => {
|
||||
// Start the actual game scene
|
||||
this.bgMusic.stop();
|
||||
this.scene.start('GameScene');
|
||||
});
|
||||
|
||||
// Play background music
|
||||
this.bgMusic = this.sound.add('menu-bgMusic', { loop: true });
|
||||
this.bgMusic.play();
|
||||
|
||||
// Setup mouse input for shooting and reticle
|
||||
this.setupMouseInput();
|
||||
|
||||
// Add scanline effect (narrow and moving)
|
||||
this.scanlines = this.add.graphics({ x: 0, y: 0 });
|
||||
this.scanlines.fillStyle(0x00ff00, 0.15); // Green scanlines with moderate opacity
|
||||
// Create narrow horizontal lines that move vertically
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const y = (i * 40) % 800;
|
||||
this.scanlines.fillRect(0, y, 600, 2); // Narrow lines (2px height)
|
||||
}
|
||||
|
||||
// Add VHS artifacts
|
||||
this.vhsArtifacts = this.add.graphics({ x: 0, y: 0 });
|
||||
this.vhsArtifacts.fillStyle(0xff00ff, 0.1); // Magenta artifacts
|
||||
this.vhsArtifacts.fillRect(0, 0, 600, 800);
|
||||
|
||||
// Add VHS artifacts 2
|
||||
this.vhsArtifacts2 = this.add.graphics({ x: 0, y: 0 });
|
||||
this.vhsArtifacts2.fillStyle(0xff00ff, 0.1); // Magenta artifacts
|
||||
this.vhsArtifacts2.fillRect(0, 0, 600, 800);
|
||||
|
||||
this.tweens.add({
|
||||
targets: this.vhsArtifacts2,
|
||||
alpha: 0.1,
|
||||
duration: 10000,
|
||||
yoyo: true,
|
||||
repeat: -1
|
||||
});
|
||||
|
||||
// Start artifact effects
|
||||
this.artifactTimer = this.time.addEvent({
|
||||
delay: 200,
|
||||
callback: this.updateVHSArtifacts,
|
||||
callbackScope: this,
|
||||
loop: true
|
||||
});
|
||||
}
|
||||
|
||||
createStarfield() {
|
||||
// Create a graphics object for the starfield
|
||||
this.starfield = this.add.graphics({ x: 0, y: 0 });
|
||||
|
||||
// Star configuration
|
||||
this.stars = [];
|
||||
const starCount = 200;
|
||||
|
||||
// Initialize stars with random positions and z-depths
|
||||
for (let i = 0; i < starCount; i++) {
|
||||
const star = {
|
||||
x: Math.random() * 600,
|
||||
y: Math.random() * 800,
|
||||
z: Math.random() * 10 + 1, // Depth from 1 to 11
|
||||
size: Math.random() * 2 + 1,
|
||||
speed: Math.random() * 0.5 + 0.2 // Significantly reduced speed
|
||||
};
|
||||
|
||||
// Set color based on depth (closer stars are brighter)
|
||||
const brightness = Math.max(0.5, 1 - star.z / 15); // Closer stars brighter
|
||||
const grayValue = Math.floor(150 + brightness * 105);
|
||||
star.color = (grayValue << 16) | (grayValue << 8) | grayValue;
|
||||
|
||||
this.stars.push(star);
|
||||
}
|
||||
|
||||
// Change direction timer - increased to make it more subtle
|
||||
this.directionChangeTimer = this.time.addEvent({
|
||||
delay: 5000, // Increase delay to 5 seconds
|
||||
callback: this.changeStarDirection,
|
||||
callbackScope: this,
|
||||
loop: true
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
// Update star positions
|
||||
if (this.starfield && this.stars) {
|
||||
this.starfield.clear();
|
||||
|
||||
for (let i = 0; i < this.stars.length; i++) {
|
||||
const star = this.stars[i];
|
||||
|
||||
// Move stars toward camera (decrease z) - greatly reduced speed
|
||||
star.z -= star.speed * 0.02;
|
||||
|
||||
// Reset stars that move too close to camera
|
||||
if (star.z <= 0) {
|
||||
star.z = Math.random() * 10 + 1; // Reset with new depth
|
||||
star.x = Math.random() * 600; // Random x position
|
||||
star.y = Math.random() * 800; // Random y position
|
||||
|
||||
// Recalculate color based on new depth
|
||||
const brightness = Math.max(0.5, 1 - star.z / 15);
|
||||
const grayValue = Math.floor(150 + brightness * 105);
|
||||
star.color = (grayValue << 16) | (grayValue << 8) | grayValue;
|
||||
}
|
||||
|
||||
// Apply perspective projection to create parallax effect
|
||||
const scale = 20 / (star.z + 20); // Scale based on depth
|
||||
const screenX = star.x * scale;
|
||||
const screenY = star.y * scale;
|
||||
|
||||
// Draw the star with its color and scaled size
|
||||
this.starfield.fillStyle(star.color);
|
||||
this.starfield.fillCircle(screenX, screenY, star.size * scale);
|
||||
}
|
||||
}
|
||||
|
||||
// Update scanline position
|
||||
if (this.scanlines) {
|
||||
this.scanlines.y = (this.scanlines.y + 0.5) % 800;
|
||||
}
|
||||
}
|
||||
|
||||
updateVHSArtifacts() {
|
||||
// Randomly update VHS artifacts
|
||||
if (this.vhsArtifacts && Math.random() > 0.7) {
|
||||
// Clear previous artifacts but keep some for flickering effect
|
||||
this.vhsArtifacts.clear();
|
||||
|
||||
// Add random color shifts and distortions
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const x = Math.random() * 600;
|
||||
const y = Math.random() * 800;
|
||||
const width = Math.random() * 10 + 1;
|
||||
const height = Math.random() * 20 + 5;
|
||||
|
||||
// Random color (neon colors for synthwave)
|
||||
const colors = [0xff00ff, 0x00ffff, 0xffff00];
|
||||
const color = Phaser.Display.Color.GetColor32(
|
||||
...colors[Math.floor(Math.random() * colors.length)].toString(16).split('').map(c => parseInt(c, 16) * 17)
|
||||
);
|
||||
|
||||
// Use higher opacity for flickering effect (instead of very low opacity)
|
||||
this.vhsArtifacts.fillStyle(color, Math.random() * 0.3 + 0.1); // Increased from 0.05 to 0.3-0.1 range
|
||||
this.vhsArtifacts.fillRect(x, y, width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changeStarDirection() {
|
||||
// Change direction of all stars (they'll move toward camera)
|
||||
for (let i = 0; i < this.stars.length; i++) {
|
||||
const star = this.stars[i];
|
||||
|
||||
// Randomly adjust speed and depth - much slower changes
|
||||
star.speed = Math.random() * 1 + 0.5; // Reduced speed range
|
||||
star.z += (Math.random() * 0.5 - 0.25); // Very subtle z position change
|
||||
|
||||
// Keep stars within reasonable bounds
|
||||
if (star.z < 0.5) {
|
||||
star.z = 0.5;
|
||||
} else if (star.z > 15) {
|
||||
star.z = 15;
|
||||
}
|
||||
|
||||
// Recalculate color based on new depth and speed
|
||||
const brightness = Math.max(0.5, 1 - star.z / 15);
|
||||
const grayValue = Math.floor(150 + brightness * 105);
|
||||
star.color = (grayValue << 16) | (grayValue << 8) | grayValue;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||