Build Flappy Bird from Scratch with HTML, CSS, and JavaScript

CodeNPlay
CodeNPlay
Published on Oct, 08 2025 5 min read 0 comments
Text Image

Introduction

Flappy Bird, the notoriously simple yet addictive game, is a perfect project for aspiring web developers. It encapsulates core game development concepts like animation, collision detection, and user input in a clean, manageable package.

In this step-by-step tutorial, we will build our own version of Flappy Bird from the ground up using pure HTML, CSS, and vanilla JavaScript. By the end, you'll have a fully functional game and a solid understanding of the logic that brings such games to life.

Project Overview

We will break down the project into three main parts:

  • HTML Structure: The skeleton of our game, containing the game container and UI elements.
  • CSS Styling: Making the game visually appealing with backgrounds, bird animation, and pipe graphics.
  • JavaScript Logic: The brain of the game, handling physics, user controls, collision detection, and score tracking.

Let's start coding!

Step 1: Setting up the HTML Structure

The HTML is straightforward. We need a container for the game, a bird element, and elements for the moving pipes and the score display.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Flappy Bird - Devs3.Pro</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="game-container">
        <div class="bird" id="bird"></div>
        <div class="pipes" id="pipes"></div>
        <div class="score-container">
            <div>Score: <span id="score">0</span></div>
            <div>High Score: <span id="high-score">0</span></div>
        </div>
        <div class="message" id="message">Click to Start</div>
    </div>
    <script src="script.js"></script>
</body>
</html>

Step 2: Styling with CSS

This CSS will create our game's visual theme. We'll style the background, the bird with a simple flapping animation, the pipes, and the scoreboard.

body {
    margin: 0;
    padding: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-color: #f7f7f7;
    font-family: 'Arial', sans-serif;
}

