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
divelements 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!