Category: Uncategorized

  • 04/09/26

    main.js

    // 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;
    }

    Index.html

    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="utf-8" />
      <title>Sam's Phaser Side-Scroller</title>
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <link rel="stylesheet" href="./style.css" />
    </head>
    <body>
      <main class="page">
        <h1>Sam's Phaser Side-Scroller</h1>
        <p class="subtitle">
          Move: Arrow Keys or A/D • Jump: Up/W/Space • Run: Shift • Restart: R
        </p>
        <div id="game-container"></div>
      </main>
    
      <script src="https://cdn.jsdelivr.net/npm/phaser@3.80.1/dist/phaser.min.js"></script>
      <script src="./main.js"></script>
    </body>
    </html>
    
    

    style.css

    :root {
      --bg-top: #0b1020;
      --bg-bottom: #111827;
      --panel: #0f172a;
      --border: #334155;
      --text: #e5e7eb;
      --muted: #94a3b8;
    }
    
    * {
      box-sizing: border-box;
    }
    
    html,
    body {
      width: 100%;
      height: 100%;
      margin: 0;
    }
    
    body {
      display: flex;
      align-items: center;
      justify-content: center;
      background:
        radial-gradient(circle at 20% 15%, #1f2937 0%, transparent 45%),
        radial-gradient(circle at 80% 85%, #111827 0%, transparent 45%),
        linear-gradient(160deg, var(--bg-top), var(--bg-bottom));
      color: var(--text);
      font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
    }
    
    .page {
      width: min(1100px, 100%);
      padding: 16px;
      text-align: center;
    }
    
    .page h1 {
      margin: 0 0 6px;
      font-size: clamp(1.15rem, 2.4vw, 1.8rem);
      letter-spacing: 0.3px;
    }
    
    .subtitle {
      margin: 0 0 12px;
      color: var(--muted);
      font-size: 0.95rem;
    }
    
    #game-container {
      display: inline-block;
      padding: 10px;
      border: 1px solid var(--border);
      border-radius: 14px;
      background: linear-gradient(180deg, #0a1224, #0a0f1b);
      box-shadow:
        0 20px 45px rgba(0, 0, 0, 0.45),
        inset 0 0 0 1px rgba(255, 255, 255, 0.03);
    }
    
    #game-container canvas {
      display: block;
      width: min(960px, 92vw);
      height: auto;
      border-radius: 10px;
      border: 2px solid #1f2937;
    }
  • HI SAM!

    I am so excited to do this game with you!

    Here is the code, we will work on this next time:


    Index.html

    <!doctype html>
    <html>
    <head>
      <meta charset="utf-8" />
      <title>Sam's Phaser Game</title>
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <style>
        body { margin: 0; background: #111; }
        canvas { display: block; margin: 0 auto; }
      </style>
    </head>
    <body>
      <script src="https://cdn.jsdelivr.net/npm/phaser@3.80.1/dist/phaser.min.js"></script>
      <script src="./main.js"></script>
    </body>
    </html>

    Paste this into main.js:

    // Sam's First Phaser Game (Week 1)
    
    const config = {
      type: Phaser.AUTO,
      width: 800,
      height: 450,
      backgroundColor: "#1b1b1b",
      physics: {
        default: "arcade",
        arcade: {
          gravity: { y: 0 },
          debug: false,
        },
      },
      scene: {
        preload,
        create,
        update,
      },
    };
    
    const game = new Phaser.Game(config);
    
    let player;
    let cursors;
    let walls;
    
    function preload() {
      // No assets yet — we’ll use simple shapes for instant success.
    }
    
    function create() {
      // World bounds (bigger than screen so camera follow feels cool)
      this.physics.world.setBounds(0, 0, 2000, 1200);
      this.cameras.main.setBounds(0, 0, 2000, 1200);
    
      // Player (a square using a built-in rectangle body)
      player = this.add.rectangle(200, 200, 40, 40, 0x4aa3ff);
      this.physics.add.existing(player);
    
      // Make the player collide and stop at world edges
      player.body.setCollideWorldBounds(true);
    
      // Simple wall group (static bodies)
      walls = this.physics.add.staticGroup();
    
      // Add a few walls (x, y, width, height)
      addWall(this, 400, 200, 300, 40);
      addWall(this, 700, 380, 40, 300);
      addWall(this, 300, 500, 500, 40);
      addWall(this, 1100, 300, 500, 40);
      addWall(this, 1400, 700, 40, 500);
    
      // Collisions: player vs walls
      this.physics.add.collider(player, walls);
    
      // Camera follows player
      this.cameras.main.startFollow(player, true, 0.08, 0.08);
    
      // Controls
      cursors = this.input.keyboard.createCursorKeys();
    
      // On-screen instructions (follows camera)
      const help = this.add.text(16, 16, "Arrow Keys to move\nGoal: explore the world!", {
        fontFamily: "Arial",
        fontSize: "18px",
        color: "#ffffff",
      });
      help.setScrollFactor(0);
    
      // Fun: click to teleport (instant kid joy)
      this.input.on("pointerdown", (pointer) => {
        player.body.stop();
        player.x = pointer.worldX;
        player.y = pointer.worldY;
      });
    }
    
    function update() {
      const speed = 260;
    
      // Reset velocity each frame
      player.body.setVelocity(0);
    
      if (cursors.left.isDown) player.body.setVelocityX(-speed);
      else if (cursors.right.isDown) player.body.setVelocityX(speed);
    
      if (cursors.up.isDown) player.body.setVelocityY(-speed);
      else if (cursors.down.isDown) player.body.setVelocityY(speed);
    
      // Normalize diagonal movement so it isn't faster
      player.body.velocity.normalize().scale(speed);
    }
    
    function addWall(scene, x, y, w, h) {
      const wall = scene.add.rectangle(x, y, w, h, 0x7a7a7a);
      scene.physics.add.existing(wall, true); // true = static
      walls.add(wall);
    }