diff --git a/.continue/rules/framework.md b/.continue/rules/framework.md new file mode 100644 index 0000000..19857b0 --- /dev/null +++ b/.continue/rules/framework.md @@ -0,0 +1,8 @@ +--- +name: Phaser 3.90 Framework +--- + +- Follow best practices for Phaser 3.90 +- Follow best ES6 practices +- Write class imports using full .js paths, NPM package manager or any other web packager is not installed +- Break things out into classes when it makes sense \ No newline at end of file diff --git a/assets/neon-city-bg.png b/assets/neon-city-bg.png new file mode 100644 index 0000000..f6520bb Binary files /dev/null and b/assets/neon-city-bg.png differ diff --git a/assets/neon-city-bg2.png b/assets/neon-city-bg2.png new file mode 100644 index 0000000..ec94859 Binary files /dev/null and b/assets/neon-city-bg2.png differ diff --git a/assets/player-sprite.png b/assets/player-sprite.png new file mode 100644 index 0000000..b05cc4e Binary files /dev/null and b/assets/player-sprite.png differ diff --git a/assets/reticle-full.png b/assets/reticle-full.png new file mode 100644 index 0000000..752b3a8 Binary files /dev/null and b/assets/reticle-full.png differ diff --git a/assets/reticle.png b/assets/reticle.png new file mode 100644 index 0000000..4ebd4fd Binary files /dev/null and b/assets/reticle.png differ diff --git a/assets/sounds/enemy-kill.mp3 b/assets/sounds/enemy-kill.mp3 new file mode 100644 index 0000000..cf32073 Binary files /dev/null and b/assets/sounds/enemy-kill.mp3 differ diff --git a/assets/sounds/main-gun.mp3 b/assets/sounds/main-gun.mp3 new file mode 100644 index 0000000..888ecc1 Binary files /dev/null and b/assets/sounds/main-gun.mp3 differ diff --git a/index.html b/index.html index e927415..ee44f0f 100644 --- a/index.html +++ b/index.html @@ -17,6 +17,7 @@ } #game-container { position: relative; + cursor: none; } diff --git a/raw/player.psd b/raw/player.psd new file mode 100644 index 0000000..babc5a9 Binary files /dev/null and b/raw/player.psd differ diff --git a/src/Player.js b/src/Player.js new file mode 100644 index 0000000..ff6db0a --- /dev/null +++ b/src/Player.js @@ -0,0 +1,40 @@ +export class Player { + constructor(scene) { + this.scene = scene; + this.sprite = null; + this.cursors = null; + } + + create() { + // Create player ship + this.sprite = this.scene.physics.add.sprite(300, 700, 'player-sprite', 0); + this.sprite.setCollideWorldBounds(true); + + // Setup controls + this.cursors = this.scene.input.keyboard.createCursorKeys(); + + // Replace cursor keys with A/D 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); + + return { aKey, dKey }; + } + + update(aKey, dKey) { + // Player movement with A/D keys + if (aKey.isDown) { + this.sprite.setVelocityX(-200); + this.sprite.setFrame(1); + } else if (dKey.isDown) { + this.sprite.setVelocityX(200); + this.sprite.setFrame(2); + } else { + this.sprite.setVelocityX(0); + this.sprite.setFrame(0); + } + } + + shoot() { + // Shooting functionality will be handled in GameScene + } +} \ No newline at end of file diff --git a/src/config.js b/src/config.js index d6e78b8..f734491 100644 --- a/src/config.js +++ b/src/config.js @@ -1,7 +1,7 @@ export const config = { type: Phaser.AUTO, - width: 800, - height: 600, + width: 600, + height: 800, parent: 'game-container', physics: { default: 'arcade', @@ -14,5 +14,9 @@ export const config = { preload: function() {}, create: function() {}, update: function() {} + }, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH } }; \ No newline at end of file diff --git a/src/objects/Player.js b/src/objects/Player.js deleted file mode 100644 index 9a97cc8..0000000 --- a/src/objects/Player.js +++ /dev/null @@ -1,66 +0,0 @@ -class Player extends Phaser.GameObjects.Container { - constructor(scene, x, y) { - super(scene, x, y); - - // Create the player sprite - this.sprite = scene.add.sprite(0, 0, 'player'); - this.add(this.sprite); - - // Set up input controls using A/D keys instead of arrow keys - this.keys = scene.input.keyboard.addKeys({ - a: Phaser.Input.Keyboard.KeyCodes.A, - d: Phaser.Input.Keyboard.KeyCodes.D - }); - - // Player properties - this.speed = 200; - this.health = 100; - this.isAlive = true; - - // Set initial position - this.setPosition(x, y); - } - - update() { - if (!this.isAlive) return; - - // Move with A/D keys instead of arrow keys - if (this.keys.a.isDown) { - this.x -= this.speed * this.scene.game.loop.delta / 1000; - } - if (this.keys.d.isDown) { - this.x += this.speed * this.scene.game.loop.delta / 1000; - } - - // Keep player within bounds - this.x = Phaser.Math.Clamp(this.x, 25, 775); - - // Update position of the container's child sprite - this.sprite.setPosition(0, 0); - } - - aimAt(targetX, targetY) { - // Not used in current implementation but kept for potential future use - } - - fire() { - if (!this.isAlive) return; - - // Fire bullet from player position - const bullet = new Bullet(this.scene, this.x, this.y - 20); - this.scene.bullets.add(bullet); - - // Set bullet velocity - bullet.setVelocityY(-300); - } - - hit(damage) { - if (!this.isAlive) return; - - this.health -= damage; - - if (this.health <= 0) { - this.destroy(); - } - } -} \ No newline at end of file diff --git a/src/scenes/GameScene.js b/src/scenes/GameScene.js index eb5bd55..52788b2 100644 --- a/src/scenes/GameScene.js +++ b/src/scenes/GameScene.js @@ -1,3 +1,5 @@ +import { Player } from '../Player.js'; + export class GameScene extends Phaser.Scene { constructor() { super({ key: 'GameScene' }); @@ -6,16 +8,37 @@ export class GameScene extends Phaser.Scene { this.player = null; this.enemies = null; this.bullets = null; - this.cursors = null; // Game state this.score = 0; this.gameOver = false; + + // Mouse reticle + this.reticle = null; } 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/bg-layer2.png'); + //this.load.image('bg-layer3', 'assets/bg-layer3.png'); + + // Load Sprites + this.load.spritesheet('player-sprite', 'assets/player-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'); // Create simple placeholder graphics for now this.createPlaceholderGraphics(); @@ -25,20 +48,20 @@ export class GameScene extends Phaser.Scene { // Set background color this.cameras.main.setBackgroundColor('#0a0a2a'); - // Create player ship - this.player = this.physics.add.sprite(400, 500, 'player'); - this.player.setCollideWorldBounds(true); + // 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.enemies = this.physics.add.group(); - // Setup controls - this.cursors = this.input.keyboard.createCursorKeys(); - // Setup collision detection this.physics.add.overlap(this.bullets, this.enemies, this.hitEnemy, null, this); - this.physics.add.overlap(this.player, this.enemies, this.hitPlayer, null, this); + this.physics.add.overlap(this.player.sprite, this.enemies, this.hitPlayer, null, this); // Start enemy spawning this.time.addEvent({ @@ -50,24 +73,82 @@ export class GameScene extends Phaser.Scene { // 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(400, 300, 'bg-layer2').setOrigin(0.5); + //this.bgLayer3 = this.add.image(400, 300, '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() { if (this.gameOver) return; - // Player movement - if (this.cursors.left.isDown) { - this.player.setVelocityX(-200); - } else if (this.cursors.right.isDown) { - this.player.setVelocityX(200); - } else { - this.player.setVelocityX(0); - } + // Update background positions for parallax effect + this.updateBackgrounds(); + + // Get A/D 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); - // Shooting - if (Phaser.Input.Keyboard.JustDown(this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE))) { - this.shoot(); + // Update player with A/D controls + this.player.update(aKey, dKey); + } + + updateBackgrounds() { + // Move background layers at different speeds + this.bgLayer1.y += this.bgSpeed1; + //this.bgLayer2.x -= this.bgSpeed2; + //this.bgLayer3.x -= this.bgSpeed3; + + // Reset positions to create continuous scrolling effect + console.log(this.bgLayer1.y); + if (this.bgLayer1.y > 736) { + this.bgLayer1.y = 30; // Width of game + layer width } + // if (this.bgLayer2.x < -400) { + // this.bgLayer2.x = 1200; + // } + // if (this.bgLayer3.x < -400) { + // this.bgLayer3.x = 1200; + // } + } + + 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() { @@ -76,17 +157,17 @@ export class GameScene extends Phaser.Scene { canvas.width = 32; canvas.height = 32; - // Player ship (triangle) - const ctx = canvas.getContext('2d'); - ctx.fillStyle = '#00ffcc'; - ctx.beginPath(); - ctx.moveTo(16, 5); - ctx.lineTo(5, 30); - ctx.lineTo(27, 30); - ctx.closePath(); - ctx.fill(); + // // Player ship (triangle) + // const ctx = canvas.getContext('2d'); + // ctx.fillStyle = '#00ffcc'; + // ctx.beginPath(); + // ctx.moveTo(16, 5); + // ctx.lineTo(5, 30); + // ctx.lineTo(27, 30); + // ctx.closePath(); + // ctx.fill(); - this.textures.addCanvas('player', canvas); + // this.textures.addCanvas('player', canvas); // Enemy (square) const enemyCanvas = document.createElement('canvas'); @@ -116,14 +197,14 @@ export class GameScene extends Phaser.Scene { createUI() { // Create score text this.scoreText = this.add.text(16, 16, 'Score: 0', { - fontSize: '32px', + fontSize: '28px', fill: '#ffffff', fontFamily: 'Arial' }); // Create game title - this.titleText = this.add.text(400, 50, 'Zenith Vector', { - fontSize: '48px', + this.titleText = this.add.text(300, 50, 'Zenith Vector', { + fontSize: '40px', fill: '#00ffcc', fontFamily: 'Arial', align: 'center' @@ -131,13 +212,32 @@ export class GameScene extends Phaser.Scene { this.titleText.setOrigin(0.5); } - shoot() { - const bullet = this.bullets.create(this.player.x, this.player.y - 20, 'bullet'); - bullet.setVelocityY(-300); + 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, 780); const enemy = this.enemies.create(x, 0, 'enemy'); @@ -152,6 +252,7 @@ export class GameScene extends Phaser.Scene { hitEnemy(bullet, enemy) { bullet.destroy(); enemy.destroy(); + this.sound.play('enemy-kill'); this.score += 10; this.scoreText.setText('Score: ' + this.score); @@ -164,8 +265,8 @@ export class GameScene extends Phaser.Scene { this.gameOver = true; // Show game over text - const gameOverText = this.add.text(400, 300, 'GAME OVER', { - fontSize: '64px', + const gameOverText = this.add.text(300, 400, 'GAME OVER', { + fontSize: '54px', fill: '#ff3366', fontFamily: 'Arial' });