🎮ArcadeLab

Flappy Bird Clone

by CrystalEagle79
240 lines5.3 KB
▶ Play
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>Flappy Bird Clone</title>
  <style>
    html, body {
      margin: 0;
      padding: 0;
      background: #4ec0ca;
      overflow: hidden;
      font-family: sans-serif;
      touch-action: manipulation;
    }
    canvas {
      display: block;
      margin: 0 auto;
      background: #4ec0ca;
    }
    #ui {
      position: fixed;
      top: 10px;
      left: 50%;
      transform: translateX(-50%);
      color: #fff;
      text-shadow: 2px 2px 0 #000;
      font-size: 24px;
      pointer-events: none;
      text-align: center;
    }
  </style>
</head>
<body>
<div id="ui">Click or tap to flap<br>Score: <span id="score">0</span></div>
<canvas id="game"></canvas>
<script>
  const canvas = document.getElementById("game");
  const ctx = canvas.getContext("2d");
  const uiScore = document.getElementById("score");

  function resize() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
  }
  resize();
  window.addEventListener("resize", resize);

  const GRAVITY = 0.4;
  const FLAP = -7;
  const PIPE_GAP = 160;
  const PIPE_WIDTH = 70;
  const PIPE_SPEED = 3;

  let bird = {
    x: canvas.width * 0.3,
    y: canvas.height * 0.5,
    vy: 0,
    r: 18
  };

  let pipes = [];
  let score = 0;
  let gameOver = false;
  let started = false;

  function resetGame() {
    bird.x = canvas.width * 0.3;
    bird.y = canvas.height * 0.5;
    bird.vy = 0;
    pipes = [];
    score = 0;
    gameOver = false;
    started = false;
    uiScore.textContent = score;
  }

  function addPipe() {
    const center = Math.random() * (canvas.height * 0.6) + canvas.height * 0.2;
    pipes.push({
      x: canvas.width + PIPE_WIDTH,
      top: center - PIPE_GAP / 2,
      bottom: center + PIPE_GAP / 2,
      passed: false
    });
  }

  function flap() {
    if (!started) {
      started = true;
    }
    if (gameOver) {
      resetGame();
      return;
    }
    bird.vy = FLAP;
  }

  window.addEventListener("mousedown", flap);
  window.addEventListener("touchstart", function(e) {
    e.preventDefault();
    flap();
  }, { passive: false });

  let pipeTimer = 0;
  const PIPE_INTERVAL = 120; // frames

  function update() {
    if (!started) {
      draw();
      requestAnimationFrame(update);
      return;
    }

    if (!gameOver) {
      bird.vy += GRAVITY;
      bird.y += bird.vy;

      pipeTimer++;
      if (pipeTimer >= PIPE_INTERVAL) {
        pipeTimer = 0;
        addPipe();
      }

      for (let i = pipes.length - 1; i >= 0; i--) {
        const p = pipes[i];
        p.x -= PIPE_SPEED;

        // score
        if (!p.passed && p.x + PIPE_WIDTH < bird.x) {
          p.passed = true;
          score++;
          uiScore.textContent = score;
        }

        // remove off-screen
        if (p.x + PIPE_WIDTH < 0) {
          pipes.splice(i, 1);
        }
      }

      // collisions
      if (bird.y - bird.r < 0 || bird.y + bird.r > canvas.height) {
        gameOver = true;
      }

      for (const p of pipes) {
        if (
          bird.x + bird.r > p.x &&
          bird.x - bird.r < p.x + PIPE_WIDTH
        ) {
          if (bird.y - bird.r < p.top || bird.y + bird.r > p.bottom) {
            gameOver = true;
          }
        }
      }
    }

    draw();
    requestAnimationFrame(update);
  }

  function drawBird() {
    ctx.save();
    ctx.translate(bird.x, bird.y);
    ctx.fillStyle = "#ffeb3b";
    ctx.beginPath();
    ctx.arc(0, 0, bird.r, 0, Math.PI * 2);
    ctx.fill();

    // eye
    ctx.fillStyle = "#fff";
    ctx.beginPath();
    ctx.arc(bird.r * 0.3, -bird.r * 0.3, bird.r * 0.35, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = "#000";
    ctx.beginPath();
    ctx.arc(bird.r * 0.45, -bird.r * 0.3, bird.r * 0.15, 0, Math.PI * 2);
    ctx.fill();

    // beak
    ctx.fillStyle = "#ff9800";
    ctx.beginPath();
    ctx.moveTo(bird.r * 0.8, 0);
    ctx.lineTo(bird.r * 1.3, -bird.r * 0.2);
    ctx.lineTo(bird.r * 1.3, bird.r * 0.2);
    ctx.closePath();
    ctx.fill();

    ctx.restore();
  }

  function drawPipes() {
    ctx.fillStyle = "#5fb34a";
    for (const p of pipes) {
      // top pipe
      ctx.fillRect(p.x, 0, PIPE_WIDTH, p.top);
      // bottom pipe
      ctx.fillRect(p.x, p.bottom, PIPE_WIDTH, canvas.height - p.bottom);
    }
  }

  function drawGround() {
    ctx.fillStyle = "#ded895";
    ctx.fillRect(0, canvas.height - 40, canvas.width, 40);
  }

  function drawText() {
    ctx.fillStyle = "#fff";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.font = "32px sans-serif";

    if (!started && !gameOver) {
      ctx.fillText("Click or tap to start", canvas.width / 2, canvas.height / 2);
    } else if (gameOver) {
      ctx.fillText("Game Over", canvas.width / 2, canvas.height / 2 - 30);
      ctx.font = "24px sans-serif";
      ctx.fillText("Click or tap to restart", canvas.width / 2, canvas.height / 2 + 10);
    }
  }

  function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // sky
    ctx.fillStyle = "#4ec0ca";
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    drawPipes();
    drawGround();
    drawBird();
    drawText();
  }

  resetGame();
  update();
</script>
</body>
</html>

Game Source: Flappy Bird Clone

Creator: CrystalEagle79

Libraries: none

Complexity: complex (240 lines, 5.3 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: flappy-bird-clone-crystaleagle79" to link back to the original. Then publish at arcadelab.ai/publish.