DEV Community

Cover image for Build a Flappy Bird Clone with TCJSGame in 30 Minutes
Kehinde Owolabi
Kehinde Owolabi

Posted on

Build a Flappy Bird Clone with TCJSGame in 30 Minutes

Build a Flappy Bird Clone with TCJSGame in 30 Minutes

Flappy Bird Game

Ready to create your first game with TCJSGame? In this tutorial, we'll build a complete Flappy Bird clone from scratch in just 30 minutes. No prior game development experience requiredโ€”just basic JavaScript knowledge!

What We'll Build

By the end of this tutorial, you'll have a fully functional Flappy Bird game with:

  • Bird character with gravity and flapping mechanics
  • Randomly generated pipes that scroll toward the bird
  • Collision detection to end the game when you hit pipes
  • Score system that increases as you pass through pipes
  • Game over screen with restart functionality

Prerequisites

  • Basic HTML and JavaScript knowledge
  • A text editor (VS Code, Sublime Text, etc.)
  • A modern web browser

Step 1: Project Setup (2 minutes)

Create a new HTML file and set up the basic TCJSGame structure:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Flappy Bird Clone - TCJSGame</title>
    <style>
        body {
            margin: 0;
            padding: 20px;
            background: linear-gradient(to bottom, #87CEEB, #E0F7FA);
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            font-family: Arial, sans-serif;
        }
        #game-container {
            text-align: center;
        }
        canvas {
            border: 3px solid #2c3e50;
            border-radius: 10px;
            box-shadow: 0 4px 15px rgba(0,0,0,0.3);
        }
        .instructions {
            margin-top: 15px;
            color: #2c3e50;
            font-size: 18px;
        }
    </style>
</head>
<body>
    <div id="game-container">
        <h1>๐Ÿฆ Flappy Bird Clone</h1>
        <div class="instructions">Press SPACE or CLICK to flap!</div>
        <!-- Game canvas will be inserted here -->
    </div>

    <!-- Include TCJSGame v3 -->
    <script src="https://tcjsgame.vercel.app/mat/tcjsgame-v3.js"></script>

    <script>
        // Our game code will go here!
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Step 2: Initialize the Game (3 minutes)

Add the TCJSGame initialization code inside the <script> tags:

// Initialize game display
const display = new Display();
display.start(400, 600, document.getElementById('game-container'));

// Game state variables
let gameStarted = false;
let gameOver = false;
let score = 0;

// Create bird character
const bird = new Component(30, 30, "#FFD700", 100, 250, "rect");
bird.physics = true;
bird.gravity = 0.5;
bird.bounce = 0.3;
display.add(bird);

// Create score display
const scoreText = new Component(24, 24, "white", 10, 10, "text");
scoreText.text = `Score: ${score}`;
display.add(scoreText);

console.log("๐ŸŽฎ Flappy Bird initialized!");
Enter fullscreen mode Exit fullscreen mode

Step 3: Bird Mechanics (5 minutes)

Implement the bird's flapping movement and input handling:

// Flap function - makes the bird jump upward
function flap() {
    if (!gameOver) {
        bird.speedY = -8;
        gameStarted = true;
    }
}

// Input handling
window.addEventListener('keydown', (e) => {
    if (e.code === 'Space') {
        flap();
    }
});

display.canvas.addEventListener('click', () => {
    flap();
});

