Flappy Bird is the best first game you can build, because it contains a little of everything: physics, touch input, collision, scoring, saved data, and a second screen. This guide walks the whole build in Max2D, a free Android app — no coding at any step. Every number here comes from a finished, working game, not from theory. By the end you'll have a bird that flaps, pipes that kill you, a score that counts, a high score that survives closing the app, and a game over screen with a play button.
The five ideas you need first
Max2D has exactly five concepts. Learn these and everything else is just placing things:
| Idea | What it is |
|---|---|
| Project | The whole game. Holds your scenes, images and sounds. |
| Scene | One screen. This game has two: scene1 for play, gameover for the end screen. |
| Game object | One thing in a scene — the bird, a pipe, a piece of ground, a text label. |
| Component | A capability you bolt on: Sprite, Rigidbody, Collider, Text, Sprite Animation. |
| Script | The visual program attached to one object, made of connected blocks. |
Scripts are a canvas of blocks in three colours. Green blocks are events and start a chain: On Play runs every frame, When Screen Touched runs on a tap, When Object Loads runs once. Orange blocks are controls that branch or wait. Accent blocks are actions that actually do things.
Each block has ports on its right edge: a green arrow for "do this next", blue true and red false for branching, and async for "carry on when this finishes" — used by timers and by saving or loading files. One script can hold several separate chains that all run independently. The bird ends up with three.
Step 1: Create the project
- Start a new projectOpen Max2D, go to the Create tab and make a new project called Flappy Bird.
- Set the project settingsOpen project settings from the gear icon and set the starting scene to scene1, the orientation to landscape, and the grid spacing to 8. Grid snapping matters more than it sounds — you're about to place 30 pipes by hand.
- Import your eight imagesOpen the assets panel and import the background, the three bird frames, the pipe, the ground tile, the game over banner and the play button. Nothing later works without them.
Step 2: The sky
The camera follows the bird, so one background isn't enough. Add a game object, name it background, give it a Sprite component with your sky image, and place it at x = −280, y = 10. Duplicate it twice and set the copies to x = 0 and x = 280.
Give the sky no collider and no rigidbody — it's decoration. In the camera settings, set the scene background colour to a blue-grey (#607D8B); that's what shows past the edges of your sky tiles.
Step 3: The bird
Add a game object and name it exactly bird. The name is not cosmetic — other objects look it up by this exact string. Set its position to x = 16, y = 0 and its scale to 1.3 on both axes.
The flapping animation
Add a Sprite Animation component and add your three bird frames in order: up-flap, mid-flap, down-flap. Set the interval to 0.193 seconds. Then, in the Sprite component, choose this animation instead of a still image.
The physics
Add a Rigidbody component:
| Field | Value | Why |
|---|---|---|
| Body type | dynamic | Gravity and collisions affect it |
| Gravity scale | 100 | The default fall is far too floaty for this game |
| Density | 1 | Standard |
| Friction | 0 | No sliding drag against pipes |
| Bounciness | 0 | Hitting a pipe should end the run, not bounce |
Then add a Circle Collider with a radius of 16. A circle beats a box here: the bird tilts as it flaps, and a tilted box collider clips pipe corners in ways that feel unfair.
Test now. Press play. The bird should drop off the bottom of the screen. If it doesn't fall, the body type isn't set to dynamic.
Step 4: Make the bird fly forward
Select the bird and open its script. Add blocks with the purple + button.
- Add an On Play blockThis green event block runs every single frame.
- Add a Move (Velocity) blockSet linear x to 105. Leave linear y, angular and angle limit as NaN.
- Connect themDrag from On Play's green arrow to the Move block's left arrow.
Leaving linear y empty is the entire trick. Forward speed is re-applied every frame, while the vertical speed — gravity pulling down, flaps pushing up — is left completely alone. Set both and the bird would ignore gravity.
Step 5: The flap
This is a second, separate chain on the same canvas. Don't connect it to On Play.
- Add a When Screen Touched blockSet the event to Touch Down and the location to Whole.
- Add a Move (Velocity) block after itSet linear y to 303, angular to 9.33 and angle limit to −0.397. Leave linear x as NaN. That's the upward kick plus a nose-up tilt that stops at a fixed angle instead of spinning forever.
- Add a Wait (Timer) blockSet seconds to 0.368 and cancel to true.
- Add a third Move (Velocity) block on the timer's async arrowSet angular to −5.467 and angle limit to 0.151. Leave both linear fields NaN. This tips the bird's nose back down as it falls.
Setting cancel to true is the difference between a polished flap and a broken one. If the player taps again before the timer ends, the old timer is discarded instead of both firing. Without it, fast tapping makes the rotation stutter and fight itself.
Test now. Tap the screen. The bird should hop, tilt up, then rotate downward as it falls.
Step 6: Make the camera follow
Back on the On Play chain, add a Set Camera block. Instead of numbers, use expressions: set pos x to position_x and pos y to position_y / 1.5, with smooth at 1.63.
Expression fields accept variable names and maths. Dividing the y position by 1.5 means the camera only partly follows the bird's height, so the view stays calmer than the bird does, and smooth adds lag so the camera glides rather than snapping. Those two small choices are most of the difference between a camera that feels professional and one that makes players seasick.
Step 7: The pipes
Build one pipe, then duplicate. Name it exactly pipe — the death check searches for that string.
Give it a Sprite with your pipe image, a Rigidbody set to kinematic, and a Box Collider matching the sprite (52 × 320 for the classic pipe art). Place it at x = 370, y = 230.
Kinematic means it collides with things but nothing pushes it and gravity ignores it — exactly right for level geometry that must never fall over.
Now duplicate it for the top pipe: same x, y = −230, and set the angle to 3.1416 (π) so it hangs upside down. The gap between the two is what the bird flies through.
Duplicate that pair 14 more times. These are the x positions and gap heights from the finished game:
| x | Top pipe y | Bottom pipe y |
|---|---|---|
| 370 | −230 | 230 |
| 520 | −257 | 190 |
| 660 | −350 | 90 |
| 790 | −270 | 190 |
| 940 | −270 | 190 |
| 1090 | −200 | 240 |
| 1220 | −190 | 240 |
| 1350 | −230 | 210 |
| 1480 | −280 | 160 |
| 1610 | −350 | 100 |
| 1730 | −320 | 130 |
| 1850 | −340 | 100 |
| 1980 | −320 | 140 |
| 2110 | −220 | 230 |
| 2240 | −340 | 120 |
Notice the rhythm: the gaps wander up and down rather than drifting in one direction, and the tightest pairs come after easier ones. That's deliberate — a difficulty curve you can feel but not predict.
Finally, select all 30 pipes and put them in a folder named pipes. With 65 objects in this scene, the object list is unusable without folders.
Step 8: The ground
Add an object named exactly ground, give it your ground sprite, a static Rigidbody, and a Box Collider matching the sprite (336 × 112 for the classic art). Place it at x = −240, y = 210.
Static is the cheapest body type: it never moves and never reacts. Use it for anything that just sits there.
Duplicate it eight times along x — roughly one sprite width apart, so the tiles touch with no seam — keeping y at 210 throughout: −240, 96, 431, 766, 1101, 1436, 1772, 2107, 2442.
Test now. The bird should land on the ground instead of falling forever.
Step 9: Scoring
Scoring needs three pieces: a variable, an invisible sensor in each gap, and a number on screen.
The variables
In the scene's variables panel, add two global number variables: score and highscore, both starting at 0. Turn on "show debug" for score while you build so you can watch it change during a test run.
Create these before you write the sensor script — the Set Variable block picks its target from a dropdown, and an empty list means nothing to pick.
The score sensor
Tap + → Empty and name it scoresensor. Empty objects carry no image at all — that's the type to reach for whenever something exists only to detect or trigger. Give it a static Rigidbody with is sensor switched on, and a Box Collider about 50 wide and 106 tall. Place it at x = 370, y = 0, centred in the first gap.
The is sensor switch is the critical setting. A sensor reports collisions but blocks nothing. Leave it off and the bird slams into an invisible wall in every single gap.
Now its script, which is only four blocks:
- On PlayRuns every frame.
- Is Colliding WithSet the object to bird.
- Set Variable, on the blue true portSet scope to global first, then variable → score, then open the num value dropdown, choose Expression solver and enter
score + 1. Scope has to come first: changing it clears the variable you picked, because the list is filtered by scope. - Destroy ObjectConnected after Set Variable. No fields to set.
That last block is not optional. On Play runs every frame, so without the destroy, a sensor overlapping the bird keeps scoring for as long as they touch — you'd gain dozens of points per gap. Destroying it means each gate can only ever score once.
Duplicate the sensor for all 15 gates, matching each pipe pair's x and centring it in that gap: (370, 0), (520, −40), (660, −130), (790, −40), (940, −50), (1090, 20), (1220, 30), (1350, −10), (1480, −60), (1610, −130), (1730, −90), (1850, −120), (1980, −90), (2110, 10), (2240, −110). Put them all in a folder called scoresensors.
The number on screen
Add a Text object (+ → Text) named txtscore with the text "0" at a large font size, in a dark colour, positioned near the top of the view. Give it a two-block script: On Play connected to Set Text. In Set Text, open the text field's dropdown, pick Expression solver and enter score — typed as plain text it would just display the word "score".
Now duplicate that label, colour the copy a pale yellow, and offset it about three pixels. Two labels, the dark one behind: that's your drop shadow. Max2D text has no shadow option, so you fake it — and the result reads far better against a busy background.
Test now. Fly through a gap. The number should tick up by exactly one.
Step 10: Death, saving, and the high score
Back on the bird's On Play chain, after the Move block, add two checks in sequence:
- Is Colliding With — groundConnect its green arrow onward to the next check, so both run every frame.
- Is Colliding With — pipeConnect its green arrow onward to the Set Camera block from step 6.
- Wire both true ports into one death chainBoth blue true ports feed the same sequence below. Death is death, whichever hit it.
The death chain itself:
- Save ValueFilename score.txt, variable score.
- If (True/False), on the async portExpression
score > highscore. - Save Value on the true branchFilename highscore.txt, variable score.
- Load SceneScene gameover. Wire the false branch here directly, and the high-score save's async port here too.
Read that out loud: on death, save the score; if it beat the record, save it as the new record; either way, go to the game over screen. Values saved this way survive closing the app — that's what makes a high score feel real.
One more chain on the bird, unconnected to the others: When Object Loads connected to Load Value reading highscore.txt into highscore. Skip this and the record is zero at the start of every run, so every score looks like a new best.
Step 11: The game over screen
Create a second scene named exactly gameover, then build these objects:
- Three sky sprites, same as before.
- Your "game over" banner, near the top.
- Two labels reading "score:" and "highscore:".
- A text object named txtscore: When Object Loads → Load Value (score.txt into score) → on its async port → Set Text whose text field uses Expression solver with
score. - A text object named txthighscore: the same three blocks, reading highscore.txt into highscore.
- Your play button sprite, scaled down, with a Box Collider and a two-block script: When Object Touched (Touch Down) → Load Scene back to scene1.
Two details decide whether this screen works. First, the async port on Load Value is required: reading a file isn't instant, and on the green arrow the label would draw before the value arrived and always show zero. Second, the play button needs that collider — a touch block has no idea where an object is without one, so a button without a collider silently ignores every tap.
One more thing: when you scale a sprite down, its collider doesn't scale with it. Size the collider yourself to match what the player sees.
Step 12: Test the whole loop
Press play and check every one of these:
- The bird falls, and flies right on its own.
- Tapping makes it hop and tilt up, then nose down.
- The camera follows, gently.
- Passing a gap adds exactly one point.
- Hitting a pipe ends the run. So does hitting the ground.
- The game over screen shows the score you just got.
- The play button returns you to the game.
- Beat your record, die, replay — the high score is still there.
Troubleshooting: the eight things that go wrong
| Symptom | Cause |
|---|---|
| Score jumps by 50 at once | Missing Destroy Object after Set Variable |
| Invisible wall in the gap | The sensor's "is sensor" switch is off |
| Bird never dies | Object name typo — must be exactly pipe and ground |
| Game over always shows 0 | Set Text wired to the green arrow instead of async |
| Label shows the word "score" | Set Text got plain text instead of an Expression solver value |
| High score resets each run | Missing the When Object Loads → Load Value chain on the bird |
| Bird falls too slowly | Rigidbody gravity scale isn't 100 |
| Play button does nothing | No collider on the button |
| Bird spins forever | Angle limit left as NaN on a Move block that sets angular |
Where to take it next
You now have a complete game loop, which means every addition is small:
- Endless pipes. Instead of 15 hand-placed pairs, use a Create Object block on a Repeat (Timer) to spawn a pair every couple of seconds, and destroy the ones that fall behind the camera.
- Sound. A Play Sound block on the flap chain and another on the death chain. Two blocks, enormous difference.
- A difficulty ramp. Make forward speed an expression like
105 + score * 2, so the game accelerates as you succeed. - Medals. On the game over screen, use If (True/False) on the score to pick a medal sprite.
- Your own game. The pattern you just learned — a sensor that scores and destroys itself, a save/load pair for persistence, an event chain per input — is most of what any 2D game needs.
If you'd rather start from something playable and tune it, the app can also generate a draft game from a written prompt; see how to make a game with AI. For other genres, try the platformer guide or the clicker guide, and if you're brand new to all of this, start with making a game without coding.
Frequently asked questions
Can I make a Flappy Bird game without coding?
Yes — the entire game above is built by placing objects and connecting visual blocks. You add components like Rigidbody and Collider from a menu, then wire blocks such as When Screen Touched and Move (Velocity) together on a canvas. There is no code at any step.
How long does it take to build?
About 60 to 90 minutes the first time, and roughly 20 minutes once you know the editor. The bird, the flap and the death logic are about a third of that; most of the rest is placing pipes and duplicating score sensors.
Why does my score jump by 50 points at once?
The score sensor is still there after it scores. On Play runs every frame, so a sensor overlapping the bird keeps adding points until they stop touching. Connect a Destroy Object block straight after Set Variable so each gap scores exactly once.
How do I save a high score between sessions?
Use Save Value to write the score to a file such as highscore.txt when the player dies, and Load Value on a When Object Loads event to read it back at the start. Values saved this way survive closing the app.
Can I publish my version to Google Play?
Yes. Max2D exports an APK or AAB, the formats Google Play requires, and you publish under your own developer account. You can share it with the Max2D community first for feedback. Use your own art and title — a clone using the original name or art can't be published.
Build it tonight, on the phone in your hand
No PC, no code, no licence. Free on Google Play.