const config = { type: Phaser.AUTO, width: 800, height: 600, physics: { default: 'arcade', arcade: { gravity: { y: 800 }, debug: false } }, scene: { preload: preload, create: create, update: update } }; const game = new Phaser.Game(config); let player; let ground; let obstacles; let score = 0; let scoreText; let highScore = 0; let highScoreText; let bg1; let bg2; let bg3; 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 () { } 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) ); } 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 the ground ground = this.add.rectangle(400, 580, 800, 40, 0x333333).setOrigin(0.5); this.physics.add.existing(ground, true); // 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); player.body.setBounce(0); player.body.setCollideWorldBounds(true); player.body.setSize(40, 40); // --- 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(); const landParticleGraphics = this.add.graphics(); landParticleGraphics.fillStyle(0xffffff, 0.5); landParticleGraphics.fillCircle(8, 8, 8); landParticleGraphics.generateTexture('land_particle', 16, 16); landParticleGraphics.destroy(); 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 }); 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.physics.add.collider(player, ground); // --- 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') { this.scene.restart(); } }; // Input for jumping and restarting this.input.on('pointerdown', handleAction); this.input.keyboard.on('keydown-SPACE', handleAction); obstacles = this.physics.add.group(); this.physics.add.collider(player, obstacles, hitObstacle, null, this); // Timer to add new obstacles obstacleTimer = this.time.addEvent({ delay: 1500, callback: addObstacle, callbackScope: this, loop: true }); 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 }); // Timer to increment score scoreTimer = this.time.addEvent({ delay: 100, callback: () => { if (gameState === 'playing') { score++; scoreText.setText('Score: ' + score); } }, 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); } function update () { // Always scroll the starfield, even on game over screen bg1.tilePositionX += 0.2; bg2.tilePositionX += 0.4; bg3.tilePositionX += 0.8; // Stop all game logic updates if the game is over if (gameState !== 'playing') { return; } const onGround = player.body.touching.down; if (onGround && !wasOnGround) { landParticles.emitParticleAt(player.x, player.y + 20); } wasOnGround = onGround; // Automatically destroy obstacles that go off-screen obstacles.getChildren().forEach(obstacle => { if (obstacle.x < -50) { obstacle.destroy(); } }); } function addObstacle() { if (gameState !== 'playing') return; 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 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); } }); // Check for and set high score if (score > highScore) { highScore = score; localStorage.setItem('highScore', highScore); } // 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 }