// Sam's Phaser Side-Scroller (Expanded) const WORLD_WIDTH = 4200; const WORLD_HEIGHT = 540; const PLAYER_SPEED = 260; const RUN_SPEED = 340; const JUMP_VELOCITY = -700; const config = { type: Phaser.AUTO, parent: "game-container", width: 960, height: 540, backgroundColor: "#87ceeb", physics: { default: "arcade", arcade: { gravity: { y: 1600 }, debug: false } }, scene: { preload, create, update } }; const game = new Phaser.Game(config); let player; let cursors; let keys; let platforms; let movingPlatforms; let coins; let hazards; let checkpoints; let goalZone; let score = 0; let lives = 3; let scoreText; let livesText; let progressText; let messageText; let respawnX = 120; let respawnY = 420; let isGameOver = false; let isWin = false; let invulnerableUntil = 0; function preload() { // No external assets required. } function create() { resetRunState(); this.physics.world.setBounds(0, 0, WORLD_WIDTH, WORLD_HEIGHT); this.cameras.main.setBounds(0, 0, WORLD_WIDTH, WORLD_HEIGHT); createBackground(this); createLevel(this); createPlayer(this); createCollectibles(this); createHazards(this); createCheckpoints(this); createGoal(this); createUI(this); setupCollisions(this); setupInput(this); this.cameras.main.startFollow(player, true, 0.08, 0.08); this.cameras.main.setDeadzone(220, 140); messageText.setText("Reach the green flag • Collect coins • Avoid red hazards"); this.time.delayedCall(2200, () => { if (!isGameOver && !isWin) { messageText.setText(""); } }); } function update() { if ((isGameOver || isWin) && Phaser.Input.Keyboard.JustDown(keys.R)) { this.scene.restart(); return; } if (!player || !player.body) return; updateMovingPlatforms(); updateHUD(); if (isGameOver || isWin) return; const moveLeft = cursors.left.isDown || keys.A.isDown; const moveRight = cursors.right.isDown || keys.D.isDown; const speed = keys.SHIFT.isDown ? RUN_SPEED : PLAYER_SPEED; if (moveLeft && !moveRight) { player.body.setVelocityX(-speed); } else if (moveRight && !moveLeft) { player.body.setVelocityX(speed); } else { player.body.setVelocityX(0); } const jumpPressed = Phaser.Input.Keyboard.JustDown(cursors.up) || Phaser.Input.Keyboard.JustDown(cursors.space) || Phaser.Input.Keyboard.JustDown(keys.W); const onGround = player.body.blocked.down || player.body.touching.down; if (jumpPressed && onGround) { player.body.setVelocityY(JUMP_VELOCITY); } if (this.time.now < invulnerableUntil) { player.setAlpha(Math.floor(this.time.now / 70) % 2 ? 0.35 : 1); } else { player.setAlpha(1); } } function resetRunState() { score = 0; lives = 3; respawnX = 120; respawnY = 420; isGameOver = false; isWin = false; invulnerableUntil = 0; } function createBackground(scene) { const sky = scene.add.rectangle( WORLD_WIDTH / 2, WORLD_HEIGHT / 2, WORLD_WIDTH, WORLD_HEIGHT, 0x87ceeb ); sky.setDepth(-50); const sun = scene.add.circle(220, 100, 52, 0xffec99); sun.setScrollFactor(0.08); sun.setDepth(-49); for (let i = 0; i < 16; i++) { const hill = scene.add.ellipse(i * 300 + 120, 470, 420, 220, 0x6c8cab); hill.setScrollFactor(0.35); hill.setDepth(-45); } for (let i = 0; i < 18; i++) { const hill = scene.add.ellipse(i * 250 + 80, 500, 320, 160, 0x5b7894); hill.setScrollFactor(0.55); hill.setDepth(-44); } for (let i = 0; i < 28; i++) { const cloudX = Phaser.Math.Between(80, WORLD_WIDTH - 80); const cloudY = Phaser.Math.Between(60, 210); const cloud = scene.add.ellipse(cloudX, cloudY, 90, 42, 0xffffff, 0.85); cloud.setScrollFactor(0.18); cloud.setDepth(-40); } scene.add.text(42, 438, "START", { fontFamily: "Arial Black", fontSize: "28px", color: "#1f2937" }).setDepth(9); scene.add.text(WORLD_WIDTH - 250, 138, "FINISH", { fontFamily: "Arial Black", fontSize: "28px", color: "#14532d" }).setDepth(9); } function createLevel(scene) { platforms = scene.physics.add.staticGroup(); movingPlatforms = scene.physics.add.group({ allowGravity: false, immovable: true }); // Ground for (let x = 200; x <= WORLD_WIDTH - 200; x += 400) { addPlatform(scene, x, WORLD_HEIGHT - 20, 400, 40, 0x4e5d6c); } // Static floating platforms const staticPlatforms = [ [420, 430, 180, 24], [700, 360, 180, 24], [980, 300, 180, 24], [1320, 260, 180, 24], [1700, 330, 200, 24], [2050, 280, 180, 24], [2360, 240, 180, 24], [2670, 280, 180, 24], [2980, 340, 200, 24], [3320, 290, 180, 24], [3660, 240, 180, 24] ]; staticPlatforms.forEach(([x, y, w, h]) => { addPlatform(scene, x, y, w, h, 0x6b7280); }); // Moving platforms addMovingPlatform(scene, 1520, 390, 140, 20, "x", 160, 95, 0x7d8597); addMovingPlatform(scene, 2510, 320, 140, 20, "y", 110, 75, 0x7d8597); addMovingPlatform(scene, 3490, 350, 130, 20, "x", 130, 85, 0x7d8597); } function createPlayer(scene) { player = scene.add.rectangle(respawnX, respawnY, 34, 50, 0x4aa3ff); player.setDepth(10); scene.physics.add.existing(player); player.body.setCollideWorldBounds(true); player.body.setMaxVelocity(500, 1200); } function createCollectibles(scene) { coins = scene.physics.add.group({ allowGravity: false, immovable: true }); const coinPositions = [ [420, 390], [700, 320], [980, 260], [1320, 220], [1700, 290], [2050, 240], [2360, 200], [2670, 240], [2980, 300], [3320, 250], [3660, 200], [1540, 350], [2510, 270], [3490, 310], [1150, 455], [2250, 455], [3150, 455] ]; coinPositions.forEach(([x, y]) => { addCoin(scene, x, y); }); } function createHazards(scene) { hazards = scene.physics.add.staticGroup(); const hazardBlocks = [ [640, 490, 80, 20], [1160, 490, 100, 20], [1880, 490, 120, 20], [2250, 490, 90, 20], [2860, 490, 120, 20], [3480, 490, 100, 20], [1320, 248, 52, 12], [2360, 228, 52, 12] ]; hazardBlocks.forEach(([x, y, w, h]) => { addHazard(scene, x, y, w, h); }); } function createCheckpoints(scene) { checkpoints = scene.physics.add.staticGroup(); addCheckpoint(scene, 1450, 460); addCheckpoint(scene, 3050, 460); } function createGoal(scene) { const pole = scene.add.rectangle(WORLD_WIDTH - 150, 355, 12, 290, 0x2f855a); pole.setDepth(8); const flag = scene.add.rectangle(WORLD_WIDTH - 115, 250, 72, 40, 0x00e676); flag.setDepth(8); goalZone = scene.add.rectangle(WORLD_WIDTH - 120, 355, 85, 290, 0x00e676, 0.2); goalZone.setDepth(7); scene.physics.add.existing(goalZone, true); } function createUI(scene) { const commonStyle = { fontFamily: "Arial", fontSize: "20px", color: "#ffffff", stroke: "#000000", strokeThickness: 4 }; scoreText = scene.add.text(16, 12, "Score: 0", commonStyle); livesText = scene.add.text(16, 40, "Lives: 3", commonStyle); progressText = scene.add.text(944, 12, "Progress: 0%", { ...commonStyle, fontSize: "18px" }).setOrigin(1, 0); scene.add.text(16, 510, "Move: Arrows or A/D • Jump: Up/W/Space • Run: Shift • Restart: R", { fontFamily: "Arial", fontSize: "15px", color: "#ffffff", stroke: "#000000", strokeThickness: 3 }); messageText = scene.add.text(480, 76, "", { fontFamily: "Arial Black", fontSize: "24px", color: "#ffffff", align: "center", stroke: "#000000", strokeThickness: 5 }).setOrigin(0.5, 0); scoreText.setScrollFactor(0).setDepth(100); livesText.setScrollFactor(0).setDepth(100); progressText.setScrollFactor(0).setDepth(100); messageText.setScrollFactor(0).setDepth(100); } function setupCollisions(scene) { scene.physics.add.collider(player, platforms); scene.physics.add.collider(player, movingPlatforms); scene.physics.add.collider(player, hazards, () => loseLife(scene), null, scene); scene.physics.add.overlap(player, coins, collectCoin, null, scene); scene.physics.add.overlap(player, checkpoints, activateCheckpoint, null, scene); scene.physics.add.overlap(player, goalZone, reachGoal, null, scene); } function setupInput(scene) { cursors = scene.input.keyboard.createCursorKeys(); keys = scene.input.keyboard.addKeys("W,A,D,SHIFT,R"); } function updateMovingPlatforms() { movingPlatforms.children.iterate((platform) => { if (!platform || !platform.body) return; if (platform.axis === "x") { if (platform.x >= platform.start + platform.range) { platform.body.setVelocityX(-platform.speed); } else if (platform.x <= platform.start - platform.range) { platform.body.setVelocityX(platform.speed); } } else if (platform.axis === "y") { if (platform.y >= platform.start + platform.range) { platform.body.setVelocityY(-platform.speed); } else if (platform.y <= platform.start - platform.range) { platform.body.setVelocityY(platform.speed); } } }); } function updateHUD() { const progress = Phaser.Math.Clamp((player.x / (WORLD_WIDTH - 160)) * 100, 0, 100); progressText.setText(`Progress: ${Math.round(progress)}%`); } function collectCoin(playerObj, coin) { if (!coin.active || isGameOver || isWin) return; coin.destroy(); score += 10; scoreText.setText(`Score: ${score}`); if (coins.countActive(true) === 0) { score += 100; scoreText.setText(`Score: ${score}`); messageText.setText("All coins collected! +100 bonus"); playerObj.scene.time.delayedCall(1200, () => { if (!isGameOver && !isWin) { messageText.setText(""); } }); } } function activateCheckpoint(playerObj, checkpoint) { if (checkpoint.activated || isGameOver || isWin) return; checkpoint.activated = true; checkpoint.setFillStyle(0x2ecc71); respawnX = checkpoint.x; respawnY = checkpoint.y - 90; messageText.setText("Checkpoint reached!"); playerObj.scene.time.delayedCall(900, () => { if (!isGameOver && !isWin) { messageText.setText(""); } }); } function loseLife(scene) { if (isGameOver || isWin) return; if (scene.time.now < invulnerableUntil) return; lives -= 1; livesText.setText(`Lives: ${lives}`); scene.cameras.main.shake(160, 0.006); if (lives <= 0) { isGameOver = true; scene.physics.world.pause(); player.setFillStyle(0xff6464); player.setAlpha(1); messageText.setText("Game Over\nPress R to Restart"); return; } invulnerableUntil = scene.time.now + 1400; player.body.stop(); player.setPosition(respawnX, respawnY); messageText.setText("Ouch! Respawning..."); scene.time.delayedCall(900, () => { if (!isGameOver && !isWin) { messageText.setText(""); } }); } function reachGoal(playerObj) { if (isGameOver || isWin) return; const scene = playerObj.scene; isWin = true; score += 250; scoreText.setText(`Score: ${score}`); scene.physics.world.pause(); player.setFillStyle(0x7cfc00); player.setAlpha(1); messageText.setText("You Win! 🎉\nPress R to Play Again"); } function addPlatform(scene, x, y, w, h, color = 0x6b7280) { const platform = scene.add.rectangle(x, y, w, h, color); platform.setDepth(6); scene.physics.add.existing(platform, true); platforms.add(platform); return platform; } function addMovingPlatform(scene, x, y, w, h, axis, range, speed, color = 0x7d8597) { const platform = scene.add.rectangle(x, y, w, h, color); platform.setDepth(6); scene.physics.add.existing(platform); platform.body.setAllowGravity(false); platform.body.setImmovable(true); platform.axis = axis; platform.range = range; platform.speed = speed; platform.start = axis === "x" ? x : y; if (axis === "x") { platform.body.setVelocityX(speed); } else { platform.body.setVelocityY(speed); } movingPlatforms.add(platform); return platform; } function addCoin(scene, x, y) { const coin = scene.add.circle(x, y, 10, 0xffd166); coin.setDepth(11); scene.physics.add.existing(coin); coin.body.setAllowGravity(false); coin.body.setImmovable(true); coins.add(coin); return coin; } function addHazard(scene, x, y, w, h) { const hazard = scene.add.rectangle(x, y, w, h, 0xe63946); hazard.setDepth(7); scene.physics.add.existing(hazard, true); hazards.add(hazard); return hazard; } function addCheckpoint(scene, x, y) { const checkpoint = scene.add.rectangle(x, y, 18, 80, 0x00bcd4); checkpoint.setDepth(8); checkpoint.activated = false; scene.physics.add.existing(checkpoint, true); checkpoints.add(checkpoint); return checkpoint; }