Overview β’ Installation β’ Hardware β’ Architecture β’ Documentation β’ Configuration β’ Troubleshooting
A complete Flappy Bird clone implementation for Arduino microcontrollers using the U8g2 graphics library and SH1106 OLED display (128x64 pixels). Features persistent high score tracking, dynamic difficulty scaling, and smooth gameplay.
- Arduino IDE 1.8.x or higher (Download here)
- USB cable (USB-A to USB-B for Uno, or appropriate for your board)
- Git (optional, for cloning method)
Perfect for beginners who just want to use the game
- Visit the Releases page
- Click on the latest release
- Download the flappy_bird_arduino.ino file
- Open Arduino IDE
- Go to Sketch β Include Library β Manage Libraries...
- In the search box, type:
U8g2 - Find U8g2 by olikraus
- Click Install
- Wait for "Installed" confirmation
- In Arduino IDE, go to File β Open
- Navigate to the project folder
- Select
flappy_bird.ino - The code will open in the IDE
- Connect your Arduino to computer via USB
- Select Board: Tools β Board β Arduino AVR Boards β Arduino Uno
- Select Port: Tools β Port β COM3 (Windows) or /dev/ttyUSB0 (Linux) or /dev/cu.usbserial (Mac)
- The port with "(Arduino Uno)" next to it
- Click the Upload button (β icon) or press
Ctrl+U - Wait for compilation to complete
- Look for "Done uploading" message at the bottom
- The game should start immediately!
Perfect for developers who want to modify or contribute
Open terminal/command prompt and run:
# Clone using HTTPS
git clone https://github.com/aydakikio/flappy_bird_arduino.git
# Navigate into project folder
cd flappy_bird_arduino- Open Arduino IDE
- Sketch β Include Library β Manage Libraries...
- Search for
U8g2 - Install U8g2 by olikraus
# Option A: Open from terminal (if arduino-cli installed)
arduino source_code/flappy_bird.ino
# Option B: Open Arduino IDE manually
# File β Open β Navigate to cloned folderβ source_code folder β flappy_bird.ino- Tools β Board β Arduino AVR Boards β Arduino Uno
- Tools β Port β Select your Arduino port
- Click Upload (β) or press
Ctrl+U
After successful upload, you should see:
β
Display powers on - OLED screen lights up
β
Game border appears - Rectangle frame around play area
β
Score shows "Score: 0" - At top left of screen
β
High score shows "Best: 0" - At top right of screen
β
Bird is visible - Small sprite at left side
β
Pipes scroll - Moving from right to left
β
Action button works - Pressing button makes bird jump
β
Reset button works - Holding reset button clears high score
If anything doesn't work, check the Troubleshooting section below.
- Microcontroller: Arduino Uno/Nano or compatible
- Display: SH1106 128x64 OLED (I2C interface)
- Input: 2 push buttons (pull-up configuration)
- Connections:
- I2C SDA β A4
- I2C SCL β A5
- Action Button β Pin 12 (INPUT_PULLUP)
- Reset Button β Pin 4 (INPUT_PULLUP)
The codebase follows a Game Loop Architecture with clear separation of concerns:
βββββββββββββββββββββββββββββββββββββββ
β Main Game Loop β
β βββββββββββββββββββββββββββββββββ β
β β 1. Input Handling β β
β β 2. State Update β β
β β 3. Collision Detection β β
β β 4. Rendering β β
β β 5. High Score Management β β
β βββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββ
- Game operates in two states:
PLAYINGandGAME_OVER - State transitions handled via
game_overboolean flag - Score and difficulty tracked globally
- High score persisted to EEPROM with magic byte validation
Velocity Update: V(t+1) = V(t) + gravity
Position Update: Y(t+1) = Y(t) + V(t+1)
Jump Impulse: V = jumpStrength (negative)Uses U8g2's full buffer system:
clearBuffer()- Clear display buffer- Draw game objects
sendBuffer()- Send buffer to display- Eliminates screen flicker
- EEPROM addresses:
0x00-0x01: High score (2 bytes, int)0x02: Magic byte (validation)
- Magic byte (42) ensures valid initialization
- Automatic save on new high score
βββ Hardware Configuration
β βββ Display driver initialisation
β βββ Pin definitions (action + reset buttons)
β
βββ Game Constants
β βββ Display dimensions
β βββ Physics parameters
β βββ Game object sizes
β βββ EEPROM addresses
β
βββ Data Structures
β βββ Bird (position, velocity, jump)
β βββ Pipe (position, gap, state)
β
βββ Game Logic
β βββ Initialise/Restart
β βββ Input Handling
β βββ Physics Update
β βββ Collision Detection
β βββ Scoring System
β βββ Difficulty Scaling
β βββ High Score Management
β
βββ EEPROM Functions
β βββ Load High Score
β βββ Save High Score
β βββ Reset High Score
β
βββ Rendering
βββ Game Scene (with dual scores)
βββ Game Over Screen (with celebration)
Purpose: Main execution loop
Frequency: ~33 FPS (30ms delay)
Flow:
Check reset button (anytime)
if (game_active):
process_input()
update_physics()
detect_collisions()
render_frame()
else:
show_game_over()
wait_for_restart()
Purpose: Reset all game objects to the starting state
Called: On startup and restart
Operations:
- Bird position: (30, 32)
- Bird velocity: 0
- Pipes: 3 instances with random gaps
- Pipe spacing: 65px Β± 10px variation
Purpose: Hardware initialization and game setup
Operations:
- Initialize U8g2 display
- Configure button pins (INPUT_PULLUP)
- Load high score from EEPROM
- Seed random number generator
- Call
initialize_game()
Purpose: Load high score from EEPROM with validation
Algorithm:
Read magic byte from address 0x02
if (magic != 42):
Initialize EEPROM (high_score = 0, magic = 42)
else:
Load high_score from address 0x00
Safety: Magic byte prevents reading uninitialized EEPROM
Purpose: Write current high score to EEPROM
Trigger: Called immediately when new high score achieved
Address: 0x00 (2 bytes for int)
Purpose: Clear high score and reset EEPROM
Trigger: Reset button (Pin 4) pressed
Operations:
- Set
high_score = 0 - Clear
new_high_scoreflag - Write to EEPROM
Purpose: Apply gravity and update bird position
Physics Model:
Gravity Constant: 1 pixel/frameΒ²
Terminal Velocity: 2 pixels/frame
Jump Strength: -3 pixels/frame
Boundary Conditions:
- Top boundary: Y = 14 (GAME_AREA_TOP + BIRD_SIZE)
- Bottom boundary: Y = 58 (SCREEN_HEIGHT - BIRD_SIZE)
- Collision with boundary β Game Over
Algorithm: Axis-Aligned Bounding Box (AABB)
Bird Hitbox:
Left: bird.x - 7
Right: bird.x + 7
Top: bird.y - 6
Bottom: bird.y + 6
Collision Logic:
- Check horizontal overlap with each pipe
- If overlapping:
- Check if the bird is above the top pipe
- Check if the bird is below the bottom pipe
- Return
trueon any collision
Time Complexity: O(n) where n = NUM_PIPES
Purpose: Move pipes and recycle off-screen pipes
Recycling Algorithm:
for each pipe:
move left by pipeSpeed
if pipe off-screen:
Find the rightmost pipe position
place new pipe with random spacing (35-55px)
randomise gap position (22-50px)
reset passed flagDesign Pattern: Object Pool Pattern (reuses 3 pipe objects)
Trigger: Bird's X position passes pipe's right edge
Implementation:
if (!pipe.passed && pipe.right < bird.x):
pipe.passed = true
score++
if (score > high_score):
high_score = score
new_high_score = true
save_high_score() // Immediate saveState Management:
passedflag prevents double-countingnew_high_scoreflag tracks if current session beat record
Strategy: Speed scaling based on score thresholds
| Score Range | Pipe Speed | Difficulty |
|---|---|---|
| 0-9 | 1 px/frame | β |
| 10-19 | 2 px/frame | ββ |
| 20-29 | 3 px/frame | βββ |
| 30-39 | 4 px/frame | ββββ |
| 40+ | 5 px/frame | βββββ |
Design Note: Linear progression every 10 points maintains balanced difficulty curve
- Format: XBM (X Bitmap)
- Dimensions: 14x12 pixels
- Size: 24 bytes
- Storage: PROGMEM (flash memory)
- Transparency: Supported (0x00 = transparent)
ββββββββββββββββββββββββββββββββββββββ
β Score: XX Best: XX (0-7) β
ββββββββββββββββββββββββββββββββββββββ€
β β
β π¦ β β
β β β
β Game Area β β
β (8-63) β β
β β β
ββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββ
β β
β GAME OVER β
β β
β New high score! (conditional) β
β β
β Score: XX β
β β
β Press any button... β
ββββββββββββββββββββββββββββββββββββββ
- Frame Rate: ~33 FPS
- Buffer Mode: Full buffer (F) for flicker-free display
- Draw Calls per Frame: ~10-15
Global Variables:
βββ Bird struct : 8 bytes
βββ Pipes[3] : 36 bytes (12 bytes Γ 3)
βββ Game state vars : 16 bytes (added high_score flags)
βββ Bird sprite : 0 bytes (moved to PROGMEM)
βββ U8g2 buffer : ~1KB
Total Static RAM: ~1.06 KB
Stack Usage: ~200 bytes
Available (Uno): ~950 bytes free
- Program Size: ~11KB
- U8g2 Library: ~15KB
- Total: ~26KB (fits comfortably on 32KB devices)
Address Map:
0x00-0x01: High score (int, 2 bytes)
0x02: Magic byte (0x2A = 42)
0x03-0xFF: Available for future features
// Make game easier
#define PIPE_GAP_SIZE 30 // Larger gap
int jumpStrength = -4; // Stronger jump
const uint8_t gravity = 1; // Keep same
// Make game harder
#define PIPE_GAP_SIZE 20 // Smaller gap
int jumpStrength = -2; // Weaker jump
const uint8_t gravity = 2; // Faster fall// Slower progression
if (score >= 15) pipeSpeed = 2;
if (score >= 30) pipeSpeed = 3;
// Faster progression
if (score >= 5) pipeSpeed = 2;
if (score >= 10) pipeSpeed = 3;#define PIPE_WIDTH 12 // Wider pipes
int pipeSpacing = random(40, 70); // Varied spacing#define EEPROM_HIGH_SCORE_ADDR 0 // Change storage location
#define EEPROM_MAGIC_VALUE 99 // Custom magic byte- Check: I2C address (default 0x3C)
- Fix: Scan I2C bus, adjust U8G2 constructor
- Test: Run U8g2 GraphicsTest example
- Cause: Missing
delay(30)in loop - Fix: Ensure frame rate limiting is active
- Cause:
passedflag not resetting - Fix: Verify
pipes[i].passed = falsein recycle logic
- Cause: Hitbox size mismatch
- Fix: Adjust collision constants to match sprite size
- Symptom: Random crashes, corrupted display
- Fix: PROGMEM already used for sprites; consider reducing buffer
- Symptom: Score resets to 0 on power cycle
- Possible Causes:
- EEPROM write failure
- Magic byte mismatch
- Debug: Add Serial prints in
load_high_score()andsave_high_score() - Fix: Verify EEPROM library is included
- Cause: Uninitialized EEPROM (first upload)
- Fix: Magic byte check automatically handles this; if persists, manually reset via reset button
- Check: Pin 4 wiring and INPUT_PULLUP configuration
- Test: Add Serial debug in reset button handler
- Fix: Ensure button connected to GND when pressed
- Object Pooling: Reuses 3 pipe objects instead of dynamic allocation
- Full Buffer Mode: Switched from page buffer for smoother rendering
- PROGMEM Usage: Bird sprite stored in flash, saving 24 bytes RAM
- Integer Math: Avoids slow floating-point operations
- Minimal Branching: Simple linear difficulty scaling
- Efficient EEPROM: Only writes on actual high score change
- Compression: Implement simple sprite compression
- Fixed-Point Math: For more complex physics
- Look-Up Tables: For common calculations
- Lazy EEPROM Writes: Batch multiple writes (if needed)
// Constants: UPPER_SNAKE_CASE
#define SCREEN_WIDTH 128
#define EEPROM_HIGH_SCORE_ADDR 0
// Global variables: snake_case or camelCase
int high_score = 0;
bool new_high_score = false;
uint8_t pipeSpeed = 1;
// Functions: PascalCase or snake_case (consistent within project)
void Update_Bird() { }
void draw_game() { }
void load_high_score() { }
// Struct members: camelCase
struct Bird {
int jumpStrength;
};- Use section headers for organisation (===== ... =====)
- Comment "why" not "what"
- Document complex algorithms
- Keep inline comments brief
- Maintain existing code style
- Test thoroughly on hardware
- Document new features in README
- Keep functions under 50 lines
- Avoid external dependencies beyond U8g2 and EEPROM
- Test EEPROM features with power cycles
Consider:
- Memory constraints (2KB RAM on Uno)
- Performance impact (maintain 30+ FPS)
- EEPROM wear leveling (100,000 write cycles typical)
- Hardware compatibility
Include:
- Arduino model
- Display type
- Steps to reproduce
- Expected vs actual behavior
- High score value when bug occurred
- Serial output if available
New Features:
- π Persistent high score saved to EEPROM
- π New high score celebration message
- β Live high score display during gameplay
- π High score reset button (Pin 4)
- π Dual score tracking (current + best)
Code Improvements:
- πΎ PROGMEM optimization for bird sprite
- π§ EEPROM magic byte validation
- π Enhanced code documentation
- π Improved button handling
- Initial release
- Basic Flappy Bird gameplay
- Dynamic difficulty scaling
- Collision detection
- Score tracking (session only)
Developed for Arduino learning and embedded systems education
This project is licensed under the MIT License - see the LICENSE file for details.
- U8g2: Monochrome graphics library by olikraus
- EEPROM: Arduino built-in EEPROM library
Original Flappy Bird by Dong Nguyen (2013)
| Pin | Function | Direction | Notes |
|---|---|---|---|
| D12 | Action Button | INPUT | Pull-up enabled, Jump/Flap |
| D4 | Reset Button | INPUT | Pull-up enabled, Reset high score |
| A4 | I2C SDA | I/O | Display data |
| A5 | I2C SCL | Output | Display clock |
| A0 | Random | Input | Seed for randomSeed() |
Flash (Program Memory):
0x0000 - 0x6800: Application code (~26KB)
0x6800 - 0x8000: Available space
RAM (SRAM):
0x0100 - 0x0500: Global variables (~1KB)
0x0500 - 0x08FF: Stack + heap (~950 bytes free)
EEPROM:
0x00-0x01: High score (2 bytes)
0x02: Magic byte (1 byte)
0x03-0xFF: Available (253 bytes)
// Constructor breakdown
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(
U8G2_R0, // No rotation
-1, // Reset pin (unused)
A5, // Clock pin (SCL)
A4 // Data pin (SDA)
);
// F variant: Full buffer mode (1KB RAM, flicker-free)
// Alternative: _1_ or _2_ for page buffer (less RAM)Power On
β
Load High Score
ββ Magic byte == 42? β YES β Load high_score
ββ Magic byte != 42? β NO β Initialize (0, 42)
β
Game Loop
ββ New high score? β Save immediately
ββ Reset pressed? β Clear and save
β
Power Off (score persists in EEPROM)
# Arduino IDE
1. Install U8g2 library (Library Manager)
2. Install EEPROM library (built-in, no action needed)
3. Select board: Arduino Uno
4. Select port: COM3 (or appropriate)
5. Compile and upload
# First Run
- EEPROM automatically initializes
- High score starts at 0
- Play to set your first record!
# Reset High Score
- Hold reset button (Pin 4)
- Score resets to 0 immediately- Issues: Found a bug? Open an issue
- Discussions: Have questions? Start a discussion
Document Version: 1.3

