> Canonical URL: https://codingcat.dev/post/flutter-flame-engine-flappy-bird

# How to make a Flappy Bird clone with Flutter and Flame Engine

![](https://i.ytimg.com/vi/y_0ouvhP8V4/sddefault.jpg)[![Alex Patterson](https://cdn.sanity.io/images/hfh83o0w/production/a606dfaa381aab08ddbe16a33fe1d3e1fb37c927-400x400.jpg?w=48&h=48&fit=max&auto=format)Alex Patterson](/author/alex-patterson)[![Lukas (spydon)](https://cdn.sanity.io/images/hfh83o0w/production/b84519ff6dd0fd9180afff97fc2134d353f1213d-2048x2048.jpg?w=48&h=48&fit=max&auto=format)Lukas (spydon)](/author/lukas-spydon)November 21, 2022View TranscriptCopy TranscriptFull TextTimestampsFetching transcript from YouTube...

Transcript not available

No auto-generated captions could be found for this video.

Lukas Klingsbo (spydon) teaches Alex and Brittney how to setup and use Flame to build a Flappy Bird clone. In this tutorial we will walk through everything from repo setup to final game.

## Project Setup

We will be using [VSCode](https://code.visualstudio.com/) for this tutorial, you will also want your favorite terminal to get started. Make sure that you have the Flutter [VSCode Extension](https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter) installed so that you can get support for running [Flutter](https://flutter.dev/). You will also need a git client installed.

## Starting Repo

spydon has provided a starter repo that has everything we will need, it also has the full application incase you get stuck. You can start out on the `dev` branch and build from there.

```
git clone -b dev https://github.com/spydon/flappy_ember.git
```

If you get stuck or want to skip to the end just run `git checkout main`.

## File Structure

All of our code can be found in the `lib` directory. All of our sprites and other images will be found in `assets`.

![](https://cdn.sanity.io/images/hfh83o0w/production/64020e6a02181f45798a28815c5f0fe0e8199c4c-1124x2634.png?rect=0,1001,1124,632&w=1920&h=1080&fit=max&auto=format)

## Dependencies

In Flutter all of your dependencies are stored in `pubspec.yaml`. I would recommend changing from the git version to a published version.

```
-flame:
-    git:
-      url: https://github.com/flame-engine/flame.git
-      ref: main
-      path: packages/flame
+flame: 1.2.1
```

If you Flutter VSCode extension is running correctly you should now see your packages install / update.

## Programming

### main.dart

This is the file that starts your Flutter application. There is only a small amount of Flutter required in your `main.dart` file. The majority of the setup here is providing the `GameWidget` component an instance of our Flame game.

```
import 'package:flame/game.dart';
import 'package:flutter/material.dart';

import 'game.dart';

void main() {
  final game = FlappyEmber();
  runApp(GameWidget(game: game));
}
```

### game.dart

The `FlappyEmber` class is an extension of `FlameGame` which comes form the Flame package. This is essential the main game that we are building, it is where we will import all of the aspects that it takes to create a game like `Player`, `Sky`, `ScreenHitbox` and set all of the variables that are needed to run the game like `speed` and `random`.

? onLoad() async {
    player = Player();
    add(Sky());
    add(ScreenHitbox());
    add(player);
	  return null;
  }

  void gameover() {
    pauseEngine();
  }

  double _timeSinceBox = 0;
  double _boxInterval = 1;
  @override
  void update(double dt) {
    super.update(dt);
    speed += 10 * dt;
    _timeSinceBox += dt;

    if (_timeSinceBox > _boxInterval) {
      add(BoxStack(isBottom: random.nextBool()));
      _timeSinceBox = 0;
    }
  }

  @override
  void onTap() {
    super.onTap();
    player.fly();
  }
}
" aria-label="Copy code">

```
import 'dart:math';

import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flappy_ember/player.dart';

import 'sky.dart';
import 'boxstack.dart';

class FlappyEmber extends FlameGame with TapDetector, HasCollisionDetection {
  late final Player player;
  double speed = 500;
  final random = Random();
  @override
  Future? onLoad() async {
    player = Player();
    add(Sky());
    add(ScreenHitbox());
    add(player);
	  return null;
  }

  void gameover() {
    pauseEngine();
  }

  double _timeSinceBox = 0;
  double _boxInterval = 1;
  @override
  void update(double dt) {
    super.update(dt);
    speed += 10 * dt;
    _timeSinceBox += dt;

    if (_timeSinceBox > _boxInterval) {
      add(BoxStack(isBottom: random.nextBool()));
      _timeSinceBox = 0;
    }
  }

  @override
  void onTap() {
    super.onTap();
    player.fly();
  }
}
```

### player.dart

In the `player.dart` file create a new Class called `Player` this is an extension of `SpriteAnimationComponent` which is a component provided by the flame package for animating our Ember Sprite.

 {
  Player() : super(size: Vector2(100, 100), position: Vector2(100, 100));

  @override
  Future? onLoad() async {
    add(CircleHitbox());

    final image = await Flame.images.load('ember.png');
    animation = SpriteAnimation.fromFrameData(
      image,
      SpriteAnimationData.sequenced(
        amount: 4,
        stepTime: 0.10,
        textureSize: Vector2.all(16),
      ),
    );
  }

  @override
  void onCollisionStart(_, __) {
    super.onCollisionStart(_, __);
    gameRef.gameover();
  }

  @override
  void update(double dt) {
    super.update(dt);

    position.y += 200 * dt;
  }

  void fly() {
    final effect = MoveByEffect(
        Vector2(0, -100),
        EffectController(
          duration: 0.5,
          curve: Curves.decelerate,
        ));

    add(effect);
  }
}
" aria-label="Copy code">

```
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/flame.dart';
import 'package:flutter/material.dart';

import 'game.dart';

class Player extends SpriteAnimationComponent
    with CollisionCallbacks, HasGameRef {
  Player() : super(size: Vector2(100, 100), position: Vector2(100, 100));

  @override
  Future? onLoad() async {
    add(CircleHitbox());

    final image = await Flame.images.load('ember.png');
    animation = SpriteAnimation.fromFrameData(
      image,
      SpriteAnimationData.sequenced(
        amount: 4,
        stepTime: 0.10,
        textureSize: Vector2.all(16),
      ),
    );
  }

  @override
  void onCollisionStart(_, __) {
    super.onCollisionStart(_, __);
    gameRef.gameover();
  }

  @override
  void update(double dt) {
    super.update(dt);

    position.y += 200 * dt;
  }

  void fly() {
    final effect = MoveByEffect(
        Vector2(0, -100),
        EffectController(
          duration: 0.5,
          curve: Curves.decelerate,
        ));

    add(effect);
  }
}
```

### sky.dart

This is a fairly simple component called `Sky` that extends the `SpriteComponent` from Flame. Its only job is to load the parallax background.

? onLoad() async {
    final image = await Flame.images.load('./parallax/bg_sky.png');

    sprite = Sprite(image);
  }

  @override
  void onGameResize(Vector2 size) {
    super.onGameResize(size);

    this.size = size;
  }
}
" aria-label="Copy code">

```
import 'package:flame/components.dart';
import 'package:flame/flame.dart';

class Sky extends SpriteComponent {
  Sky() : super(priority: -1);

  @override
  Future? onLoad() async {
    final image = await Flame.images.load('./parallax/bg_sky.png');

    sprite = Sprite(image);
  }

  @override
  void onGameResize(Vector2 size) {
    super.onGameResize(size);

    this.size = size;
  }
}
```

### box.dart

The `Box` component is equally as easy as `Sky`, with one small difference that we also add a `RectangleHitbox` so that when our ember runs into the box we understand that it has struck the box.

? onLoad() async {
    final image = await Flame.images.load('./boxes/1.png');

    sprite = Sprite(image);

    add(RectangleHitbox());
  }
}
" aria-label="Copy code">

