Skip to content

Mystik01/FlappyBird

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

36 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Flappy Bird

P5 produce basic documentation for a working game

D2 produce detailed technical documentation for a working game

User Documentation

Installation:

Flappy Bird is a web based game written in JavaScript so can be played in your browser. I have provided the local code attached in a zip file but shouldn’t need to install anything to run it.

Running online:

I have the game running on my GitHub live so shouldn’t require any installation. On there you can also view the code without downloading it. This will be the most up-to-date version as well.

https://mystik01.github.io/FlappyBird/public/

Running locally:

To run the game simply direct into the ~/public folder and open the index.html file.

How to play?

The game is quite simple to play, simply hit space or click with your mouse if running on a computer to make the bird jump. The aim of the game is to get through as many gaps as possible and your live score is tracked in the top left.

The games does also work on mobile but is not as responsive and not recommended.

Reporting bugs:

Bugs can be reported into my Github through this link https://github.com/Mystik01/FlappyBird/issues/new/choose

This will allow you to either create a bug report or feedback report. Users are also welcome to collaborate and expand on the code/improve it’s performance or compatibility and can submit that in the PR section.

Technical info:

The game tracks your highest score in your browsers cookies and is the only data which it tracks and stores. The whole game is client sided and the code is only hosted GitHub. The actual game is run on your pc so if you have an older/slower computer it may impact the performance. All computers and mobiles should be able to run this fine without any performance issues however mobile isn’t completely compatible. You do need to have a newish/updated browser to play, e.g old internet explorer would not be able to run this. This shouldn’t be an issue for majority of users.

Have had some issues where the bird’s hitbox is hitting the walls when visually it is not. This is because the image the the bird is not exactly cut around the bird. I haven’t been able to find a fix for this and have cropped it as tight as possible.

You can view the hitbox by opening your browser console (F12 or CTRL+SHIFT+I) and typing into the console debugMode = true . You can achieve the same by clicking CTRL + ALT + SHIFT + S .

You can view the raw game code here:

