From 26f79dd7241016fbc260cb5ee4a500c164030496 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 1 Sep 2025 15:02:18 +0000 Subject: [PATCH] menu --- game.js | 511 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 311 insertions(+), 200 deletions(-) diff --git a/game.js b/game.js index 757cd08..393e4ed 100644 --- a/game.js +++ b/game.js @@ -9,249 +9,360 @@ const config = { debug: false } }, - scene: { - preload: preload, - create: create, - update: update - } + scene: [] // Scenes will be added dynamically }; const game = new Phaser.Game(config); -let player; -let ground; -let obstacles; -let score = 0; -let scoreText; -let highScore = 0; -let highScoreText; +// --- Base Scene for common functionality --- +class BaseScene extends Phaser.Scene { + constructor(key) { + super(key); + } -let bg1; -let bg2; -let bg3; + createBackButton() { + const backButton = this.add.text(this.cameras.main.width - 20, 20, 'Back to Menu', { + fontSize: '24px', + fill: '#fff', + backgroundColor: '#333', + padding: { x: 10, y: 5 } + }).setOrigin(1, 0); -let jumpParticles; -let landParticles; -let wasOnGround = false; - -let gameState = 'playing'; // Can be 'playing' or 'gameOver' -let gameOverText; -let restartText; - -let obstacleTimer; -let scoreTimer; - - -function preload () { + backButton.setInteractive(); + backButton.on('pointerdown', () => { + this.scene.start('MainMenuScene'); + }); + backButton.on('pointerover', () => backButton.setStyle({ fill: '#ff0' })); + backButton.on('pointerout', () => backButton.setStyle({ fill: '#fff' })); + } } -function create () { - gameState = 'playing'; - // --- Get High Score --- - highScore = localStorage.getItem('highScore') || 0; - // --- Create Starfield --- - const starfieldGraphics = this.add.graphics(); - starfieldGraphics.fillStyle(0xffffff); - for (let i = 0; i < 100; i++) { - starfieldGraphics.fillPoint( - Phaser.Math.Between(0, 800), - Phaser.Math.Between(0, 600), - Phaser.Math.Between(1, 2) - ); +// --- Main Menu Scene --- +class MainMenuScene extends Phaser.Scene { + constructor() { + super('MainMenuScene'); } - starfieldGraphics.generateTexture('starfield', 800, 600); - starfieldGraphics.destroy(); - bg1 = this.add.tileSprite(400, 300, 800, 600, 'starfield').setAlpha(0.3); - bg2 = this.add.tileSprite(400, 300, 800, 600, 'starfield').setAlpha(0.6); - bg3 = this.add.tileSprite(400, 300, 800, 600, 'starfield'); + create() { + this.add.text(400, 100, 'Cosmic Runner', { fontSize: '64px', fill: '#fff' }).setOrigin(0.5); + const startButton = this.add.text(400, 250, 'Start Game', { fontSize: '32px', fill: '#0f0', backgroundColor: '#333', padding: { x: 10, y: 5 } }).setOrigin(0.5); + const rulesButton = this.add.text(400, 320, 'Rules & Bonuses', { fontSize: '32px', fill: '#ff0', backgroundColor: '#333', padding: { x: 10, y: 5 } }).setOrigin(0.5); + const scoresButton = this.add.text(400, 390, 'High Scores', { fontSize: '32px', fill: '#f0f', backgroundColor: '#333', padding: { x: 10, y: 5 } }).setOrigin(0.5); - // Create the ground - ground = this.add.rectangle(400, 580, 800, 40, 0x333333).setOrigin(0.5); - this.physics.add.existing(ground, true); + // Interactivity + [startButton, rulesButton, scoresButton].forEach(button => { + button.setInteractive(); + button.on('pointerover', () => button.setAlpha(0.8)); + button.on('pointerout', () => button.setAlpha(1)); + }); - // Create the player - player = this.add.container(100, 450); - const body = this.add.rectangle(0, 0, 40, 40, 0xff8800); - const cockpit = this.add.rectangle(10, -5, 20, 20, 0x88ccff); - const wing = this.add.triangle(0, 0, -20, -15, -20, 15, 0, 0, 0xffffff); - player.add([body, cockpit, wing]); - this.physics.add.existing(player); + startButton.on('pointerdown', () => this.scene.start('GameScene')); + rulesButton.on('pointerdown', () => this.scene.start('RulesScene')); + scoresButton.on('pointerdown', () => this.scene.start('HighScoresScene')); + } +} - player.body.setBounce(0); - player.body.setCollideWorldBounds(true); - player.body.setSize(40, 40); +// --- Rules Scene --- +class RulesScene extends BaseScene { + constructor() { + super('RulesScene'); + } - // --- Particle Effects --- - const jumpParticleGraphics = this.add.graphics(); - jumpParticleGraphics.fillStyle(0xffffff, 1); - jumpParticleGraphics.fillGradientStyle(0xffff00, 0xffff00, 0xffffff, 0xffffff, 1); - jumpParticleGraphics.fillRect(0, 0, 16, 16); - jumpParticleGraphics.generateTexture('jump_particle', 16, 16); - jumpParticleGraphics.destroy(); + create() { + this.add.text(400, 50, 'Rules & Bonuses', { fontSize: '48px', fill: '#fff' }).setOrigin(0.5); - const landParticleGraphics = this.add.graphics(); - landParticleGraphics.fillStyle(0xffffff, 0.5); - landParticleGraphics.fillCircle(8, 8, 8); - landParticleGraphics.generateTexture('land_particle', 16, 16); - landParticleGraphics.destroy(); + const rulesText = [ + '1. Click or press SPACE to jump.', + '2. Avoid the green and blue obstacles.', + '3. Collect yellow coins for extra points (+100).', + '', + 'Bonuses:', + '- Blue Shield: Grants 10 seconds of invincibility.', + ' (You will destroy obstacles on contact!)' + ]; - jumpParticles = this.add.particles('jump_particle'); - jumpParticles.createEmitter({ - speed: { min: -100, max: 100 }, - angle: { min: 250, max: 290 }, - scale: { start: 0.8, end: 0 }, - blendMode: 'ADD', - lifespan: 400, - tint: { start: 0xffff00, end: 0xffffff }, - quantity: 15, - on: false - }); + this.add.text(400, 300, rulesText, { fontSize: '24px', fill: '#fff', align: 'center' }).setOrigin(0.5); - landParticles = this.add.particles('land_particle'); - landParticles.createEmitter({ - speed: { min: 50, max: 100 }, - angle: { min: 260, max: 280 }, - scale: { start: 1, end: 0 }, - alpha: { start: 0.6, end: 0 }, - lifespan: 500, - quantity: 20, - on: false - }); + this.createBackButton(); + } +} - this.physics.add.collider(player, ground); +// --- High Scores Scene --- +class HighScoresScene extends BaseScene { + constructor() { + super('HighScoresScene'); + } - // --- ACTION HANDLER --- - const handleAction = () => { - if (gameState === 'playing' && player.body.touching.down) { - player.body.setVelocityY(-500); - jumpParticles.emitParticleAt(player.x, player.y + 25); - } else if (gameState === 'gameOver') { + create() { + this.add.text(400, 50, 'High Scores', { fontSize: '48px', fill: '#fff' }).setOrigin(0.5); + + const highScore = localStorage.getItem('highScore') || 0; + this.add.text(400, 200, `Best Score: ${highScore}`, { fontSize: '38px', fill: '#fff' }).setOrigin(0.5); + + const clearButton = this.add.text(400, 400, 'Clear Scores', { fontSize: '24px', fill: '#f00', backgroundColor: '#333', padding: { x: 10, y: 5 } }).setOrigin(0.5); + clearButton.setInteractive(); + clearButton.on('pointerdown', () => { + localStorage.setItem('highScore', '0'); this.scene.restart(); + }); + clearButton.on('pointerover', () => clearButton.setAlpha(0.8)); + clearButton.on('pointerout', () => clearButton.setAlpha(1)); + + + this.createBackButton(); + } +} + + +// --- Game Scene --- +class GameScene extends Phaser.Scene { + constructor() { + super('GameScene'); + this.player = null; + this.ground = null; + this.obstacles = null; + this.coins = null; + this.shields = null; + this.score = 0; + this.scoreText = null; + this.highScore = 0; + this.highScoreText = null; + this.bg1 = null; + this.bg2 = null; + this.bg3 = null; + this.jumpParticles = null; + this.landParticles = null; + this.wasOnGround = false; + this.gameState = 'playing'; + this.gameOverText = null; + this.restartText = null; + this.obstacleTimer = null; + this.scoreTimer = null; + this.coinTimer = null; + this.shieldTimer = null; + } + + preload() { + // Preload assets if any - currently all generated dynamically + } + + create() { + this.gameState = 'playing'; + this.score = 0; + this.highScore = localStorage.getItem('highScore') || 0; + + // --- Create Starfield --- + const starfieldGraphics = this.add.graphics(); + starfieldGraphics.fillStyle(0xffffff); + for (let i = 0; i < 100; i++) { + starfieldGraphics.fillPoint( + Phaser.Math.Between(0, 800), + Phaser.Math.Between(0, 600), + Phaser.Math.Between(1, 2) + ); } - }; + starfieldGraphics.generateTexture('starfield', 800, 600); + starfieldGraphics.destroy(); - // Input for jumping and restarting - this.input.on('pointerdown', handleAction); - this.input.keyboard.on('keydown-SPACE', handleAction); + this.bg1 = this.add.tileSprite(400, 300, 800, 600, 'starfield').setAlpha(0.3); + this.bg2 = this.add.tileSprite(400, 300, 800, 600, 'starfield').setAlpha(0.6); + this.bg3 = this.add.tileSprite(400, 300, 800, 600, 'starfield'); - obstacles = this.physics.add.group(); - this.physics.add.collider(player, obstacles, hitObstacle, null, this); + this.ground = this.add.rectangle(400, 580, 800, 40, 0x333333).setOrigin(0.5); + this.physics.add.existing(this.ground, true); - // Timer to add new obstacles - obstacleTimer = this.time.addEvent({ - delay: 1500, - callback: addObstacle, - callbackScope: this, - loop: true - }); + this.player = this.add.container(100, 450); + const body = this.add.rectangle(0, 0, 40, 40, 0xff8800); + const cockpit = this.add.rectangle(10, -5, 20, 20, 0x88ccff); + const wing = this.add.triangle(0, 0, -20, -15, -20, 15, 0, 0, 0xffffff); + this.player.add([body, cockpit, wing]); + this.physics.add.existing(this.player); - score = 0; - scoreText = this.add.text(16, 16, 'Score: 0', { fontSize: '32px', fill: '#fff' }); - highScoreText = this.add.text(16, 50, `Best: ${highScore}`, { fontSize: '24px', fill: '#fff', alpha: 0.7 }); + this.player.body.setBounce(0); + this.player.body.setCollideWorldBounds(true); + this.player.body.setSize(40, 40); + this.player.isInvincible = false; + const jumpParticleGraphics = this.add.graphics(); + jumpParticleGraphics.fillStyle(0xffffff, 1); + jumpParticleGraphics.fillGradientStyle(0xffff00, 0xffff00, 0xffffff, 0xffffff, 1); + jumpParticleGraphics.fillRect(0, 0, 16, 16); + jumpParticleGraphics.generateTexture('jump_particle', 16, 16); + jumpParticleGraphics.destroy(); - // Timer to increment score - scoreTimer = this.time.addEvent({ - delay: 100, - callback: () => { - if (gameState === 'playing') { - score++; - scoreText.setText('Score: ' + score); + const landParticleGraphics = this.add.graphics(); + landParticleGraphics.fillStyle(0xffffff, 0.5); + landParticleGraphics.fillCircle(8, 8, 8); + landParticleGraphics.generateTexture('land_particle', 16, 16); + landParticleGraphics.destroy(); + + this.jumpParticles = this.add.particles('jump_particle'); + this.jumpParticles.createEmitter({ + speed: { min: -100, max: 100 }, + angle: { min: 250, max: 290 }, + scale: { start: 0.8, end: 0 }, + blendMode: 'ADD', + lifespan: 400, + tint: { start: 0xffff00, end: 0xffffff }, + quantity: 15, + on: false + }); + + this.landParticles = this.add.particles('land_particle'); + this.landParticles.createEmitter({ + speed: { min: 50, max: 100 }, + angle: { min: 260, max: 280 }, + scale: { start: 1, end: 0 }, + alpha: { start: 0.6, end: 0 }, + lifespan: 500, + quantity: 20, + on: false + }); + + this.physics.add.collider(this.player, this.ground); + + const handleAction = () => { + if (this.gameState === 'playing' && this.player.body.touching.down) { + this.player.body.setVelocityY(-500); + this.jumpParticles.emitParticleAt(this.player.x, this.player.y + 25); + } else if (this.gameState === 'gameOver') { + this.scene.start('MainMenuScene'); // Go back to menu } - }, - callbackScope: this, - loop: true - }); + }; - // Game Over text (initially invisible) - gameOverText = this.add.text(400, 250, 'Game Over', { fontSize: '64px', fill: '#ff0000', align: 'center' }).setOrigin(0.5); - restartText = this.add.text(400, 380, 'Click or Press Space to Restart', { fontSize: '32px', fill: '#fff' }).setOrigin(0.5); - gameOverText.setVisible(false); - restartText.setVisible(false); -} + this.input.on('pointerdown', handleAction); + this.input.keyboard.on('keydown-SPACE', handleAction); -function update () { - // Always scroll the starfield, even on game over screen - bg1.tilePositionX += 0.2; - bg2.tilePositionX += 0.4; - bg3.tilePositionX += 0.8; + this.obstacles = this.physics.add.group(); + this.physics.add.collider(this.player, this.obstacles, this.hitObstacle, null, this); - // Stop all game logic updates if the game is over - if (gameState !== 'playing') { - return; + this.coins = this.physics.add.group({ allowGravity: false }); + this.physics.add.overlap(this.player, this.coins, this.collectCoin, null, this); + + this.shields = this.physics.add.group({ allowGravity: false }); + this.physics.add.overlap(this.player, this.shields, this.collectShield, null, this); + + this.obstacleTimer = this.time.addEvent({ delay: 1500, callback: this.addObstacle, callbackScope: this, loop: true }); + this.coinTimer = this.time.addEvent({ delay: 2000, callback: this.addCoin, callbackScope: this, loop: true }); + this.shieldTimer = this.time.addEvent({ delay: 10000, callback: this.addShield, callbackScope: this, loop: true }); + + this.scoreText = this.add.text(16, 16, 'Score: 0', { fontSize: '32px', fill: '#fff' }); + this.highScoreText = this.add.text(16, 50, `Best: ${this.highScore}`, { fontSize: '24px', fill: '#fff', alpha: 0.7 }); + + this.scoreTimer = this.time.addEvent({ + delay: 100, + callback: () => { + if (this.gameState === 'playing') { + this.score++; + this.scoreText.setText('Score: ' + this.score); + } + }, + callbackScope: this, + loop: true + }); + + this.gameOverText = this.add.text(400, 250, 'Game Over', { fontSize: '64px', fill: '#ff0000', align: 'center' }).setOrigin(0.5).setVisible(false); + this.restartText = this.add.text(400, 380, 'Click or Press Space for Menu', { fontSize: '32px', fill: '#fff' }).setOrigin(0.5).setVisible(false); } - const onGround = player.body.touching.down; - if (onGround && !wasOnGround) { - landParticles.emitParticleAt(player.x, player.y + 20); - } - wasOnGround = onGround; + update() { + this.bg1.tilePositionX += 0.2; + this.bg2.tilePositionX += 0.4; + this.bg3.tilePositionX += 0.8; - // Automatically destroy obstacles that go off-screen - obstacles.getChildren().forEach(obstacle => { - if (obstacle.x < -50) { + if (this.gameState !== 'playing') { + return; + } + + const onGround = this.player.body.touching.down; + if (onGround && !this.wasOnGround) { + this.landParticles.emitParticleAt(this.player.x, this.player.y + 20); + } + this.wasOnGround = onGround; + + ['obstacles', 'coins', 'shields'].forEach(groupName => { + this[groupName].getChildren().forEach(child => { + if (child.x < -50) { + child.destroy(); + } + }); + }); + } + + addObstacle() { + if (this.gameState !== 'playing') return; + const isFlying = Phaser.Math.Between(0, 2) === 0; + const obstacleHeight = Phaser.Math.Between(50, 120); + let obstacleY = isFlying ? Phaser.Math.Between(400, 480) : 600 - obstacleHeight; + const obstacle = this.add.rectangle(850, obstacleY, 50, obstacleHeight, isFlying ? 0x00aaff : 0x00ff00).setOrigin(0.5, 0); + this.physics.add.existing(obstacle); + this.obstacles.add(obstacle); + obstacle.body.setVelocityX(-200).setImmovable(true).setAllowGravity(false); + } + + addCoin() { + if (this.gameState !== 'playing') return; + const coin = this.add.circle(850, Phaser.Math.Between(300, 500), 15, 0xffff00); + this.physics.add.existing(coin); + this.coins.add(coin); + coin.body.setVelocityX(-200).setAllowGravity(false); + } + + addShield() { + if (this.gameState !== 'playing') return; + const shield = this.add.rectangle(850, Phaser.Math.Between(300, 500), 30, 30, 0x0000ff).setOrigin(0.5); + this.physics.add.existing(shield); + this.shields.add(shield); + shield.body.setVelocityX(-200).setAllowGravity(false); + } + + collectCoin(player, coin) { + coin.destroy(); + this.score += 100; + this.scoreText.setText('Score: ' + this.score); + } + + collectShield(player, shield) { + shield.destroy(); + player.isInvincible = true; + player.setAlpha(0.5); + this.time.delayedCall(10000, () => { + player.isInvincible = false; + player.setAlpha(1); + }, [], this); + } + + hitObstacle(player, obstacle) { + if (player.isInvincible) { obstacle.destroy(); + return; } - }); -} + if (this.gameState === 'gameOver') return; -function addObstacle() { - if (gameState !== 'playing') return; + this.gameState = 'gameOver'; + this.physics.pause(); + [this.obstacleTimer, this.scoreTimer, this.coinTimer, this.shieldTimer].forEach(timer => timer.remove()); - const isFlying = Phaser.Math.Between(0, 2) === 0; // 1 in 3 chance - const obstacleHeight = Phaser.Math.Between(50, 120); - let obstacleY; - let obstacleColor = 0x00ff00; // Default green + player.list.forEach(child => child.setTint && child.setTint(0xff0000)); - if (isFlying) { - // Flying obstacle - obstacleY = Phaser.Math.Between(400, 480); // Adjusted Y to avoid being too high - obstacleColor = 0x00aaff; // Light blue for flying - } else { - // Ground obstacle - obstacleY = 600 - obstacleHeight; - } - - const obstacle = this.add.rectangle(850, obstacleY, 50, obstacleHeight, obstacleColor).setOrigin(0.5, 0); - - this.physics.add.existing(obstacle); - obstacles.add(obstacle); - - obstacle.body.setVelocityX(-200); - obstacle.body.setImmovable(true); - obstacle.body.setAllowGravity(false); -} - -function hitObstacle(player, obstacle) { - if (gameState === 'gameOver') return; // Don't run this logic twice - - gameState = 'gameOver'; - this.physics.pause(); // Stop all physics - - // Stop timers - obstacleTimer.remove(); - scoreTimer.remove(); - - // Make player red to show they've been hit - player.list.forEach(child => { - if (child.setTint) { - child.setTint(0xff0000); + if (this.score > this.highScore) { + this.highScore = this.score; + localStorage.setItem('highScore', this.highScore); } - }); - // Check for and set high score - if (score > highScore) { - highScore = score; - localStorage.setItem('highScore', highScore); + this.gameOverText.setText(`Game Over\nScore: ${this.score}\nBest: ${this.highScore}`).setVisible(true); + this.restartText.setVisible(true); + this.highScoreText.setVisible(false); } - - // Show "Game Over" text - gameOverText.setText(`Game Over\nScore: ${score}\nBest: ${highScore}`); - gameOverText.setVisible(true); - restartText.setVisible(true); - highScoreText.setVisible(false); // Hide score during game over } +// Add all scenes to the game +game.scene.add('MainMenuScene', MainMenuScene); +game.scene.add('GameScene', GameScene); +game.scene.add('RulesScene', RulesScene); +game.scene.add('HighScoresScene', HighScoresScene); + +// Start with the Main Menu +game.scene.start('MainMenuScene');