// Add visual feedback for the bird
function updateBirdAppearance() {
    // Make bird "tilt" based on vertical speed
    bird.angle = bird.speedY * 0.05;

    // Color change for visual feedback
    if (bird.speedY < -5) {
        bird.color = "#FF6B6B"; // Red when flapping hard
    } else {
        bird.color = "#FFD700"; // Gold normally
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Pipe System (8 minutes)

Create the pipe generation and movement system:

const pipes = [];
const pipeGap = 150; // Space between top and bottom pipes
const pipeWidth = 60;
let pipeTimer = 0;

// Create a new pipe pair
function createPipe() {
    const pipeX = display.canvas.width;
    const gapPosition = Math.random() * 200 + 150; // Random gap position

    // Top pipe
    const topPipe = new Component(pipeWidth, gapPosition - pipeGap, "#27ae60", pipeX, 0, "rect");
    topPipe.speedX = -3;
    topPipe.name = "pipe";
    display.add(topPipe);

    // Bottom pipe
    const bottomPipe = new Component(pipeWidth, display.canvas.height - gapPosition, "#27ae60", 
                                   pipeX, gapPosition, "rect");
    bottomPipe.speedX = -3;
    bottomPipe.name = "pipe";
    display.add(bottomPipe);

    pipes.push({ top: topPipe, bottom: bottomPipe, scored: false });
}

// Update pipes and check for scoring
function updatePipes() {
    pipeTimer++;

    // Create new pipes every 100 frames
    if (pipeTimer > 100 && gameStarted && !gameOver) {
        createPipe();
        pipeTimer = 0;
    }

    // Update pipe positions and check scoring
    pipes.forEach((pipePair, index) => {
        // Remove pipes that are off-screen
        if (pipePair.top.x + pipeWidth < 0) {
            display.add(pipePair.top, 1); // Remove from display
            display.add(pipePair.bottom, 1);
            pipes.splice(index, 1);
        }

        // Score when bird passes through pipe
        if (!pipePair.scored && pipePair.top.x + pipeWidth < bird.x) {
            pipePair.scored = true;
            score++;
            scoreText.text = `Score: ${score}`;
        }
    });
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Collision Detection (5 minutes)

Implement game over conditions and collision checking:

function checkCollisions() {
    // Check if bird hits the ground or ceiling
    if (bird.y >= display.canvas.height - bird.height || bird.y <= 0) {
        endGame();
        return;
    }

    // Check collisions with pipes
    for (let pipePair of pipes) {
        if (bird.crashWith(pipePair.top) || bird.crashWith(pipePair.bottom)) {
            endGame();
            return;
        }
    }
}

function endGame() {
    if (!gameOver) {
        gameOver = true;
        console.log("๐Ÿ’€ Game Over! Final score:", score);

        // Show game over message
        const gameOverText = new Component(28, 28, "#e74c3c", 120, 250, "text");
        gameOverText.text = `Game Over! Score: ${score}`;
        display.add(gameOverText);

        // Add restart instruction
        const restartText = new Component(18, 18, "white", 140, 290, "text");
        restartText.text = "Refresh to play again!";
        display.add(restartText);
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 6: Main Game Loop (5 minutes)

Bring everything together in the update function:

function update() {
    if (!gameStarted) return;

    updateBirdAppearance();
    updatePipes();

    if (!gameOver) {
        checkCollisions();
    }

    // Add some visual effects to pipes
    pipes.forEach(pipePair => {
        // Make pipes pulse slightly for visual interest
        const pulse = Math.sin(display.frameNo * 0.1) * 2;
        pipePair.top.width = pipeWidth + pulse;
        pipePair.bottom.width = pipeWidth + pulse;
    });
}

// Add some starting instructions
const startText = new Component(20, 20, "#2c3e50", 120, 200, "text");
startText.text = "Press SPACE or CLICK to start!";
display.add(startText);

// Remove start text when game begins
function checkGameStart() {
    if (gameStarted && startText) {
        display.add(startText, 1); // Remove from display
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 7: Enhanced Update Function (2 minutes)

Update the main update function to include all features:

function finalUpdate() {
    checkGameStart();
    updateBirdAppearance();
    updatePipes();

    if (gameStarted && !gameOver) {
        checkCollisions();
    }
}

// Replace the original update function
// Note: In practice, you'd integrate this step by step
Enter fullscreen mode Exit fullscreen mode

๐ŸŽฎ Complete Source Code

Here's the complete code for your Flappy Bird clone:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Flappy Bird Clone - TCJSGame</title>
    <style>
        body { margin: 0; padding: 20px; background: linear-gradient(to bottom, #87CEEB, #E0F7FA); 
               display: flex; justify-content: center; align-items: center; min-height: 100vh; 
               font-family: Arial, sans-serif; }
        #game-container { text-align: center; }
        canvas { border: 3px solid #2c3e50; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.3); }
        .instructions { margin-top: 15px; color: #2c3e50; font-size: 18px; }
    </style>
</head>
<body>
    <div id="game-container">
        <h1>๐Ÿฆ Flappy Bird Clone</h1>
        <div class="instructions">Press SPACE or CLICK to flap!</div>
    </div>

    <script src="https://tcjsgame.vercel.app/mat/tcjsgame-v3.js"></script>
    <script>
        const display = new Display();
        display.start(400, 600, document.getElementById('game-container'));

        let gameStarted = false, gameOver = false, score = 0;
        const pipes = [];
        const pipeGap = 150;
        let pipeTimer = 0;

        // Bird
        const bird = new Component(30, 30, "#FFD700", 100, 250, "rect");
        bird.physics = true; bird.gravity = 0.5; bird.bounce = 0.3;
        display.add(bird);

        // UI
        const scoreText = new Component(24, 24, "white", 10, 10, "text");
        scoreText.text = `Score: ${score}`;
        display.add(scoreText);

        const startText = new Component(20, 20, "#2c3e50", 120, 200, "text");
        startText.text = "Press SPACE or CLICK to start!";
        display.add(startText);

        // Input
        function flap() {
            if (!gameOver) { bird.speedY = -8; gameStarted = true; }
        }
        window.addEventListener('keydown', (e) => { if (e.code === 'Space') flap(); });
        display.canvas.addEventListener('click', flap);

        // Pipes
        function createPipe() {
            const pipeX = display.canvas.width;
            const gapPosition = Math.random() * 200 + 150;

            const topPipe = new Component(60, gapPosition - pipeGap, "#27ae60", pipeX, 0, "rect");
            const bottomPipe = new Component(60, display.canvas.height - gapPosition, "#27ae60", 
                                           pipeX, gapPosition, "rect");
            topPipe.speedX = bottomPipe.speedX = -3;
            display.add(topPipe); display.add(bottomPipe);
            pipes.push({ top: topPipe, bottom: bottomPipe, scored: false });
        }

        function update() {
            // Start check
            if (gameStarted && startText) display.add(startText, 1);

            // Bird effects
            bird.angle = bird.speedY * 0.05;
            bird.color = bird.speedY < -5 ? "#FF6B6B" : "#FFD700";

            // Pipes
            pipeTimer++;
            if (pipeTimer > 100 && gameStarted && !gameOver) {
                createPipe(); pipeTimer = 0;
            }

            pipes.forEach((pipePair, index) => {
                pipePair.top.x += pipePair.top.speedX;
                pipePair.bottom.x += pipePair.bottom.speedX;

                if (pipePair.top.x + 60 < 0) {
                    display.add(pipePair.top, 1); display.add(pipePair.bottom, 1);
                    pipes.splice(index, 1);
                }

                if (!pipePair.scored && pipePair.top.x + 60 < bird.x) {
                    pipePair.scored = true; score++;
                    scoreText.text = `Score: ${score}`;
                }

                if (!gameOver && (bird.crashWith(pipePair.top) || bird.crashWith(pipePair.bottom))) {
                    endGame();
                }
            });

            // Boundaries
            if (!gameOver && (bird.y >= display.canvas.height - bird.height || bird.y <= 0)) {
                endGame();
            }
        }

        function endGame() {
            gameOver = true;
            const gameOverText = new Component(28, 28, "#e74c3c", 120, 250, "text");
            gameOverText.text = `Game Over! Score: ${score}`;
            display.add(gameOverText);
        }
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

๐Ÿš€ Next Steps

Congratulations! You've built a complete Flappy Bird clone with TCJSGame. Here are some ways to extend your game:

  1. Add sound effects for flapping, scoring, and game over
  2. Implement particle effects when the bird crashes
  3. Add different pipe types with varying difficulties
  4. Create a high score system using localStorage
  5. Add power-ups that give temporary abilities

๐Ÿ“š What You Learned

  • TCJSGame basics: Display setup, component creation, physics
  • Game loop management: Update functions and frame-based logic
  • Collision detection: Using TCJSGame's built-in crash detection
  • Game state management: Tracking game started/over states
  • User input handling: Keyboard and mouse events

๐ŸŽฏ Challenge Yourself

Try implementing these additional features:

  • Add a countdown before the game starts
  • Create moving background elements
  • Implement different bird characters
  • Add difficulty progression (faster pipes over time)

What will you build next with TCJSGame? Share your Flappy Bird variations in the comments below! If you got stuck or have questions, feel free to askโ€”I'm here to help you on your game development journey.

Follow me for more TCJSGame tutorials and game development tips!

Top comments (0)