Introduction
Build Flappy Bird with jQuery and 100 lines of Javascript
Play online at
Gasp Mobile Games
Background
So Flappy bird became a big news: the developer walked away from $50,000 a day. I downloaded a game and
played it - it did look simplistic and silly. I put it aside. Then my friends brough up the same topic
and jokingly asked "you can probably build it in a couple of days. $50,000 in a couple of days - not
a bad business." I though I should, but then I was busy at work. Some other things came up, and finally
when the Apple started to reject Flappy Bird clones because there were soo many, I thought - "I got to get me
some of this. With the right timing and a little bit of luck - those $50,000/day can be mine."
The version I'm presenting here took me a whooping 2 hours to build and under a 100 lines of my javascript.
For those who don't know the game - get out of your caves. You tap on the bird to give it some initial up
speed. Then it start falling (under gravity). You need to keep it in the air and avoid the obstacles it
can run into. To simplify the objective, the bird is only moves up and down, perception of horisontal motion
is acheived by scrolling background. This is it - get it done and $50,000 a day is yours.
Assets
The game needs only 5 images : the bird, background grass, background sky, obstacles and instuction
tap-to-start.
As you can see, to save myself some headace I'm skimping out on frame animation by using an
animated gif file. This way browser can use it much more efficiently. Also it's something that prevented me from publishing it on Windows Phone - since browser control there doesn't support animated GIF files.
The base html is also pretty simple:
<div id='board' style='position:absolute; left:50px; top:50px; width:478px;
height:300px; overflow:hidden;'>
<div id='score' style='position:absolute; left:400px; top:0px; height:25px;
z-index:5; color:red; font-weight:900'></div>
<img class="c" id='bird' src="b2.gif" style="z-index:5"/>
<img id='instr' src='instr.png' class='c' style="left:205px; top:75px;
z-index:100" />
<div class="bg" id='bGrnd' style="top:-20px; height:320px;
background-image:url(bg1.png) "/>
<div class="bg" id='fGrnd' style="top:235px; height:85px; z-index:4;
background-image:url(bg2.png) "/>
</div>
I also include latest jQuery from http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.js
Global variables and Initialization
The game uses following global variables:
bird | jQuery object to hold our bird |
board | jQuery object to hold the board - container object |
dimPipe | Obstacle dimentions |
cPos | Current bird position (only Y coordinate can change) |
gravity | Configurable gravity constant - how fast the bird falls |
iniSpeed | Configurable initial speed |
curSpeed | Current vertical bird's speed |
score | Current Score |
noClr | number of obstacles cleared |
tmStep | Step timer to position a bird and launch obstacles |
state | Game state : 0 - not started; 1 - in play; 2 - game over |
The game is initialized in 2 steps: usual jQuery $.ready and reusable start() that we can call every
time the game is restarted:
$(document).ready(function() {
bird = $('#bird');
var evt = (typeof(bird[0].ontouchend) == "function")
? "touchstart" : "mousedown";
board = $('#board').bind(evt, onTap);
start();
});
function start() {
state = noClr = score = 0;
cPos = { x: 80, y:100, h:40, w:50 };
bird.css({left:cPos.x, top:cPos.y, width:cPos.w, height:cPos.h, rotate:0});
$('.obs').remove();
$('#instr').show();
}
As you can see in $.ready we are initializing bird and board global variables, attaching tap event handler
to the board and calling start() function. One word about tap: on Android devices mouseDown event comes quite a bit after the actual tap happens, so in the code above, we are checking if element has onTouchEnd element and using that as an indication of the touch support.
In the start() function I'm resetting all the variables, removing any obstacles still on the board and
showing instructions image prompting to tap/click.
Click/Tap handling
So the game is ready to go. The missing part is what happen when you click on the board.
The game at this point starts main timer (BirdStep) (if needed) and sets bird's initial up speed:
function onTap() {
if (state > 1) return;
if (state == 0) {
state = 1;
$('#instr').hide();
tmStep = window.setInterval(BirdStep, 30);
}
curSpeed = iniSpeed;
}
Thing to consider is that the program uses 3 states -
- 0 - not running
- 1 - play mode
- 2 - die mode - no input is accepted.
So here we are checking - if we are in die mode - just get out.
If we are not playing, then go into play mode - change state, hide intro image and start timer.
Aside from that we want to give our bird initial up speed.
The main logic however is done in the BirdStep timer function:
function BirdStep() {
curSpeed += gravity;
cPos.y = Math.max(cPos.y + curSpeed, 0);
var mh = board.height()-cPos.h, m = -12, lo = 0, actPipe = $('.obs');
bird.css({top: cPos.y});
if (cPos.y > mh)
return gameOver();
for (var i = actPipe.length-1; i >= 0; i--) {
var s = actPipe[i].style, x = parseInt(s.left), y = parseInt(s.top);
lo = Math.max(lo, x);
if (x+dimPipe.width +m < cPos.x || x > cPos.x+cPos.w+m) continue;
if (y+dimPipe.height+m < cPos.y || y > cPos.y+cPos.h+m) continue;
return gameOver();
}
if (actPipe.length > 3 || lo > 300 || Math.random() >= 0.05 * (1+noClr))
return;
var og = cPos.h * 2;
var oh = og + Math.floor(Math.random() * (mh-og+1));
var obs = $("<img /><img />").addClass('c obs').css({left:480, zIndex:3}).css(dimPipe).attr('src', 'vine.png')
.appendTo(board).animate({left:-50}, 3000, 'linear', function() {
$('#score').text(' Score: ' + (score += 1 + Math.floor(++noClr/10)));
this.remove();
});
obs[0].style.top = oh + 'px';
obs[1].style.top = (oh - og - dimPipe.height) + "px";
}
As you can see this function tries to do 3 major things:
Update bird position
Every time the BirdStep timer executed, the current bird speed get's increased by gravity and added
to current bird Y position. Also at this point I'm checking to make sure bird doesn't fly above ceiling
(negative Y).
Hit Testing
Here we are testing if bird didn't fall too low (Y exceeds board height) or we hitting any obstacles
- loop that checks if bird's position (stored in
cPos
and reduced by some fudge margin - m = 12px) intersects
with any of the obstacles - any objects with class of
.obs. If so then the game is lost - we can
just get out.
Launch new obstacles
First thing we check if new obstacles can be launched:
- Less then 4 obstacles already on thescreen
- Last obstacles travel some distance
- add some random factor
If conditions are satisfied, we can launch 2 more obstacles, one on top of the other, with the gap of
2 bird sizes between randomly positioned along Y coordinate, right after right edge of the board (left = 480px).
After they are created they are animated to move off the left edge of the screen (left = -50px), at which
point the score is increased and obstacles are removed. To do the animation we are using plain and simple
jQuery linear animation.
Bells and wistles: parallax scrolling effect
That's pretty much a game. But so far it looks too plain. To add some umpf lets add parallax scrolling effect.
Actually we are adding 2 parallax layers - the sky and the grass. We are also need to add a depth perception -
in this implementation - the sky will just move slower then the grass - it should suffice. To create a
parallax layer, I will create a very wide div
element (16,000px) with background-repeat: repeat-x;
and set desired image as a background. The browser will horisontally replicate the image. The only thing
I need to do is just to add animation - set left position of the div using very handy jQuery animate:
function Parallax(elm, tmo) {
elm.css('left', 0).animate({left:-15360}, {
duration:tmo*1000, easing:'linear',
complete : function() { Parallax(elm, tmo); }
});
}
function onTap() {
if (state == 0) {
...
Parallax($('#bGrnd'), 240);
Parallax($('#fGrnd'), 80);
...
}
}
As you can see the code is surprisingly simple : the left position of the div is set to 0px and then
linearly animated to -15,360px (the largest common denominator less then 16,000 of all the background images
width - just so I don't have to add extra parameter to the function) after which
the whole process repeats. The supplied argument is a time to animate - the foreground (grass) supposed
to scroll for 80 seconds and background (sky) - 240 sec - 3 times slower.
Bells and wistles: rotation
Aside from parallax, it would be nice to rotate the bird - tilt it up when it flies up, and down when it
falls. Also when the game is over, to show bird roll over. To do that i created simple jquery css hook.
Please check jQuery documentation on detail about CSS Hooks.
$.cssNumber.rotate = true;
$.cssHooks.rotate = {
set : function(el, v) {
if (typeof v === 'string')
v = (v.indexOf("rad") != -1) ? parseInt(v) * 180 / Math.PI : parseInt(v);
v = (~~v);
if (v == ($.data(el, 'rotate') || 0)) return;
el.style["MozTransform"] = el.style["MozTransform"] = el.style["-webkit-transform"]
= el.style["transform"] = " rotate(" + (v % 360) + "deg)";
$.data(el, 'rotate', v);
},
get : function(el, computed) {
return $.data(el, 'rotate') || 0;
}
};
As you can see over here we are storing current rotation value in
$.data("rotate")
and setting element's
browser specific CSS attributes to set current rotation.
To use newly acquired capability let's change our
BirdStep
function to rotate a bird with the angle of 5 times
the speed. If bird flies up and speed negative, the bird tilts up, if bird is falling and speed is positive,
the bird tilts down. On top of that we want to limit the tilt between -20 and 90 degrees - completely
arbitrary:
function BirdStep() {
...
var ang = curSpeed * 5;
bird.css({top: cPos.y, rotate:(ang < -20) ? -20 : (ang > 90) ? 90 : ang});
...
}
Also we can introduce a nice animation when the bird dies - it will fall to the ground and rotate 540 degrees for one second and
then wait for another half a second :
function gameOver() {
state = 2;
$(":animated").stop();
if (tmStep) tmStep = window.clearInterval(tmStep);
bird.animate({ top:board.height()-cPos.h, rotate:540}, 1000)
.animate({ top:board.height()-cPos.h}, 500, function() {
$('#score').text(' Score: ' + score);
start();
});
}
Beside that as you can see we are setting our game state to the 'die' mode so we don't check for any clicks
while we are showing the animation, stop all the animations (think parallax scrolling and moving obstacles),
stop the bird timer. After that we can play 'die sequence' and once done move back to the start screen.
Points of Interest
Just as I mentioned in the begining this was an initial version of the game that took me 2 hours to put
together. Added a PhoneGap, threw onto the Galaxy and got completely bummed out: while working just fine on the slowest laptop, it would completely
choke even on the best smart phone, so I had to spend next 2 days trying to improve performance - caching
jQuery obstacles objects, accounting for time between timer executions not being as requested, ... Even with all those improvements, depending on the model of your smartphone performance
may be somewhat disappointing - check it for yourself at:
As far as $50,000/day - that didn't quite materialize - in a last 3 month on the market I made a cool $20 off
that game - or about $1/hr for the time I spend. Once again I attribute my success to the perfect timing and a little bit of luck. Good thing I already had Google developer account (one time $25),
Apple Developer account ($99/year), Microsoft Store ($99/year) and Web Publishing ($49/year) - otherwise
it could of been really expensive excersize. But since I already paid for those - it's a $20 I didn't
have before - the glass is half full!
History