Guide

How to make a Flappy Bird game on your phone

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.

What you need before you start Max2D installed (free on Google Play), and eight images: three bird frames, a pipe, a ground tile, a background, a "game over" banner, and a play button. Any sprite pack works. If you plan to publish, use your own art and your own title — a clone using the original Flappy Bird name or art can't go on the store.

The five ideas you need first

Max2D has exactly five concepts. Learn these and everything else is just placing things:

IdeaWhat it is
ProjectThe whole game. Holds your scenes, images and sounds.
SceneOne screen. This game has two: scene1 for play, gameover for the end screen.
Game objectOne thing in a scene — the bird, a pipe, a piece of ground, a text label.
ComponentA capability you bolt on: Sprite, Rigidbody, Collider, Text, Sprite Animation.
ScriptThe 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.

NaN is not an error Empty number fields show NaN. That means "leave this property alone." If a Move block sets only linear y, whatever horizontal speed the object already had is untouched. This one behaviour is what makes Flappy Bird's flight work, as you'll see in a moment.

Step 1: Create the project

  1. Start a new projectOpen Max2D, go to the Create tab and make a new project called Flappy Bird.
  2. 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.
  3. 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:

FieldValueWhy
Body typedynamicGravity and collisions affect it
Gravity scale100The default fall is far too floaty for this game
Density1Standard
Friction0No sliding drag against pipes
Bounciness0Hitting 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.

  1. Add an On Play blockThis green event block runs every single frame.
  2. Add a Move (Velocity) blockSet linear x to 105. Leave linear y, angular and angle limit as NaN.
  3. 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.

  1. Add a When Screen Touched blockSet the event to Touch Down and the location to Whole.
  2. 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.
  3. Add a Wait (Timer) blockSet seconds to 0.368 and cancel to true.
  4. 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:

xTop pipe yBottom pipe y
370−230230
520−257190
660−35090
790−270190
940−270190
1090−200240
1220−190240
1350−230210
1480−280160
1610−350100
1730−320130
1850−340100
1980−320140
2110−220230
2240−340120

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.

This is a finite level, and that's fine Fifteen gates, then the level ends. It's the easiest version to build, it's what the finished game does, and it's more than enough to be fun. Endless pipe spawning is a good second project — see the end of this guide.

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:

  1. On PlayRuns every frame.
  2. Is Colliding WithSet the object to bird.
  3. Set Variable, on the blue true portSet scope to global first, then variablescore, 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.
  4. 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:

  1. Is Colliding With — groundConnect its green arrow onward to the next check, so both run every frame.
  2. Is Colliding With — pipeConnect its green arrow onward to the Set Camera block from step 6.
  3. 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:

  1. Save ValueFilename score.txt, variable score.
  2. If (True/False), on the async portExpression score > highscore.
  3. Save Value on the true branchFilename highscore.txt, variable score.
  4. 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 LoadsLoad 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

SymptomCause
Score jumps by 50 at onceMissing Destroy Object after Set Variable
Invisible wall in the gapThe sensor's "is sensor" switch is off
Bird never diesObject name typo — must be exactly pipe and ground
Game over always shows 0Set 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 runMissing the When Object Loads → Load Value chain on the bird
Bird falls too slowlyRigidbody gravity scale isn't 100
Play button does nothingNo collider on the button
Bird spins foreverAngle 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.

Free onGoogle Play