.game-container {
    position: relative;
    width: 320px;
    height: 480px;
    overflow: hidden;
    border: 2px solid #333;
    border-radius: 5px;
    background: linear-gradient(to bottom, #6ab7f5, #36d1dc);
}

/* Bird Styling and Animation */
.bird {
    position: absolute;
    width: 40px;
    height: 30px;
    background-color: #ffeb3b; /* Yellow */
    border-radius: 50% 50% 40% 40%;
    top: 50%;
    left: 50px;
    z-index: 10;
    /* Simple "flap" animation */
    animation: flap 0.3s infinite alternate;
}

@keyframes flap {
    from { transform: translateY(0px) rotate(-5deg); }
    to { transform: translateY(-3px) rotate(5deg); }
}

/* Pipe Styling */
.pipe {
    position: absolute;
    width: 60px;
    background-color: #4CAF50; /* Green */
    border: 2px solid #388E3C;
    border-radius: 3px;
}

.pipe-top {
    top: 0;
    /* The height is set dynamically by JavaScript */
}

.pipe-bottom {
    bottom: 0;
    /* The height is set dynamically by JavaScript */
}

/* Score and UI Styling */
.score-container {
    position: absolute;
    top: 10px;
    left: 10px;
    color: white;
    font-size: 1.2em;
    font-weight: bold;
    z-index: 20;
    text-shadow: 1px 1px 2px black;
}

.message {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background-color: rgba(0, 0, 0, 0.7);
    color: white;
    padding: 20px;
    border-radius: 10px;
    text-align: center;
    z-index: 100;
    font-size: 1.5em;
}

Step 3: The JavaScript Game Logic

This is where the magic happens. We'll handle the game state, gravity, pipe generation, and collisions.

// Game Variables
const bird = document.getElementById('bird');
const pipesContainer = document.getElementById('pipes');
const scoreDisplay = document.getElementById('score');
const highScoreDisplay = document.getElementById('high-score');
const message = document.getElementById('message');

let birdTop = 200; // Initial bird position
let gravity = 0.8;
let velocity = 0;
let gameInterval;
let pipeInterval;
let score = 0;
let highScore = localStorage.getItem('flappyHighScore') || 0;
let isGameOver = true;

highScoreDisplay.textContent = highScore;

// Apply initial bird position
bird.style.top = birdTop + 'px';

// Event Listeners
document.addEventListener('click', jump);
document.addEventListener('keydown', (e) => {
    if (e.code === 'Space') {
        jump();
    }
});

// Jump Function
function jump() {
    if (isGameOver) {
        startGame();
    } else {
        velocity = -12; // Negative velocity makes the bird go up
    }
}

// Start the Game
function startGame() {
    if (!isGameOver) return; // Prevent multiple starts

    isGameOver = false;
    score = 0;
    scoreDisplay.textContent = score;
    birdTop = 200;
    velocity = 0;
    pipesContainer.innerHTML = ''; // Clear old pipes
    message.style.display = 'none';

    // Clear any existing intervals
    clearInterval(gameInterval);
    clearInterval(pipeInterval);

    // Start the main game loop and pipe generation
    gameInterval = setInterval(updateGame, 20);
    pipeInterval = setInterval(createPipe, 1500); // Create a new pipe every 1.5 seconds
}

// Main Game Loop
function updateGame() {
    // Apply gravity
    velocity += gravity;
    birdTop += velocity;
    bird.style.top = birdTop + 'px';

    // Rotate bird based on velocity
    bird.style.transform = `rotate(${velocity * 2}deg)`;

    // Check for collisions with top and bottom
    if (birdTop >= 450 || birdTop <= 0) {
        endGame();
    }

    // Check for collisions with pipes
    checkCollision();
}

// Create a new pair of pipes
function createPipe() {
    if (isGameOver) return;

    const pipeGap = 150; // Gap between top and bottom pipe
    const minPipeHeight = 50;
    const maxPipeHeight = 320;

    // Random height for the top pipe
    const pipeTopHeight = Math.floor(Math.random() * (maxPipeHeight - minPipeHeight)) + minPipeHeight;

    // Create DOM elements for the pipes
    const pipeTop = document.createElement('div');
    const pipeBottom = document.createElement('div');

    pipeTop.className = 'pipe pipe-top';
    pipeBottom.className = 'pipe pipe-bottom';

    // Set the heights and positions
    pipeTop.style.height = pipeTopHeight + 'px';
    pipeBottom.style.height = (480 - pipeTopHeight - pipeGap) + 'px';

    pipesContainer.appendChild(pipeTop);
    pipesContainer.appendChild(pipeBottom);

    // Animate the pipes (move from right to left)
    let pipePosition = 320;
    const pipeSpeed = 2;

    const movePipes = setInterval(() => {
        if (isGameOver) {
            clearInterval(movePipes);
            return;
        }

        pipePosition -= pipeSpeed;
        pipeTop.style.left = pipePosition + 'px';
        pipeBottom.style.left = pipePosition + 'px';

        // Remove pipes when they go off-screen
        if (pipePosition < -60) {
            clearInterval(movePipes);
            pipesContainer.removeChild(pipeTop);
            pipesContainer.removeChild(pipeBottom);
        }

        // Score a point when the bird passes a pair of pipes
        if (pipePosition === 50) { // Adjusted to when the pipes pass the bird's x-position
            updateScore();
        }

    }, 20);
}

// Check for collision between the bird and pipes
function checkCollision() {
    const birdRect = bird.getBoundingClientRect();
    const pipes = document.getElementsByClassName('pipe');

    for (let pipe of pipes) {
        const pipeRect = pipe.getBoundingClientRect();

        // Simple AABB (Axis-Aligned Bounding Box) collision detection
        if (birdRect.right > pipeRect.left &&
            birdRect.left < pipeRect.right &&
            birdRect.bottom > pipeRect.top &&
            birdRect.top < pipeRect.bottom) {
            endGame();
            break;
        }
    }
}

// Update the score
function updateScore() {
    if (isGameOver) return;
    score++;
    scoreDisplay.textContent = score;

    // Update high score in real-time
    if (score > highScore) {
        highScore = score;
        highScoreDisplay.textContent = highScore;
    }
}

// End the Game
function endGame() {
    isGameOver = true;
    clearInterval(gameInterval);
    clearInterval(pipeInterval);

    // Save high score to localStorage
    localStorage.setItem('flappyHighScore', highScore);

    // Show game over message
    message.textContent = 'Game Over! Click to Restart';
    message.style.display = 'block';
}

Conclusion and Next Steps

Congratulations! You've just built a fully functional Flappy Bird game. Let's recap what we accomplished:

  • Game Physics: Implemented gravity and jump mechanics.
  • Procedural Generation: Created pipes with random heights at regular intervals.
  • Collision Detection: Used bounding box logic to detect when the bird hits a pipe or the ground.
  • State Management: Handled the game's start, running, and end states smoothly.

Ideas for Enhancement:

  • Add Sound Effects: Include a jump sound, score sound, and a crash sound.
  • Improve Graphics: Replace the colored div elements with sprite images for the bird and pipes.
  • Difficulty Progression: Gradually increase the game speed or decrease the gap between pipes as the score gets higher.
  • Mobile Optimization: Fine-tune the touch controls for a better mobile experience.

The full source code is available in the code blocks above. Feel free to experiment, break it, and fix it. This is how you truly master game development with JavaScript.

Happy Coding!

0 Comments