```
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/widgets.dart';
import 'package:flame/flame.dart';

class Box extends SpriteComponent {
  static Vector2 initialSize = Vector2.all(150);
  Box({super.position}) : super(size: initialSize);

  @override
  Future? onLoad() async {
    final image = await Flame.images.load('./boxes/1.png');

    sprite = Sprite(image);

    add(RectangleHitbox());
  }
}
```

### boxstack.dart

The `BoxStack` component extends `PositionComponent` and also uses the game that we created called `FlappyEmber` for a reference. This is massively useful as we can calculate things like `gameHeight` directly from the `gameRef.size.y` of the instance. This allows you to calculate how many boxes can be stacked randomly while still fitting within the boundaries of the game.

 {
  final bool isBottom;
  static final random = Random();

  BoxStack({required this.isBottom});

  @override
  Future? onLoad() async {
    position.x = gameRef.size.x;
    final gameHeight = gameRef.size.y;
    final boxHeight = Box.initialSize.y;
    final maxStackHeight = (gameHeight / boxHeight).floor() - 2;

    final stackHeight = random.nextInt(maxStackHeight + 1);
    final boxSpacing = boxHeight * (2 / 3);
    final initialY = isBottom ? gameHeight - boxHeight : -boxHeight / 3;

    final boxs = List.generate(stackHeight, (index) {
      return Box(
        position:
            Vector2(0, initialY + index * boxSpacing * (isBottom ? -1 : 1)),
      );
    });
    addAll(isBottom ? boxs : boxs.reversed);
  }

  @override
  void update(double dt) {
    super.update(dt);
    if (position.x

```
import 'dart:math';

import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/widgets.dart';
import 'package:flame/flame.dart';

import 'game.dart';
import 'box.dart';

class BoxStack extends PositionComponent with HasGameRef {
  final bool isBottom;
  static final random = Random();

  BoxStack({required this.isBottom});

  @override
  Future? onLoad() async {
    position.x = gameRef.size.x;
    final gameHeight = gameRef.size.y;
    final boxHeight = Box.initialSize.y;
    final maxStackHeight = (gameHeight / boxHeight).floor() - 2;

    final stackHeight = random.nextInt(maxStackHeight + 1);
    final boxSpacing = boxHeight * (2 / 3);
    final initialY = isBottom ? gameHeight - boxHeight : -boxHeight / 3;

    final boxs = List.generate(stackHeight, (index) {
      return Box(
        position:
            Vector2(0, initialY + index * boxSpacing * (isBottom ? -1 : 1)),
      );
    });
    addAll(isBottom ? boxs : boxs.reversed);
  }

  @override
  void update(double dt) {
    super.update(dt);
    if (position.x

## Testing the Game

Throughout this process you most likely will want to debug the game. Once again have the VSCode Flutter plugin this is made super simple. All you need to do is be in any of the the files in the source directory and use the run and debug screen like normal and this will build out your desktop or mobile version of the game.

![](https://cdn.sanity.io/images/hfh83o0w/production/244ef4546b660d784e0e64c5ee514d379152ebe1-1588x1242.png?rect=0,175,1588,893&w=1920&h=1080&fit=max&auto=format)

## Recent Posts

[View More](/blog/page/1)[![Dockerless Serverless GPUs: How I Built a Fast, Cheap AI SaaS with RunPod Flash](https://cdn.sanity.io/images/hfh83o0w/production/78383c8986667bc1eaa504786488d88f212929a8-1280x720.jpg?w=1920&h=1080&q=80&fit=max&auto=format)](/post/dockerless-serverless-gpus-fast-cheap-ai-saas-with-runpod-flash)

### [Dockerless Serverless GPUs: How I Built a Fast, Cheap AI SaaS with RunPod Flash](/post/dockerless-serverless-gpus-fast-cheap-ai-saas-with-runpod-flash)

October 6, 2026

Hosted image APIs cost 3 to 4 cents per generation. Using RunPod Flash for dockerless serverless GPUs, FlashFun.dev drops that to a fraction of a cent, turning $1 into 500 to 3,000 images. Here is the full architecture, the benchmarks, and the code.

[![Alex Patterson](https://cdn.sanity.io/images/hfh83o0w/production/a606dfaa381aab08ddbe16a33fe1d3e1fb37c927-400x400.jpg?w=48&h=48&fit=max&auto=format)Alex Patterson](/author/alex-patterson)[![Why We Are Absolutely Obsessed with Sticker Mule’s Holographic Stickers](https://cdn.sanity.io/images/hfh83o0w/production/6338342f33d07e920431bb2eaa7a1015b5f6ad45-2752x1536.jpg?rect=11,0,2731,1536&w=1920&h=1080&q=80&fit=max&auto=format)](/post/why-we-are-absolutely-obsessed-with-sticker-mule-s-holographic-stickers)

### [Why We Are Absolutely Obsessed with Sticker Mule’s Holographic Stickers](/post/why-we-are-absolutely-obsessed-with-sticker-mule-s-holographic-stickers)

June 23, 2026

From the first unboxing to daily wear on laptops, bottles, and notebooks, these iridescent, ultra-durable stickers turn every surface into a vivid, futuristic showcase for your brand or art.

[![Alex Patterson](https://cdn.sanity.io/images/hfh83o0w/production/a606dfaa381aab08ddbe16a33fe1d3e1fb37c927-400x400.jpg?w=48&h=48&fit=max&auto=format)Alex Patterson](/author/alex-patterson)