Flappy Bird in less than 100 lines - with HTML5

Flappy Bird

In the indie world, successes are often only respected if the effort involved seems high enough. In the eyes of many, success is only justified then. There are many examples where Indie developers don't treat their successful competitors to success. In some forums, for example, I often read the opinion that the huge success and hype Minecraft has achieved is not deserved, according to logic: "Minecraft is such a simple game, I can also do that, why did he get rich with it and not me."

In the vernacular one can probably also speak of envy. In fact, Minecraft in its first versions is a quite "easy" to develop game (no longer for a long time). The developer of the game Flappy Bird got the same envious looks, which is probably one of the reasons why he removed the game from the App- and Playstore at some point. In this article I would like to show you how easy Flappy Bird is to realize.

HTML5 - Document

The HTML5 basic framework is extremely simple and self-explanatory, all we need is a canvas element with the ID canvas

<!DOCTYPE html>
<html>
    <head>
        <script src="game.js"></script>
    </head>
    <body>
        <canvas id="canvas" width="800" height="600"></canvas>
    </body>
</html>

The interesting part - Javascript

In this part I will only go into the most interesting one. My Flappy Bird clone consists of three functions. The first one is the onload method I use to initialize, the createPipe function which, as the name suggests, creates the tubes and the game function which does the drawing and the game logic.

onload

window.onload = function()
{
    canvas = document.getElementById("canvas");
    canvas.addEventListener("mouseup", function() { bird.accY = -200; bird.a = -40; });
    ctx = canvas.getContext("2d"); 
    
    background.src = "data/background.png";
    bird.img.src = "data/bird.png";
    floor.src = "data/floor.png";
    createPipe();
    setInterval(game, 0);
    setInterval(createPipe, 1000);
}

In the onload method, which is called after all DOM elements have been loaded, I initialize the canvas variable, insert a mouse button triggered event listener, which makes our bird jump. bird.a stands for the rotation and is purely visual.

Then I load the image files, create a tube, call the game logic function as often as possible, and the createPipe() function every second.

createPipe

Continue with the createPipe function, which takes care of the tubes in our Flappy Bird clone.

function createPipe(reverse)
{
    var lastPipe = (reverse) ? pipes[pipes.length-1] : zero;
    
    // random length
    **var l = Math.floor(Math.random() * 5) + 1;
    if(reverse) l = Math.floor(((background.height - lastPipe.y) - pipeSize*2) / pipeSize);

    for(var i = 0; i < l; ++i)
    {
        **var pipe = { img: new Image(), x: pipePenX, y: 0 };
        **if(i < l-1) pipe.img.src = "data/pipe.png";
        **else if(!reverse) pipe.img.src= "data/pipe_end.png"; 
        else if(reverse) pipe.img.src="data/pipe_end_r.png";
        
        **if(!reverse) pipe.y = i * pipeSize;
        else pipe.y = background.height - (i+1)*pipeSize;
        **pipes[pipes.length] = pipe;
   }
    
    **if(!reverse) createPipe(true);
    else pipePenX += 100 + pipeSize;
}

The function performs two tasks: First it creates a tube above and then a mirrored tube below. In the first run, only the highlighted lines are interesting. In the first highlighted line we generate a random number of 1-5, which indicates the upper tube length. Then we execute the code 1-5 times in the counting loop. This creates a new tube part, positions it at the top and adds it to the pipes array. Finally we call the function again with the reverse parameter. Now the same is done, only that the lower tube length is calculated from the previous tube length, so we make sure that the tubes are always the same distance apart. The positioning and the end tube piece will of course also be adjusted. Finally, we increase the pipePenX variable by 164 pixels so that the tube pairs don't lie on top of each other.

game

And we come to the heart of our Flappy Bird clone, the game function.

function game()
{
    var now = Date.now();
    ft = (now - ftBefore) / 1000;
    
    var birdSizeHalf = bird.img.width*0.5;
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.drawImage(background, 0, 0);
    ctx.drawImage(floor, 0, background.height);
    
    ctx.save();
    ctx.translate(bird.x, bird.y);
    ctx.rotate(bird.a * Math.PI / 180);
    ctx.drawImage(bird.img, -birdSizeHalf, -birdSizeHalf);   
    ctx.restore();
    
    if(!gameover)
    {
        bird.y += bird.accY * ft;
        if(bird.accY < 400) bird.accY += 1000 * ft;
        if(bird.a <= 80) bird.a += 140*ft;
    }
    
    for(var i = 0; i < pipes.length; ++i)
    {
        var p = pipes[i], pimg = p.img, pend = pimg.src.indexOf("pipe_end.png");
        if(!gameover) p.x -= 60 * ft;
        ctx.drawImage(pimg, p.x, p.y);
        
        if(bird.x + birdSizeHalf >= p.x && bird.x - birdSizeHalf <= p.x + pipeSize 
           && bird.y + birdSizeHalf >= p.y && bird.y - birdSizeHalf <= p.y + pipeSize)
        {
            gameover = true;
        }
        else if(pend !== -1 && p.blocked === undefined && bird.x > p.x + pipeSize)
        {
            ++counter;
            p.blocked = true;
        }
        
        if(p.x + pipeSize <= 0) pipes.splice(i, 1);
    }

    if(bird.y - birdSizeHalf <= 0 || bird.y + birdSizeHalf >= 500) gameover = true;
    
    ctx.font = "40px Arial";
    ctx.fillText(counter, background.width*0.5 - 10, 50);
    
    ftBefore = now;
}