const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");
const aspectRatio = 400 / 600; // original width / height
canvas.height = window.innerHeight;
canvas.width = canvas.height * aspectRatio;
// Set background color
ctx.fillStyle = "lightblue";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Game variables
let bird = { x: 50, y: 150, velocityY: 0, width: 35, height: 35 };
let gravity = 0.6;
let jump = -8;
let pipes = [];
let pipeWidth = 50;
let pipeGap = 150;
let score = 0;
let highScore = getCookie("highScore") || 0;
let gameRunning = true;
let frameCount = 0;
var debugMode = false;
let gameStarted = false;
let gamePaused = false;
// Bird image
let birdImg = new Image();
birdImg.src = "bird4.png"; // Bird
function drawBird() {
ctx.drawImage(birdImg, bird.x, bird.y, bird.width, bird.height);
}
function updateBird() {
bird.velocityY += gravity;
bird.y += bird.velocityY;
if (bird.y + bird.height > canvas.height || bird.y < 0) {
gameOver();
}
}
function addPipe() {
// Must always have a bottom pipe
let minTopPipeHeight = 100; // Minimum height for the top pipe
let maxTopPipeHeight = canvas.height - pipeGap - 100; // Maximum height for the top pipe
let topPipeHeight =
Math.floor(Math.random() * (maxTopPipeHeight - minTopPipeHeight + 1)) +
minTopPipeHeight;
pipes.push({ x: canvas.width, y: topPipeHeight });
}
function drawPipes() {
ctx.fillStyle = "#af185b";
pipes.forEach(function (pipe) {
let topPipeHeight = pipe.y;
let bottomPipeY = topPipeHeight + pipeGap;
ctx.fillRect(pipe.x, 0, pipeWidth, topPipeHeight);
ctx.fillRect(pipe.x, bottomPipeY, pipeWidth, canvas.height - bottomPipeY);
});
}
function updatePipes() {
if (frameCount % 120 === 0) {
// Change from 90 to 180 - Gap between each set of pipes
addPipe();
}
pipes.forEach(function (pipe, index) {
pipe.x -= 2;
if (pipe.x + pipeWidth < -pipeWidth) {
// Change from 0 to -pipeWidth
pipes.splice(index, 1);
}
if (pipe.x + pipeWidth < bird.x && !pipe.scored) {
score++;
pipe.scored = true; // Mark the pipe as scored - tracking scores
}
if (gameRunning && collisionDetection(pipe)) {
gameOver(); // you die
}
});
}
function collisionDetection(pipe) {
let birdRight = bird.x + bird.width;
let birdBottom = bird.y + bird.height;
let pipeRight = pipe.x + pipeWidth;
let pipeTop = pipe.y;
let pipeBottom = pipe.y + pipeGap;
// Check for collision with the top pipe
if (birdRight > pipe.x && bird.x < pipeRight && bird.y < pipeTop) {
return true;
}
// Check for collision with the bottom pipe
if (birdRight > pipe.x && bird.x < pipeRight && birdBottom > pipeBottom) {
return true;
}
return false;
}
function timeUntilFloorCollision() {
let distanceToFloor = canvas.height - (bird.y + bird.height);
let velocityY = bird.velocityY;
let time =
(-velocityY +
Math.sqrt(velocityY * velocityY + 2 * gravity * distanceToFloor)) /
gravity;
// Check for NaN or negative values
if (isNaN(time) || time < 0) {
return 0;
}
return time;
}
function perfectJump() {
// Find the next pipe
console.log("Perfect jump");
let nextPipe = pipes.find((pipe) => pipe.x + pipe.width > bird.x);
// Check if the bird is at the same height as the bottom of the next pipe's gap
if (nextPipe && bird.y >= nextPipe.y + nextPipe.gap) {
// Make the bird jump
bird.velocityY = -jump; // Assuming a negative velocityY makes the bird move upwards
}
}
function drawDebug() {
// Debug: Draw collision boxes
console.log("Debug mode on");
pipes.forEach(function (pipe) {
ctx.fillStyle = "rgba(255, 0, 0, 0.3)";
ctx.fillRect(pipe.x, 0, pipeWidth, pipe.y);
ctx.fillStyle = "rgba(0, 0, 255, 0.3)";
ctx.fillRect(
pipe.x,
pipe.y + pipeGap,
pipeWidth,
canvas.height - (pipe.y + pipeGap)
);
// Highlight the hitbox of the pipes in red
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
ctx.strokeRect(pipe.x, 0, pipeWidth, pipe.y);
ctx.strokeRect(
pipe.x,
pipe.y + pipeGap,
pipeWidth,
canvas.height - (pipe.y + pipeGap)
);
});
ctx.fillStyle = "rgba(0, 255, 0, 0.3)";
ctx.fillRect(bird.x, bird.y, bird.width, bird.height);
// Highlight the hitbox of the bird in red
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
ctx.strokeRect(bird.x, bird.y, bird.width, bird.height);
drawGravityArrow();
}
function drawGravityArrow() {
let velocityY = bird.velocityY;
let distanceToFloor = canvas.height - (bird.y + bird.height);
let timeToCollision =
(-velocityY +
Math.sqrt(velocityY * velocityY + 2 * gravity * distanceToFloor)) /
gravity;
// Check for NaN or negative values
if (isNaN(timeToCollision) || timeToCollision < 0) {
timeToCollision = 0;
}
// Only draw if there is significant velocity
if (Math.abs(velocityY) > 1) {
// Set the color and style for the arrow
ctx.strokeStyle = "purple";
ctx.fillStyle = "purple";
ctx.lineWidth = 2;
// Calculate the center of the bird
let centerX = bird.x + bird.width / 2;
let centerY = bird.y + bird.height / 2;
// Calculate the end point of the arrow based on velocity
let endX = centerX;
let endY = centerY + velocityY * 10; // Scale the length of the arrow
// Draw the line for the arrow
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.lineTo(endX, endY);
ctx.stroke();
// Draw the arrow head
let arrowHeadSize = 5;
ctx.beginPath();
ctx.moveTo(endX, endY);
ctx.lineTo(endX - arrowHeadSize, endY - arrowHeadSize);
ctx.lineTo(endX + arrowHeadSize, endY - arrowHeadSize);
ctx.closePath();
ctx.fill();
ctx.fillStyle = "black";
ctx.font = "14px Arial";
ctx.fillText(`${timeToCollision.toFixed(2)}ms`, endX + 10, endY);
}
}
function gameOver() {
gameRunning = false;
gameStarted = false;
updateHighScore();
ctx.fillStyle = "black";
ctx.font = "36px Arial";
ctx.fillText("Game Over", 100, canvas.height / 2);
ctx.fillText(`Score: ${score}`, 130, canvas.height / 2 + 40);
ctx.fillText(`High Score: ${highScore}`, 100, canvas.height / 2 + 80);
document.getElementById("restartButton").style.display = "block";
} // When die/failure
function updateHighScore() {
if (score > highScore) {
highScore = score;
setCookie("highScore", highScore, 365);
}
} // Store highest score in cookies
function restartGame() {
// Self explainatry
bird = { x: 50, y: 150, velocityY: 0, width: 30, height: 30 };
pipes = [];
score = 0;
gameRunning = true;
frameCount = 0;
document.getElementById("restartButton").style.display = "none";
gameStarted = false;
//debugMode = window.debugMode || false; // Keep the debug mode state
gameLoop();
}
function setCookie(name, value, days) {
// Set highscore
const d = new Date();
d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
let expires = "expires=" + d.toUTCString();
document.cookie = name + "=" + value + ";" + expires + ";path=/";
} // Don't touch
function getCookie(name) {
// Gets high score
let nameEQ = name + "=";
let ca = document.cookie.split(";");
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == " ") c = c.substring(1);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
} // dont touch
document.addEventListener("keydown", function (e) {
if (e.code === "Space") {
// Jump
if (gameRunning) {
bird.velocityY = jump;
gameStarted = true;
} else {
restartGame();
}
}
if (e.ctrlKey && e.altKey && e.shiftKey && e.key === "S") {
// Toggle debugMode
debugMode = !debugMode;
}
if (e.code === "Escape" && gameStarted) {
//pause
console.log(gameStarted);
gamePaused = !gamePaused;
if (gamePaused) {
showPauseMenu();
} else {
requestAnimationFrame(gameLoop);
}
}
if (e.ctrlKey && e.altKey && e.shiftKey && e.key === "H" && gameStarted) {
// Activate the "hacker" shortcut key ++ Doesn't work
perfectJump();
}
}); // Trigger
canvas.addEventListener("click", function (event) {
// left click on mouse
if (gameRunning) {
bird.velocityY = jump;
gameStarted = true;
} else {
restartGame();
}
}); // Another trigger
canvas.addEventListener(
"touchstart",
function (event) {
// mobile clicks
if (gameRunning) {
bird.velocityY = jump;
gameStarted = true;
}
event.preventDefault(); // Prevent the default action to avoid scrolling the page
},
false
); // Helps with mobile support
function showPauseMenu() {
// Display pause menu
ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "white";
ctx.font = "30px Arial";
ctx.fillText("Game Paused", canvas.width / 2 - 70, canvas.height / 2);
}
function hidePauseMenu() {
// No need to clear the canvas here
if (!gameRunning) {
requestAnimationFrame(gameLoop);
}
}
function gameLoop() {
if (!gamePaused) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
// Set the background color
ctx.fillStyle = "#A1DAC0";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawBird();
if (debugMode) {
drawDebug(); // Debugging collision boxes
}
if (gameStarted) {
// Add this line
drawPipes();
updateBird();
updatePipes();
ctx.font = "20px Arial";
ctx.fillStyle = "black";
// Show the score
ctx.fillText(score.toString(), 10, 30);
}
if (gamePaused) {
showPauseMenu();
} else if (gameRunning) {
requestAnimationFrame(gameLoop);
}
frameCount++;
}
document.getElementById("restartButton").addEventListener("click", restartGame);
gameLoop();


