├── img
└── sky.png
├── css
└── main.css
├── server
├── server.js
└── package.json
├── index.html
├── README.md
└── js
├── imageLoader.js
├── util.js
├── gameLoop.js
└── game.js
/img/sky.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/augustoclaro/my-flappy-bird/HEAD/img/sky.png
--------------------------------------------------------------------------------
/css/main.css:
--------------------------------------------------------------------------------
1 | *{
2 | margin:0;
3 | padding:0;
4 | }
5 |
6 | #gameCanvas{
7 | border:1px solid black;
8 | }
9 | #game-container{
10 | width:800px;
11 | height:600px;
12 | margin:0 auto;
13 | }
14 |
15 | h2{
16 | text-align:center;
17 | }
18 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | var connect = require('connect');
2 | var serveStatic = require('serve-static');
3 | var path = require('path');
4 | var port = 8080;
5 |
6 | connect().use(serveStatic(path.join(__dirname, '..'))).listen(port, function(){
7 | console.log('Servidor rodando na porta ' + port + '.');
8 | });
9 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "server.js",
6 | "dependencies": {
7 | "connect": "^3.4.0",
8 | "serve-static": "^1.10.0"
9 | },
10 | "devDependencies": {},
11 | "scripts": {
12 | "test": "echo \"Error: no test specified\" && exit 1",
13 | "start": "node server.js"
14 | },
15 | "author": "",
16 | "license": "ISC"
17 | }
18 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Flappy bird game
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #my-flappy-bird
2 | Flappy bird like html 5 and javascript game, for fun and learn.
3 |
4 | All the code is commented and free to use anyway you want.
5 |
6 | #Setup game
7 | Make sure you have node and npm installed.
8 |
9 | After checking out the project, on command-line, navigate to the server folder and run
10 |
11 | ```sh
12 | npm install
13 | ```
14 | After downloading and installing, run the server file:
15 |
16 | ```sh
17 | node server.js
18 | ```
19 |
20 | Now, you can go to your browser and check the game out at http://localhost:8080
21 |
22 | My website article (portuguese): http://augustoclaro.com.br/jogo-inspirado-em-flappy-bird-feito-em-html-5-e-javascript-puro/
23 |
24 | Demo: http://www.augustoclaro.com.br/flappy-game/
25 |
26 | Thanks for reading :D
27 |
--------------------------------------------------------------------------------
/js/imageLoader.js:
--------------------------------------------------------------------------------
1 | var ImageLoader = (function(){
2 | //Image loader helper
3 | var _results = {};
4 | var _loadImages = function(images, cb){
5 | for (var key in images){
6 | //Create new image
7 | var img = new Image();
8 | //Store key on alt attribute
9 | img.alt = key;
10 | img.onload = function(){
11 | //Set the loaded image to the results object using the stored key
12 | _results[this.alt] = this;
13 | //if all images are loaded, execute callback, if exists
14 | if (Object.keys(_results).length === Object.keys(images).length
15 | && typeof cb === 'function')
16 | cb(_results);
17 | };
18 | img.src = images[key];
19 | }
20 | };
21 | return {
22 | loadImages: _loadImages
23 | };
24 | })();
25 |
--------------------------------------------------------------------------------
/js/util.js:
--------------------------------------------------------------------------------
1 | var Util = (function(){
2 | return {
3 | clearCanvas: function(canvas){
4 | //clear canvas helper
5 | var context = canvas.getContext('2d');
6 | context.clearRect(0, 0, canvas.width, canvas.height);
7 | },
8 | transferCanvas: function(canvasFrom, canvasTo){
9 | //transfer data between canvas helper. Used to buffer the image data before showing
10 | var sourceContext = canvasFrom.getContext('2d');
11 | var destContext = canvasTo.getContext('2d');
12 | var imgData = sourceContext.getImageData(0, 0, canvasFrom.width, canvasFrom.height);
13 | destContext.putImageData(imgData, 0, 0, 0, 0, canvasFrom.width, canvasFrom.height);
14 | },
15 | random: function(min, max){
16 | //generate random number between range
17 | return Math.floor(Math.random() * (max - min + 1)) + min;
18 | },
19 | checkBoxCollision: function(box1, box2){
20 | //check collision between boxes
21 | return box1.x < box2.x + box2.w && //onde o player começa menor que onde o item termina - horizontal
22 | box1.x + box1.w > box2.x && //onde o player termina maior que onde o item começa - horizontal
23 | box1.y < box2.y + box2.h && //onde o player começa menor que onde o item termina - vertical
24 | box1.y + box1.h > box2.y; //onde o player termina maior que onde o item começa - vertical
25 | }
26 | };
27 | })();
28 |
--------------------------------------------------------------------------------
/js/gameLoop.js:
--------------------------------------------------------------------------------
1 | var GameLoop = (function(){
2 | //function to verify if given object is a function
3 | var _isFunction = function(obj){ return typeof obj === 'function'; };
4 |
5 | var _gameLoopData = {
6 | //default FPS
7 | FPS: 20,
8 | //initialize some loop vars
9 | lastTime: new Date().getTime(),
10 | currentTime: 0,
11 | interval: 0,
12 | delta: 0,
13 | action: function(){}
14 | };
15 |
16 | var GameLoop = function(o){
17 | //check parameter to accept loop action directly or options to override FPSS
18 | //and set the options
19 | if (_isFunction(o))
20 | _gameLoopData.action = o;
21 | else{
22 | if (_isFunction(o.action)) _gameLoopData.action = o.action;
23 | if (o.FPS) _gameLoopData.FPS = o.FPS;
24 | }
25 | //main loop function
26 | var _mainAction = function(){
27 | //check for paused state
28 | if (_gameLoopData.mode === 'running')
29 | window.requestAnimationFrame(_mainAction);
30 | //initialize current time
31 | _gameLoopData.currentTime = new Date().getTime();
32 | //set time variation between actions
33 | _gameLoopData.delta = _gameLoopData.currentTime - _gameLoopData.lastTime;
34 | //set ms interval
35 | _gameLoopData.interval = 1000 / _gameLoopData.FPS
36 | //ensure enough time has passed to execute loop action
37 | if (_gameLoopData.delta > _gameLoopData.interval){
38 | //run loop action
39 | _gameLoopData.action(_gameLoopData);
40 | //seting last time compensating the time overpassed
41 | _gameLoopData.lastTime = _gameLoopData.currentTime - _gameLoopData.delta % _gameLoopData.interval;
42 | }
43 | };
44 | var _start = function(){
45 | //set mode to running and call main loop
46 | _gameLoopData.mode = 'running';
47 | _mainAction();
48 | return GameLoop;
49 | };
50 | var _pause = function(){
51 | //set mode to pause forcing the loop to stop
52 | _gameLoopData.mode = 'pause';
53 | return GameLoop;
54 | };
55 | return {
56 | start: _start,
57 | pause: _pause
58 | };
59 | };
60 | return GameLoop;
61 | })();
62 |
--------------------------------------------------------------------------------
/js/game.js:
--------------------------------------------------------------------------------
1 | var flapGame = (function(imageLoader, util){
2 | //Main game object
3 | var gameObj = {
4 | //Default player values
5 | defaults: {
6 | player: {
7 | x: 50,
8 | y: 50,
9 | gravity: 17 //gravity is the amount of pixels to sum to player Y every loop
10 | }
11 | },
12 | stage: 'start', //initial stage
13 | log: function(msg){
14 | //Log function. Using h2 to log messages
15 | document.getElementById('msg').innerText = msg;
16 | },
17 | inputData: {
18 | keyPressed: 0, //key code
19 | mouseClick: 0, //1=left click,2=right click
20 | clear: function(){
21 | gameObj.inputData.keyPressed =
22 | gameObj.inputData.mouseClick = 0;
23 | }
24 | },
25 | player: {
26 | //Player object
27 | points: 0,
28 | x: undefined,
29 | y: undefined,
30 | gravity: undefined,
31 | size: {
32 | width: 50,
33 | height: 50
34 | },
35 | renderToCanvas: function(){
36 | var ctx = gameObj.bufferCanvas.getContext('2d');
37 | //Draw bird's body
38 | ctx.fillStyle = 'red';
39 | ctx.fillRect(gameObj.player.x, gameObj.player.y, gameObj.player.size.width, gameObj.player.size.height);
40 | //Draw bird's beak
41 | ctx.fillStyle = 'yellow';
42 | ctx.fillRect(gameObj.player.x + gameObj.player.size.width - 5, gameObj.player.y + 12, 15, 10);
43 | //Draw bird's wing
44 | ctx.beginPath();
45 | ctx.moveTo(gameObj.player.x + 5, gameObj.player.y + 25);
46 | ctx.lineTo(gameObj.player.x + 40, gameObj.player.y + 25);
47 | ctx.lineTo(gameObj.player.x + 22.5, gameObj.player.y + (gameObj.player.wingUp ? 13 : 37));
48 | ctx.fill();
49 | }
50 | },
51 | gamePhysics: {
52 | //game physics config
53 | jumpGravity: 30,
54 | jumpLoops: 10,
55 | treeDistance: 350,
56 | treeSpeed: 10
57 | },
58 | createRandomTreePair: function(){
59 | //create tree pair. the x pos is the
60 | //game width or, if not the first one,
61 | //some pixels after the last one.
62 | //the y pos is a percentage of the canvas height
63 | //that represents the bottom of the upper tree. the
64 | //down tree is calculated through this value
65 | return {
66 | x: gameObj.treePairs.length
67 | ? (gameObj.treePairs[gameObj.treePairs.length - 1].x + gameObj.gamePhysics.treeDistance)
68 | : gameObj.gameSize.width,
69 | y: util.random(20, 60)
70 | };
71 | },
72 | //Game size configs
73 | gameSize: {
74 | width: 800,
75 | height: 600
76 | },
77 | //Init some vars
78 | gameLoop: undefined,
79 | gameCanvas: undefined,
80 | bufferCanvas: undefined,
81 | treePairs: undefined,
82 | //Tree size
83 | treeSize: {
84 | width: 82,
85 | height: 381
86 | },
87 | //Game image resources
88 | resources: {
89 | sky: 'img/sky.png'
90 | },
91 | createCanvas: function(){
92 | //Create canvas from game settings
93 | var el = document.createElement('canvas');
94 | el.setAttribute('width', gameObj.gameSize.width);
95 | el.setAttribute('height', gameObj.gameSize.height);
96 | return el;
97 | },
98 | detectInput: function(){
99 | //Set document event handlers to fill up inputData
100 | document.onkeypress = function(e){
101 | //Set key pressed code
102 | var key = e.keyCode || e.which;
103 | gameObj.inputData.keyPressed = key;
104 | };
105 |
106 | gameObj.gameCanvas.onclick = function(){
107 | //Set mouseClick to left click code
108 | gameObj.inputData.mouseClick = 1;
109 | };
110 | },
111 | startRound: function(){
112 | //Set initial values to new round
113 | gameObj.treePairs = [];
114 | //Set player data
115 | gameObj.player.x = gameObj.defaults.player.x;
116 | gameObj.player.y = gameObj.defaults.player.y;
117 | gameObj.player.gravity = gameObj.defaults.player.gravity;
118 | gameObj.player.points = 0;
119 | //Generate starting trees. ony 5 trees. The other ones will be
120 | //generated as the existing trees disapears
121 | for (var i = 1; i <= 5; i++)
122 | gameObj.treePairs.push(gameObj.createRandomTreePair())
123 | },
124 | init: function(parentElId){
125 | window.onload = function(){
126 | gameObj.log('Starting game and loading objects.');
127 | //Create game and buffer canvas. Set game canvas ID and append to parent elementgameObj
128 | gameObj.gameCanvas = gameObj.createCanvas();
129 | gameObj.bufferCanvas = gameObj.createCanvas();
130 | gameObj.gameCanvas.setAttribute('id', 'gameCanvas');
131 | var parentEl = document.getElementById(parentElId);
132 | parentEl.appendChild(gameObj.gameCanvas);
133 | //Start first round
134 | gameObj.startRound();
135 | //Load resources
136 | imageLoader.loadImages(gameObj.resources, function(loadedResources){
137 | gameObj.resources = loadedResources;
138 | //Start browser event handlers
139 | gameObj.detectInput();
140 | //Fire gameloop
141 | gameObj.startGameLoop();
142 | });
143 | };
144 | },
145 | startGameLoop: function(){
146 | //Start game loop
147 | gameObj.gameLoop = GameLoop(gameObj.loopAction).start();
148 | },
149 | loopAction: function(){
150 | //Update game entries
151 | gameObj.update();
152 | //Render game objects
153 | gameObj.render();
154 | },
155 | update: function(){
156 | //Detect player action through enter key, space bar and mouse left click
157 | var playerAction = gameObj.inputData.keyPressed === 32 ||
158 | gameObj.inputData.keyPressed === 13 ||
159 | gameObj.inputData.mouseClick === 1;
160 |
161 | if (gameObj.stage === 'start'){
162 | //On start stage, just show the message and
163 | //handle player action to start the game
164 | gameObj.log('All Ready! Press space, enter or click the mouse to start!');
165 | if (playerAction)
166 | gameObj.stage = 'play';
167 | }
168 | else if (gameObj.stage === 'pause'){
169 | //On pause stage, just show the message and
170 | //handle player action to resume the game
171 | gameObj.log('Game paused! Press space, enter or click the mouse to resume!');
172 | if (playerAction)
173 | gameObj.stage = 'play';
174 | }
175 | else if (gameObj.stage === 'gameover'){
176 | //On gameover stage, just show the message and
177 | //handle player action to restart the game
178 | gameObj.log('Game over! Press space, enter or click the mouse to play again!');
179 | if (playerAction){
180 | //reset round data
181 | gameObj.startRound();
182 | gameObj.stage = 'play';
183 | }
184 | }
185 | else{
186 | //On play stage, show the pause message,
187 | //handle pause action and all game events
188 | gameObj.log('Press P to pause!');
189 | var _player = gameObj.player;
190 | //check for player action
191 | if (playerAction){
192 | //set negative gravity to make the bird jump up
193 | _player.gravity = gameObj.defaults.player.gravity - gameObj.gamePhysics.jumpGravity;
194 | }else if (gameObj.inputData.keyPressed === 112){
195 | //handle P key to pause the game
196 | gameObj.stage = 'pause';
197 | }
198 | else if(_player.gravity !== gameObj.defaults.player.gravity){
199 | //gradually restore the original gravity
200 | _player.gravity += gameObj.gamePhysics.jumpGravity / gameObj.gamePhysics.jumpLoops;
201 | }
202 | //Apply gravity
203 | _player.y += _player.gravity;
204 | //Wing up and down
205 | gameObj.player.frameCount = gameObj.player.frameCount || 0;
206 | gameObj.player.frameCount = gameObj.player.frameCount === 5 ? 0 : gameObj.player.frameCount + 1;
207 | if (!gameObj.player.frameCount)
208 | gameObj.player.wingUp = !gameObj.player.wingUp;
209 | //Move all tree pairs
210 | for (var i = 0; i < gameObj.treePairs.length; i++){
211 | var treePair = gameObj.treePairs[i];
212 | //Move the tree
213 | treePair.x -= gameObj.gamePhysics.treeSpeed;
214 | //Check if tree is already gone
215 | if (treePair.x < gameObj.treeSize.width * -1){
216 | //Remove tree from array
217 | gameObj.treePairs.splice(i, 1);
218 | //Replace the deleted tree with new one on the end of the quere
219 | gameObj.treePairs.push(gameObj.createRandomTreePair());
220 | //Sum one point to the player
221 | gameObj.player.points++;
222 | }
223 | }
224 | //Check for gameover
225 | if (gameObj.hasLost()){
226 | gameObj.stage = 'gameover';
227 | }
228 | }
229 | //Clear input data
230 | gameObj.inputData.clear();
231 | },
232 | hasLost: function(){
233 | //Check if the bird has hit the ground or if there was any collision with trees
234 | return (gameObj.player.y > (gameObj.gameSize.height - gameObj.player.size.height))
235 | || gameObj.checkTreeCollision();
236 | },
237 | checkTreeCollision: function(){
238 | for (var i = 0; i < gameObj.treePairs.length; i++){
239 | //For every tree, check if the collision data is set
240 | //and if so, if any of them collides with the bird
241 | var treePair = gameObj.treePairs[i];
242 | if (treePair.collisions && treePair.collisions.some(function(item){
243 | return util.checkBoxCollision({
244 | x: gameObj.player.x,
245 | y: gameObj.player.y,
246 | w: gameObj.player.size.width,
247 | h: gameObj.player.size.height
248 | }, item);
249 | })) return true;
250 | }
251 | return false;
252 | },
253 | render: function(){
254 | //Clear buffer canvas
255 | util.clearCanvas(gameObj.bufferCanvas);
256 | //Render background
257 | var ctx = gameObj.bufferCanvas.getContext('2d');
258 | var bgRes = gameObj.resources.sky;
259 | ctx.drawImage(bgRes, 0, 0, bgRes.width, bgRes.height, 0, 0, gameObj.gameSize.width, gameObj.gameSize.height);
260 | //Render all tree pairs
261 | for (var i = 0; i < gameObj.treePairs.length; i++)
262 | gameObj.drawTreePair(gameObj.treePairs[i]);
263 | //Render bird
264 | gameObj.player.renderToCanvas();
265 | //Render points
266 | document.getElementById('pontos').innerText = 'Score: ' + gameObj.player.points + ' point(s).';
267 | //Transfer buffer image data to game canvas
268 | //This avoid the main canvas to blink when updating
269 | util.transferCanvas(gameObj.bufferCanvas, gameObj.gameCanvas);
270 | },
271 | drawTreePair: function(treePair){
272 | var ctx = gameObj.bufferCanvas.getContext('2d');
273 | //Determine the upper tree bottom and de down tree top position
274 | var upCorner = treePair.y * gameObj.gameSize.height / 100;
275 | var downCorner = upCorner + gameObj.player.size.height * 3;
276 | //Calculate the y coord for both trees
277 | var treeDownY = upCorner - gameObj.treeSize.height;
278 | var treeUpY = downCorner;
279 | //Set collision data for this tree pair
280 | treePair.collisions = [{x: treePair.x, y: treeDownY, w: gameObj.treeSize.width, h: gameObj.treeSize.height},
281 | {x: treePair.x, y: treeUpY, w: gameObj.treeSize.width, h: gameObj.treeSize.height}];
282 | //Draw the tree pair rects
283 | ctx.fillStyle = 'brown';
284 | ctx.fillRect(treePair.x, treeDownY, gameObj.treeSize.width, gameObj.treeSize.height);
285 | ctx.fillRect(treePair.x, treeUpY, gameObj.treeSize.width, gameObj.treeSize.height);
286 | }
287 | };
288 |
289 | return gameObj;
290 | })(ImageLoader, Util);
291 |
--------------------------------------------------------------------------------