Flappy Bird in unter 100 Zeilen - mit HTML5 (Live Demo)

Flappy Bird

In der Indie Welt werden Erfolge oft nur dann respektiert, wenn der betriebene Aufwand hoch genug scheint. In den Augen vieler ist der Erfolg erst dann berechtigt. Es gibt viele Beispiele wo Indie Entwickler ihren erfolgreichen Konkurrenten den Erfolg nicht gönnen. In einigen Foren habe ich damals beispielsweise oft die Meinung gelesen, dass der riesige Erfolg und Hype den Minecraft erzielt hat nicht verdient wäre, frei nach der Logik: "Minecraft ist so ein simples Spiel, ich kann das auch machen, wieso ist er damit reich geworden und nicht ich."

Im Volksmunde kann man hier vermutlich auch von Neid reden. Tatsächlich ist Minecraft in seinen ersten Versionen ein ziemlich "einfach" zu entwickelndes Spiel (inzwischen schon lange nicht mehr). Die selben neidischen Blicke hat der Entwickler des Spiels Flappy Bird abbekommen, was wohl eines der Gründe gewesen sein soll, warum er das Spiel irgendwann aus dem App- und Playstore entfernt hat. Wie einfach Flappy Bird realisierbar ist, möchte ich euch in diesem Artikel zeigen.

HTML5 - Dokument

Das HTML5 Grundgerüst ist extrem einfach und selbsterklärend, alles was wir benötigen ist ein Canvas Element mit der ID canvas

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

Der interessante Teil - Javascript

In diesem Teil werde ich nur auf dass interessanteste eingehen. Mein Flappy Bird Klon besteht aus drei Funktionen. Die erste ist die onload Methode die ich zum Initialisieren verwende, die createPipe Funktion die, wie der Name schon sagt, die Röhren erstellt und die game Funktion die sich ums Zeichnen und die Spiellogik kümmert.

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 der onload Methode, welche aufgerufen wird nachdem alle DOM Elemente geladen wurden, initialisiere ich die canvas Variable, füge einen Maustaste ausgelöst Eventlistener ein, welcher unseren Vogel springen lässt. bird.a steht für die Rotation und ist rein visuell.

Dann lade ich die Bilddateien, erstelle eine Röhre, rufe die Spiellogik Funktion so oft wie möglich auf und die createPipe() Funktion jede Sekunde.

createPipe

Weiter geht es mit der createPipe Funktion, welche sich um die Röhren in unserem Flappy Bird Klon kümmern soll.

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;
}

Die Funktion erledigt zwei Aufgaben: Zuerst erstellt sie eine Röhre oberhalb und dann eine gespiegelte Röhre unterhalb. Im ersten Durchlauf sind nur die hervorgehobenen Zeilen interessant. In der ersten hervorgehobenen Zeile generieren wir eine Zufallszahl von 1-5, welche die obere Röhrenlänge angibt. Dann führen wir den Code in der Zählschleife 1-5 mal aus. Dieser erstellt ein neues Röhrenteil, positioniert diesen oben und fügt es zum pipes Array hinzu. Zum Schluss rufen wir die Funktion nochmal auf, mit dem reverse Parameter. Jetzt wird das selbe getan, nur dass die untere Röhrenlänge anhand der vorherigen Röhrenlänge berechnet wird, damit stellen wir sicher, dass die Röhren immer den selben Abstand zueinander haben. Die Positionierung und das Endröhren Stück werden natürlich auch angepasst. Zu guter Letzt erhöhen wir die pipePenX Variable um 164 Pixel, damit die Röhrenpaare nicht aufeinander liegen.

game

Und schon kommen wir zum Herzstück unseres Flappy Bird Klons, der game Funktion.

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 den ersten Zeilen initialisieren wir die nötigen Variablen für die Berechnung der Frametime. Daraufhin säubern wir das Spielfeld und zeichen hierauf unseren Hintergrund und den Boden. Wir speichern den momentanen Zustand, rotieren und zeichnen dann unseren Flappy Bird. Dann kommt schon der schwerfällige Sprung unseren Flapp Birds. Wie man einen Sprung realisiert, kann hier detaillierter nachgelesen werden: Charakter Sprung simulieren.

Weiter geht es mit den Röhren, welche wir konstant und permanent langsam in Richtung unseres Flappy Birds bewegen, welcher sich nur in der Y Achse verschiebt (beim Sprung). Wir zeichnen die Röhre. Dann prüfen wir ob eine Kollision stattfand zwischen Röhre und Vogel, ist dies der Fall: Game Over. Falls keine Kollision stattfand, prüfen wir noch ob unser Vogel ein Röhrenpaar überstanden hat. Ist dies der Fall erhöhen wir den Highscore Counter und "blockieren" intern die überstandenen Röhren. Mit dem blockieren wird verhindert, dass der Highscore Counter mehrmals erhöht wird. Ist der Röhrenteil außerhalb unseres Spielfelds, wird er aus dem Array entfernt.

Dann prüfen wir noch ob der Vogel den Boden berührt oder das Spielfeld verlassen wollte, denn dann hat der Spieler auch verloren. Anschließend wird der Highscore Text gezeichnet.

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

Eine mit Tiled erstellte Map laden.

Fertig ist unser Flappy Bird Klon in unter 100 Zeilen.

Flappy Bird in unter 100 Zeilen - Quelltext

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;
}

Abschluss

Zum Ende bleibt nur noch zu sagen, dass trotz des geringem Aufwands meiner Meinung nach jeder Erfolg verdient ist, auch wenn Glück dazu gehört. Im übrigen kann man auch durchaus sagen, dass Flappy Bird ein sehr durchdachtes und rundes Spiel mit hohem Suchtfaktor ist, was es verdient hat zu einem Spielhit zu werden.

Projekt herunterladen: Flappy Bird

Hinterlasse gerne einen Like oder Kommentar (~‾▿‾)~
Name Text
Kommentare
Usama> 9 Mt.@Kuku Ich habe einen Link zum herunterladen des gesamten Projektes am Ende des Artikels hinzugefügt.
Kuku> 9 Mt.Gibt es Dateien für Flappy Bird Klone auch zum downloaden damit ich mir das besser anschauen kann wie es aufgebaut ist ?