├── README.md
├── assets
├── birdie.png
├── clouds.png
├── fence.png
├── finger.png
├── flap.wav
├── hurt.wav
├── icon-120.png
├── icon.png
└── score.wav
├── index.html
├── main.js
├── phaser.min.js
└── screenshots.png
/README.md:
--------------------------------------------------------------------------------
1 | # Don't Touch My Birdie
2 |
3 | A clone of the widely-popular game [Flappy Bird](http://en.wikipedia.org/wiki/Flappy_Bird) created using the [Phaser framework](http://phaser.io/).
4 |
5 | [Open in your mobile browser](https://marksteve.com/dtmb)
6 |
7 | 
8 |
9 | ## Note
10 |
11 | This was only tested on an iPhone 5. Expect it to hilariously fail on other devices.
12 |
13 | ## License
14 |
15 | This work is licensed under a [Creative Commons Attribution-NonCommercial 4.0 International License](http://creativecommons.org/licenses/by-nc/4.0/).
16 |
--------------------------------------------------------------------------------
/assets/birdie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marksteve/dtmb/39331cc21daf524a322ff71cb143fb84d98ef782/assets/birdie.png
--------------------------------------------------------------------------------
/assets/clouds.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marksteve/dtmb/39331cc21daf524a322ff71cb143fb84d98ef782/assets/clouds.png
--------------------------------------------------------------------------------
/assets/fence.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marksteve/dtmb/39331cc21daf524a322ff71cb143fb84d98ef782/assets/fence.png
--------------------------------------------------------------------------------
/assets/finger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marksteve/dtmb/39331cc21daf524a322ff71cb143fb84d98ef782/assets/finger.png
--------------------------------------------------------------------------------
/assets/flap.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marksteve/dtmb/39331cc21daf524a322ff71cb143fb84d98ef782/assets/flap.wav
--------------------------------------------------------------------------------
/assets/hurt.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marksteve/dtmb/39331cc21daf524a322ff71cb143fb84d98ef782/assets/hurt.wav
--------------------------------------------------------------------------------
/assets/icon-120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marksteve/dtmb/39331cc21daf524a322ff71cb143fb84d98ef782/assets/icon-120.png
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marksteve/dtmb/39331cc21daf524a322ff71cb143fb84d98ef782/assets/icon.png
--------------------------------------------------------------------------------
/assets/score.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marksteve/dtmb/39331cc21daf524a322ff71cb143fb84d98ef782/assets/score.wav
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | DTMB
5 |
6 |
7 |
8 |
9 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | var DEBUG = false;
2 | var SPEED = 180;
3 | var GRAVITY = 18;
4 | var FLAP = 420;
5 | var SPAWN_RATE = 1 / 1.2;
6 | var OPENING = 144;
7 |
8 | function init(parent) {
9 |
10 | var state = {
11 | preload: preload,
12 | create: create,
13 | update: update,
14 | render: render
15 | };
16 |
17 | var game = new Phaser.Game(
18 | 480,
19 | 700,
20 | Phaser.CANVAS,
21 | parent,
22 | state,
23 | false,
24 | false
25 | );
26 |
27 | function preload() {
28 | var assets = {
29 | spritesheet: {
30 | birdie: ['assets/birdie.png', 48, 24],
31 | clouds: ['assets/clouds.png', 128, 64]
32 | },
33 | image: {
34 | finger: ['assets/finger.png'],
35 | fence: ['assets/fence.png']
36 | },
37 | audio: {
38 | flap: ['assets/flap.wav'],
39 | score: ['assets/score.wav'],
40 | hurt: ['assets/hurt.wav']
41 | }
42 | };
43 | Object.keys(assets).forEach(function(type) {
44 | Object.keys(assets[type]).forEach(function(id) {
45 | game.load[type].apply(game.load, [id].concat(assets[type][id]));
46 | });
47 | });
48 | }
49 |
50 | var gameStarted,
51 | gameOver,
52 | score,
53 | bg,
54 | credits,
55 | clouds,
56 | fingers,
57 | invs,
58 | birdie,
59 | fence,
60 | scoreText,
61 | instText,
62 | gameOverText,
63 | flapSnd,
64 | scoreSnd,
65 | hurtSnd,
66 | fingersTimer,
67 | cloudsTimer,
68 | cobraMode = 0,
69 | gameOvers = 0;
70 |
71 | function create() {
72 | game.stage.scaleMode = Phaser.StageScaleMode.SHOW_ALL;
73 | game.stage.scale.setScreenSize(true);
74 | // Draw bg
75 | bg = game.add.graphics(0, 0);
76 | bg.beginFill(0xDDEEFF, 1);
77 | bg.drawRect(0, 0, game.world.width, game.world.height);
78 | bg.endFill();
79 | // Credits 'yo
80 | credits = game.add.text(
81 | game.world.width / 2,
82 | 10,
83 | 'marksteve.com/dtmb\n@themarksteve',
84 | {
85 | font: '8px "Press Start 2P"',
86 | fill: '#fff',
87 | align: 'center'
88 | }
89 | );
90 | credits.anchor.x = 0.5;
91 | // Add clouds group
92 | clouds = game.add.group();
93 | // Add fingers
94 | fingers = game.add.group();
95 | // Add invisible thingies
96 | invs = game.add.group();
97 | // Add birdie
98 | birdie = game.add.sprite(0, 0, 'birdie');
99 | birdie.anchor.setTo(0.5, 0.5);
100 | birdie.animations.add('fly', [0, 1, 2, 3], 10, true);
101 | birdie.animations.add('cobra', [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], 60, false);
102 | birdie.inputEnabled = true;
103 | birdie.body.collideWorldBounds = true;
104 | birdie.body.gravity.y = GRAVITY;
105 | // Add fence
106 | fence = game.add.tileSprite(0, game.world.height - 32, game.world.width, 32, 'fence');
107 | fence.tileScale.setTo(2, 2);
108 | // Add score text
109 | scoreText = game.add.text(
110 | game.world.width / 2,
111 | game.world.height / 4,
112 | "",
113 | {
114 | font: '16px "Press Start 2P"',
115 | fill: '#fff',
116 | stroke: '#430',
117 | strokeThickness: 4,
118 | align: 'center'
119 | }
120 | );
121 | scoreText.anchor.setTo(0.5, 0.5);
122 | // Add instructions text
123 | instText = game.add.text(
124 | game.world.width / 2,
125 | game.world.height - game.world.height / 4,
126 | "",
127 | {
128 | font: '8px "Press Start 2P"',
129 | fill: '#fff',
130 | stroke: '#430',
131 | strokeThickness: 4,
132 | align: 'center'
133 | }
134 | );
135 | instText.anchor.setTo(0.5, 0.5);
136 | // Add game over text
137 | gameOverText = game.add.text(
138 | game.world.width / 2,
139 | game.world.height / 2,
140 | "",
141 | {
142 | font: '16px "Press Start 2P"',
143 | fill: '#fff',
144 | stroke: '#430',
145 | strokeThickness: 4,
146 | align: 'center'
147 | }
148 | );
149 | gameOverText.anchor.setTo(0.5, 0.5);
150 | gameOverText.scale.setTo(2, 2);
151 | // Add sounds
152 | flapSnd = game.add.audio('flap');
153 | scoreSnd = game.add.audio('score');
154 | hurtSnd = game.add.audio('hurt');
155 | // Add controls
156 | game.input.onDown.add(flap);
157 | game.input.keyboard.addCallbacks(game, onKeyDown, onKeyUp);
158 | // Start clouds timer
159 | cloudsTimer = new Phaser.Timer(game);
160 | cloudsTimer.onEvent.add(spawnCloud);
161 | cloudsTimer.start();
162 | cloudsTimer.add(Math.random());
163 | // RESET!
164 | reset();
165 | }
166 |
167 | function reset() {
168 | gameStarted = false;
169 | gameOver = false;
170 | score = 0;
171 | credits.renderable = true;
172 | scoreText.setText("DON'T\nTOUCH\nMY\nBIRDIE");
173 | instText.setText("TOUCH TO FLAP\nBIRDIE WINGS");
174 | gameOverText.renderable = false;
175 | birdie.body.allowGravity = false;
176 | birdie.angle = 0;
177 | birdie.reset(game.world.width / 4, game.world.height / 2);
178 | birdie.scale.setTo(1.5, 1.5);
179 | birdie.animations.play('fly');
180 | fingers.removeAll();
181 | invs.removeAll();
182 | }
183 |
184 | function start() {
185 | credits.renderable = false;
186 | birdie.body.allowGravity = true;
187 | // SPAWN FINGERS!
188 | fingersTimer = new Phaser.Timer(game);
189 | fingersTimer.onEvent.add(spawnFingers);
190 | fingersTimer.start();
191 | fingersTimer.add(2);
192 | // Show score
193 | scoreText.setText(score);
194 | instText.renderable = false;
195 | // START!
196 | gameStarted = true;
197 | }
198 |
199 | function flap() {
200 | if (!gameStarted) {
201 | start();
202 | }
203 | if (!gameOver) {
204 | birdie.body.velocity.y = -FLAP;
205 | flapSnd.play();
206 | }
207 | }
208 |
209 | function spawnCloud() {
210 | cloudsTimer.stop();
211 |
212 | var cloudY = Math.random() * game.height / 2;
213 | var cloud = clouds.create(
214 | game.width,
215 | cloudY,
216 | 'clouds',
217 | Math.floor(4 * Math.random())
218 | );
219 | var cloudScale = 2 + 2 * Math.random();
220 | cloud.alpha = 2 / cloudScale;
221 | cloud.scale.setTo(cloudScale, cloudScale);
222 | cloud.body.allowGravity = false;
223 | cloud.body.velocity.x = -SPEED / cloudScale;
224 | cloud.anchor.y = 0;
225 |
226 | cloudsTimer.start();
227 | cloudsTimer.add(4 * Math.random());
228 | }
229 |
230 | function o() {
231 | return OPENING + 60 * ((score > 50 ? 50 : 50 - score) / 50);
232 | }
233 |
234 | function spawnFinger(fingerY, flipped) {
235 | var finger = fingers.create(
236 | game.width,
237 | fingerY + (flipped ? -o() : o()) / 2,
238 | 'finger'
239 | );
240 | finger.body.allowGravity = false;
241 |
242 | // Flip finger! *GASP*
243 | finger.scale.setTo(1.5, flipped ? -1.5 : 1.5);
244 | finger.body.offset.y = flipped ? -finger.body.height * 1.5 : 0;
245 |
246 | // Move to the left
247 | finger.body.velocity.x = -SPEED;
248 |
249 | return finger;
250 | }
251 |
252 | function spawnFingers() {
253 | fingersTimer.stop();
254 |
255 | var fingerY = ((game.height - 16 - o() / 2) / 2) + (Math.random() > 0.5 ? -1 : 1) * Math.random() * game.height / 6;
256 | // Bottom finger
257 | var botFinger = spawnFinger(fingerY);
258 | // Top finger (flipped)
259 | var topFinger = spawnFinger(fingerY, true);
260 |
261 | // Add invisible thingy
262 | var inv = invs.create(topFinger.x + topFinger.width, 0);
263 | inv.width = 2;
264 | inv.height = game.world.height;
265 | inv.body.allowGravity = false;
266 | inv.body.velocity.x = -SPEED;
267 |
268 | fingersTimer.start();
269 | fingersTimer.add(1 / SPAWN_RATE);
270 | }
271 |
272 | function addScore(_, inv) {
273 | invs.remove(inv);
274 | score += 1;
275 | scoreText.setText(score);
276 | scoreSnd.play();
277 | }
278 |
279 | function setGameOver() {
280 | gameOver = true;
281 | instText.setText("TOUCH BIRDIE\nTO TRY AGAIN");
282 | instText.renderable = true;
283 | var hiscore = window.localStorage.getItem('hiscore');
284 | hiscore = hiscore ? hiscore : score;
285 | hiscore = score > parseInt(hiscore, 10) ? score : hiscore;
286 | window.localStorage.setItem('hiscore', hiscore);
287 | gameOverText.setText("GAME OVER");
288 | gameOverText.renderable = true;
289 | // Stop all fingers
290 | fingers.forEachAlive(function(finger) {
291 | finger.body.velocity.x = 0;
292 | });
293 | invs.forEach(function(inv) {
294 | inv.body.velocity.x = 0;
295 | });
296 | // Stop spawning fingers
297 | fingersTimer.stop();
298 | // Make birdie reset the game
299 | birdie.events.onInputDown.addOnce(reset);
300 | hurtSnd.play();
301 | gameOvers++;
302 | }
303 |
304 | function update() {
305 | if (gameStarted) {
306 | // Make birdie dive
307 | var dvy = FLAP + birdie.body.velocity.y;
308 | birdie.angle = (90 * dvy / FLAP) - 180;
309 | if (birdie.angle < -30) {
310 | birdie.angle = -30;
311 | }
312 | if (
313 | gameOver ||
314 | birdie.angle > 90 ||
315 | birdie.angle < -90
316 | ) {
317 | birdie.angle = 90;
318 | birdie.animations.stop();
319 | birdie.frame = 3;
320 | } else {
321 | birdie.animations.play(cobraMode > 0 ? 'cobra' : 'fly');
322 | }
323 | // Birdie is DEAD!
324 | if (gameOver) {
325 | if (birdie.scale.x < 4) {
326 | birdie.scale.setTo(
327 | birdie.scale.x * 1.2,
328 | birdie.scale.y * 1.2
329 | );
330 | }
331 | // Shake game over text
332 | gameOverText.angle = Math.random() * 5 * Math.cos(game.time.now / 100);
333 | } else {
334 | // Check game over
335 | if (cobraMode < 1) {
336 | game.physics.overlap(birdie, fingers, setGameOver);
337 | if (!gameOver && birdie.body.bottom >= game.world.bounds.bottom) {
338 | setGameOver();
339 | }
340 | }
341 | // Add score
342 | game.physics.overlap(birdie, invs, addScore);
343 | }
344 | // Remove offscreen fingers
345 | fingers.forEachAlive(function(finger) {
346 | if (finger.x + finger.width < game.world.bounds.left) {
347 | finger.kill();
348 | }
349 | });
350 | // Update finger timer
351 | fingersTimer.update();
352 | } else {
353 | birdie.y = (game.world.height / 2) + 8 * Math.cos(game.time.now / 200);
354 | }
355 | if (!gameStarted || gameOver) {
356 | // Shake instructions text
357 | instText.scale.setTo(
358 | 2 + 0.1 * Math.sin(game.time.now / 100),
359 | 2 + 0.1 * Math.cos(game.time.now / 100)
360 | );
361 | }
362 | // Shake score text
363 | scoreText.scale.setTo(
364 | 2 + 0.1 * Math.cos(game.time.now / 100),
365 | 2 + 0.1 * Math.sin(game.time.now / 100)
366 | );
367 | // Update clouds timer
368 | cloudsTimer.update();
369 | // Remove offscreen clouds
370 | clouds.forEachAlive(function(cloud) {
371 | if (cloud.x + cloud.width < game.world.bounds.left) {
372 | cloud.kill();
373 | }
374 | });
375 | // Scroll fence
376 | if (!gameOver) {
377 | fence.tilePosition.x -= game.time.physicsElapsed * SPEED / 2;
378 | }
379 | // Decrease cobra mode
380 | cobraMode -= game.time.physicsElapsed * SPEED * 5;
381 | }
382 |
383 | function render() {
384 | if (DEBUG) {
385 | game.debug.renderSpriteBody(birdie);
386 | fingers.forEachAlive(function(finger) {
387 | game.debug.renderSpriteBody(finger);
388 | });
389 | invs.forEach(function(inv) {
390 | game.debug.renderSpriteBody(inv);
391 | });
392 | }
393 | }
394 |
395 | function onKeyDown(e) { }
396 |
397 | var pressTime = 0;
398 | function onKeyUp(e) {
399 | if (Phaser.Keyboard.SPACEBAR == e.keyCode) {
400 | if (game.time.now - pressTime < 200) {
401 | cobraMode = 1000;
402 | } else {
403 | flap();
404 | }
405 | pressTime = game.time.now;
406 | }
407 | }
408 |
409 | };
410 |
--------------------------------------------------------------------------------
/screenshots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marksteve/dtmb/39331cc21daf524a322ff71cb143fb84d98ef782/screenshots.png
--------------------------------------------------------------------------------