In the first lines we initialize the necessary variables for the calculation of the frame time. Then we clean the playing field and draw our background and the ground on it. We save the current state, rotate and draw our Flappy Bird. Then comes the ponderous jump of our Flapp Birds. How to realize a jump can be read here in more detail: Simulate character jump.

We continue with the tubes, which we move constantly and permanently slowly in the direction of our Flappy Bird, which moves only in the Y axis (when jumping). We draw the tube. Then we check if there was a collision between tube and bird, this is the case: Game Over. If there was no collision, we check if our bird has survived a pair of tubes. If this is the case, we increase the highscore counter and "block" the tubes internally. Blocking prevents that the highscore counter is increased several times. If the tube part is outside our playing field, it is removed from the array.

Vielleicht auch interessant
Tiled Map - Multiplayer Indie Game - Gether
Tiled Map - Multiplayer Indie Game - Gether

Eine mit Tiled erstellte Map laden.

Then we check whether the bird touches the ground or wanted to leave the playing field, because then the player also lost. Then we draw the highscore text.

Our Flappy Bird clone is ready in less than 100 lines.

Flappy Bird in less than 100 lines - Source code

Live Demo

/** (c) Usama Ahmad - 2019 - http://www.devindie.de **/

var canvas = null, ctx = null;
var ft = 0, ftBefore = Date.now();
var background = new Image(), floor = new Image();
var bird = {img: new Image(), x: 200, y: 10, a: 0, accY: 400};
var pipes = new Array(), pipePenX = 500, pipeSize = 64;
var counter = 0, gameover = false;

window.onload = function()
{
    canvas = document.getElementById("canvas");
    canvas.addEventListener("mouseup", function() { bird.accY = -200; bird.a = -40; });
    ctx = canvas.getContext("2d"); 
    
    background.src = "data/background.png";
    bird.img.src = "data/bird.png";
    floor.src = "data/floor.png";
    createPipe();
    setInterval(game, 0);
    setInterval(createPipe, 1000);
}

function createPipe(reverse)
{
    var lastPipe = (reverse) ? pipes[pipes.length-1] : null;
    
    // random length
    var l = Math.floor(Math.random() * 5) + 1;
    if(reverse) l = Math.floor(((background.height - lastPipe.y) - pipeSize*2) / pipeSize);

    for(var i = 0; i < l; ++i)
    {
        var pipe = { img: new Image(), x: pipePenX, y: 0 };
        if(i < l-1) pipe.img.src = "data/pipe.png";
        else if(!reverse) pipe.img.src= "data/pipe_end.png"; 
        else if(reverse) pipe.img.src="data/pipe_end_r.png";
        
        if(!reverse) pipe.y = i * pipeSize;
        else pipe.y = background.height - (i+1)*pipeSize;
        pipes[pipes.length] = pipe;
    }
    
    if(!reverse) createPipe(true);
    else pipePenX += 100 + pipeSize;
}

function game()
{
    var now = Date.now();
    ft = (now - ftBefore) / 1000;
    
    var birdSizeHalf = bird.img.width*0.5;
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.drawImage(background, 0, 0);
    ctx.drawImage(floor, 0, background.height);
    
    ctx.save();
    ctx.translate(bird.x, bird.y);
    ctx.rotate(bird.a * Math.PI / 180);
    ctx.drawImage(bird.img, -birdSizeHalf, -birdSizeHalf);   
    ctx.restore();
    
    if(!gameover)
    {
        bird.y += bird.accY * ft;
        if(bird.accY < 400) bird.accY += 1000 * ft;
        if(bird.a <= 80) bird.a += 140*ft;
    }
    
    for(var i = 0; i < pipes.length; ++i)
    {
        var p = pipes[i], pimg = p.img, pend = pimg.src.indexOf("pipe_end.png");
        if(!gameover) p.x -= 60 * ft;
        ctx.drawImage(pimg, p.x, p.y);
        
        if(bird.x + birdSizeHalf >= p.x && bird.x - birdSizeHalf <= p.x + pipeSize 
           && bird.y + birdSizeHalf >= p.y && bird.y - birdSizeHalf <= p.y + pipeSize)
        {
            gameover = true;
        }
        else if(pend !== -1 && p.blocked === undefined && bird.x > p.x + pipeSize)
        {
            ++counter;
            p.blocked = true;
        }
        
        if(p.x + pipeSize <= 0) pipes.splice(i, 1);
    }

    if(bird.y - birdSizeHalf <= 0 || bird.y + birdSizeHalf >= 500) gameover = true;
    
    ctx.font = "40px Arial";
    ctx.fillText(counter, background.width*0.5 - 10, 50);
    
    ftBefore = now;
}

Finalization

At the end it remains only to say that despite the small expenditure in my opinion every success is deserved, even if luck belongs to it. By the way, you can also say that Flappy Bird is a very well thought-out and round game with a high addiction factor, which deserves to become a game hit.

Leave a like or comment (~‾▿‾)~
Name Text