├── .vscode └── settings.json ├── assets ├── bird.png ├── road.png ├── column.png ├── image.png └── background.png ├── index.html ├── README.md └── app.js /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveServer.settings.port": 5501 3 | } -------------------------------------------------------------------------------- /assets/bird.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaithMutua-code/flappy-bird/HEAD/assets/bird.png -------------------------------------------------------------------------------- /assets/road.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaithMutua-code/flappy-bird/HEAD/assets/road.png -------------------------------------------------------------------------------- /assets/column.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaithMutua-code/flappy-bird/HEAD/assets/column.png -------------------------------------------------------------------------------- /assets/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaithMutua-code/flappy-bird/HEAD/assets/image.png -------------------------------------------------------------------------------- /assets/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaithMutua-code/flappy-bird/HEAD/assets/background.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Flappy Bird Clone 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flappy Bird Clone 2 | 3 | A simple Flappy Bird clone built with Phaser 3, featuring basic physics, collision detection, and obstacle avoidance gameplay. 4 | 5 |

6 | Flappy Bird Clone Gameplay 7 |
8 | Gameplay screenshot showing bird and columns 9 |

10 | 11 | ## Features 12 | 13 | - Classic Flappy Bird-style gameplay 14 | - Physics-based movement with gravity 15 | - Randomly generated obstacles (columns) 16 | - Collision detection with ground and obstacles 17 | - Win condition after passing all obstacles 18 | - Responsive controls (keyboard only) 19 | - Game state management (start, play, game over, win) 20 | 21 | ## Technologies Used 22 | 23 | - [Phaser 3](https://phaser.io/) - HTML5 game framework 24 | - JavaScript 25 | - HTML5 26 | 27 | ## How to Play 28 | 29 | 1. Press the **UP ARROW** key to start the game 30 | 2. Keep pressing **UP ARROW** to make the bird fly upward 31 | 3. Navigate through the gaps between the columns 32 | 4. Avoid hitting the columns or the ground 33 | 5. Pass all three columns to win! 34 | 35 | ## Installation 36 | 37 | To run the game locally: 38 | 39 | 1. Clone this repository: 40 | ```bash 41 | git clone https://github.com/your-username/flappy-bird-clone.git 42 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | type: Phaser.AUTO, 3 | width: 800, 4 | height: 600, 5 | physics: { 6 | default: 'arcade', 7 | arcade: { 8 | gravity: { y: 300 }, 9 | debug: false // Change to true to visualize hitboxes 10 | } 11 | }, 12 | scene: { 13 | preload: preload, 14 | create: create, 15 | update: update 16 | } 17 | }; 18 | 19 | let game = new Phaser.Game(config); 20 | 21 | // Game variables 22 | let bird; 23 | let hasLanded = false; 24 | let hasBumped = false; 25 | let gameActive = false; 26 | let cursors; 27 | let messagesToPlayer; 28 | let birdSpeed = 100; 29 | let topColumns; 30 | let bottomColumns; 31 | const COLUMN_SPACING = 300; 32 | const GAP_SIZE = 150; 33 | 34 | function preload() { 35 | this.load.image('background', 'assets/background.png'); 36 | this.load.image('road', 'assets/road.png'); 37 | this.load.image('column', 'assets/column.png'); 38 | this.load.spritesheet('bird', 'assets/bird.png', { 39 | frameWidth: 64, 40 | frameHeight: 96 41 | }); 42 | } 43 | 44 | function create() { 45 | // Background 46 | const background = this.add.image(0, 0, 'background').setOrigin(0, 0); 47 | 48 | // Ground (road) 49 | const roads = this.physics.add.staticGroup(); 50 | const road = roads.create(400, 568, 'road').setScale(2).refreshBody(); 51 | 52 | // Columns (obstacles) 53 | topColumns = this.physics.add.staticGroup(); 54 | bottomColumns = this.physics.add.staticGroup(); 55 | 56 | // Generate column pairs with vertical gaps 57 | for (let i = 0; i < 3; i++) { 58 | let x = 400 + i * COLUMN_SPACING; 59 | let gapY = Phaser.Math.Between(150, 400); // vertical center of gap 60 | 61 | // Create top column just above the gap 62 | let top = topColumns.create(x, gapY - GAP_SIZE / 2, 'column'); 63 | top.setOrigin(0.5, 1); // bottom aligned 64 | top.refreshBody(); 65 | 66 | // Create bottom column just below the gap 67 | let bottom = bottomColumns.create(x, gapY + GAP_SIZE / 2, 'column'); 68 | bottom.setOrigin(0.5, 0); // top aligned 69 | bottom.refreshBody(); 70 | } 71 | 72 | // Bird setup 73 | bird = this.physics.add.sprite(100, 300, 'bird').setScale(2); 74 | bird.setBounce(0.2); 75 | bird.setCollideWorldBounds(true); 76 | 77 | // Collisions 78 | this.physics.add.collider(bird, road, () => { 79 | hasLanded = true; 80 | if (gameActive) gameOver(this); 81 | }); 82 | 83 | this.physics.add.collider(bird, topColumns, () => { 84 | hasBumped = true; 85 | if (gameActive) gameOver(this); 86 | }); 87 | 88 | this.physics.add.collider(bird, bottomColumns, () => { 89 | hasBumped = true; 90 | if (gameActive) gameOver(this); 91 | }); 92 | 93 | // Controls 94 | cursors = this.input.keyboard.createCursorKeys(); 95 | 96 | this.input.keyboard.on('keydown-UP', () => { 97 | if (!gameActive && (hasLanded || hasBumped)) { 98 | resetGame(this); 99 | return; 100 | } 101 | 102 | if (!gameActive && !hasLanded && !hasBumped) { 103 | startGame(this); 104 | } 105 | 106 | if (gameActive) { 107 | bird.setVelocityY(-160); 108 | } 109 | }); 110 | 111 | // UI message 112 | messagesToPlayer = this.add.text(0, 0, 'Press UP ARROW to start', { 113 | fontFamily: '"Comic Sans MS", times, serif', 114 | fontSize: "20px", 115 | color: "white", 116 | backgroundColor: "black", 117 | padding: { x: 10, y: 5 } 118 | }); 119 | 120 | Phaser.Display.Align.In.BottomCenter(messagesToPlayer, background, 0, 50); 121 | } 122 | 123 | function update() { 124 | if (gameActive && !hasLanded && !hasBumped) { 125 | bird.setVelocityX(birdSpeed); 126 | 127 | // Win condition 128 | if (bird.x > 400 + (COLUMN_SPACING * 3)) { 129 | gameActive = false; 130 | bird.setVelocityX(0); 131 | messagesToPlayer.text = '🎉 Congratulations! You won! 🎉'; 132 | } 133 | } 134 | } 135 | 136 | function startGame(scene) { 137 | gameActive = true; 138 | bird.setVelocityX(birdSpeed); 139 | messagesToPlayer.text = 'Press UP to fly!\nAvoid the columns!'; 140 | } 141 | 142 | function gameOver(scene) { 143 | gameActive = false; 144 | bird.setVelocityX(0); 145 | messagesToPlayer.text = '💥 Game Over!\nPress UP to try again'; 146 | } 147 | 148 | function resetGame(scene) { 149 | bird.setPosition(100, 300); 150 | bird.setVelocity(0); 151 | hasLanded = false; 152 | hasBumped = false; 153 | startGame(scene); 154 | } 155 | // Reset columns 156 | topColumns.clear(true, true); 157 | bottomColumns.clear(true, true); 158 | 159 | // Regenerate columns 160 | for (let i = 0; i < 3; i++) { 161 | let x = 400 + i * COLUMN_SPACING; 162 | let gapY = Phaser.Math.Between(150, 400); // vertical center of gap 163 | 164 | let top = topColumns.create(x, gapY - GAP_SIZE / 2, 'column'); 165 | top.setOrigin(0.5, 1); 166 | top.refreshBody(); 167 | 168 | let bottom = bottomColumns.create(x, gapY + GAP_SIZE / 2, 'column'); 169 | bottom.setOrigin(0.5, 0); 170 | bottom.refreshBody(); 171 | } --------------------------------------------------------------------------------