Program Development

I was inspired to make my own version of Flappy Bird by the game’s iconic graphics and simple layout. Flappy Bird’s simplicity is what makes it appealing and widely playable. In addition to being simple to understand and play, the goal of the game is to pass between pipes to get the highest score. This design also makes coding the game simpler. Because of the simple goal and mechanics of the game, creating a variant would be both an easy attempt and a chance to add to a beloved genre. I chose to write it in JavaScript as it can be easily run by anyone on any device allowing anyone to play without any issues. I am familiar with JavaScript from experience and knew it could be a simple task. I also used Visual Studio Code as I find it to be the best text-editor (in my opinion) for it’s simple design and ability to customise it in any way you want and have extensions to help make your programming experience more efficient. Visual Studio Code also allowed me to connect to my GitHub codespace where I was actively writing and testing the program and synchronising it with GitHub.

Data Use

In the game, player scores are dynamically managed using variables, with the live score displayed in the top right or when the player dies/game ends. Only the highest score achieved is saved between sessions by storing it in cookies, ensuring that new higher scores always replace lesser ones. Player jumps are enabled through responsive input methods like spacebar presses, mouse clicks, or touchscreen taps. However, the game continuously generates obstacles, such as pipes, in real-time, without saving their positions, focusing solely on the immediate gameplay experience and scorekeeping.

Development of other parts of the game

The actual bird image:

Was created based on the original flappy bird but wanted to have my own version of it. My version has more colours and has slightly more complexity to it. The colours used are reflected with the background of my version of the game. Every other part of the game is rendered using Javascript and the Canvas API using HTML. This is why the game is simple and easy to run as it uses very light weight renders which are already built into your browser.

palette