Making Games with Flutter - Getting started using flame

Sandro Maglione

Sandro Maglione

Games

Learn how to develop games in Flutter using the flame package:

  • How to create a flutter app using flame
  • How a flame game works
  • How to implement your first game with examples

Project setup

Create a normal Flutter project by running flutter create:

flutter create my_first_game

Add flame in pubspec.yaml:

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
 
  flame: ^1.12.0

Create a new assets folder in the root of the project for images, audio, fonts, and any other asset used in your project:

flame uses the assets folder to load assets for the gameflame uses the assets folder to load assets for the game

First example: Add player in the game

The entry point of every flame game is a class that extends FlameGame:

class MyGame extends FlameGame with SingleGameInstance {
  @override
  FutureOr<void> onLoad() {
    super.onLoad();
  }
 
  @override
  void update(double dt) {
    super.update(dt);
  }
}

Every game in most engines relies on 2 methods:

  • onLoad: Called 1 time when the component is created to initialize state, load assets, and add components to the game
  • update: Called each frame to update the current state, move the player, react to events

In flame every element in the game is represented by a class that extends an instance of Component

We create a new Player class that extends SpriteComponent:

  • Define the size of the player in the constructor (size: Vector2.all(50.0))
  • Implement the onLoad function to initialize the player
  • Use with HasGameRef to access a reference to the game (gameRef). Using gameRef we can load a sprite for the player (from the assets/images folder) and place it at the center of the screen using position
player.dart
import 'package:flame/components.dart';
 
class Player extends SpriteComponent with HasGameRef {
  Player() : super(size: Vector2.all(50.0));
 
  @override
  Future<void> onLoad() async {
    super.onLoad();
    sprite = await gameRef.loadSprite('logo.png');
    position = gameRef.size / 2;
  }
}

Remember to add logo.png inside assets/images and declare it inside pubspec.yaml:

All game assets must be included inside the assets folderAll game assets must be included inside the assets folder

pubspec.yaml
flutter:
  uses-material-design: true
  assets:
    - assets/images/logo.png

Let's create an instance of Player and add it to the game inside the onLoad method of FlameGame:

final Player _player = Player();
 
class MyGame extends FlameGame with SingleGameInstance {
  @override
  FutureOr<void> onLoad() {
    add(_player);
  }
}

Finally, we add MyGame to our flutter app using GameWidget to render an instance of FlameGame like a normal flutter widget:

main.dart
void main() {
  runApp(
    GameWidget(
      game: MyGame(),
    ),
  );
}

This is it! Now lunch the game and you will see your player standing in the center of the screen.

There is more.

Every week I build a new open source project, with a new language or library, and teach you how I did it, what I learned, and how you can do the same. Join me and other 700+ readers.

Anatomy of a game with flame

A game with flame starts with an entry class that extends FlameGame (or Game for low-level API control).

A game is a composition of Component, similar to how a Flutter is built by composing Widget.

Like a Widget, every Component has its set of properties that define how to render it. For example every PositionComponent has position, size, anchor, scale, paint, and more.

You build the game by adding components using the add method:

class MyGame extends FlameGame {
  @override
  FutureOr<void> onLoad() {
    add(PositionComponent(position: Vector2(10, 10)));
  }
}

You can either add a component directly (as in the example above), or you can create an instance of a component to add more properties to it:

class MyGame extends FlameGame {
  @override
  FutureOr<void> onLoad() {
    add(MyComponent(100));
  }
}
 
class MyComponent extends PositionComponent {
  double health;
  MyComponent(this.health);
}

You then compose components just like widgets. You can override the onLoad method also inside MyComponent and add another component inside it:

class MyGame extends FlameGame {
  @override
  FutureOr<void> onLoad() {
    add(MyComponent(100));
  }
}
 
class MyComponent extends PositionComponent {
  double health;
  MyComponent(this.health);
 
  @override
  FutureOr<void> onLoad() {
    add(PositionComponent(position: Vector2(10, 10)));
  }
}

Add controls using mixins

flame uses mixins to add controls to components:

class MyComponent extends PositionComponent with CollisionCallbacks {
  double health;
  MyComponent(this.health);
 
  @override
  void onCollisionStart(Set<Vector2> intersectionPoints, PositionComponent other) {
    /// Detect collisions with other components in the game here ☝️
  }
}

By adding CollisionCallbacks our component now has access to a onCollisionStart method.

This method will be called every time our component collides with another component PositionComponent (other in the example).

πŸ’‘ Tip: You can visit the Flame API and take a look at the Mixins sections to learn about new features to add to your components.

For example this is the Mixins API for collisions and this is the Mixins API for events πŸ‘ˆ

Custom World and CameraComponent

A World is the entry component from where all other components originate.

A World is renderer using a CameraComponent to "look" at the game:

FlameGame has one World (called world) which is added by default and paired together with the default CameraComponent (called camera)

It is common practice to define our own instance of World and CameraComponent:

class CustomGame extends FlameGame {
  CustomGame() : customWorld = CustomWorld() {
    cameraComponent = CameraComponent(world: customWorld);
  }
 
  late final CameraComponent cameraComponent;
  final CustomWorld customWorld;
 
  @override
  FutureOr<void> onLoad() async {
    await super.onLoad();
    addAll([cameraComponent, customWorld]);
  }
}
 
class CustomWorld extends World with HasGameRef<CustomGame> {
  CustomWorld();
}

By doing this we now have control on the camera (we can define the viewport, move it around, attach it to the position of the player, and more).

We also control our own instance of World from where we can add all the components in the game.

Handling events

Some mixins must be added to the entry FlameGame to enable certain functionalities.

Handling user events requires to add HasKeyboardHandlerComponents:

class CustomGame extends FlameGame with HasKeyboardHandlerComponents {
  CustomGame() : customWorld = CustomWorld() {
    cameraComponent = CameraComponent(world: customWorld);
  }
 
  late final CameraComponent cameraComponent;
  final CustomWorld customWorld;
 
  @override
  FutureOr<void> onLoad() async {
    await super.onLoad();
    addAll([cameraComponent, customWorld]);
  }
}

We enabled listening to events for the components in our game.

We can now attach another mixin KeyboardHandler to a component to implement methods like onKeyEvent (for keyboard events):

class CustomWorld extends World with HasGameRef<CustomGame>, KeyboardHandler {
  CustomWorld();
 
  @override
  bool onKeyEvent(RawKeyEvent event, Set<LogicalKeyboardKey> keysPressed) {
    /// Handle keyboard events here 🎹
  }
}

There is more.

Every week I build a new open source project, with a new language or library, and teach you how I did it, what I learned, and how you can do the same. Join me and other 700+ readers.

FlameGame in a flutter app

As we saw above, GameWidget allows to embed an instance of FlameGame like a normal widget everywhere in your flutter app.

This allows to implement normal flutter code and include a flame game everywhere as any other widget

void main() {
  runApp(const MyApp());
}
 
class MyApp extends StatelessWidget {
  const MyApp({super.key});
 
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flame Game Jam 3.0',
      theme: ThemeData(fontFamily: 'Font'),
      home: const Scaffold(
        body: GameView()
      ),
    );
  }
}
 
class GameView extends StatelessWidget {
  const GameView({super.key});
 
  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Positioned.fill(
          child: GameWidget(
            game: MyGame(),
          ),
        ),
        const Positioned(
          bottom: 50,
          left: 150,
          right: 150,
          child: Text("Some text at the bottom of the screen"),
        ),
      ],
    );
  }
}
 
class MyGame extends FlameGame {

We can render a MaterialApp as usual. We then insert a GameWidget with an instance of MyGame inside the widget tree.

This allows to render normal widgets (Positioned, Text, Stack) on top of our flame game.

You can create the UI of your game as a normal flutter app using widgets, and use flame with GameWidget to implement the game itself in the same app

State management

Like a normal Flutter app, you may need to share some state in your flame game as well (points, timer, equipment).

Since a flame game works like a normal flutter app, we can use the same state management strategies and packages used in any other app

The flame ecosystem has many Bridge Packages to integrate with state management solutions like riverpod (flame_riverpod) and bloc (flame_bloc).

For example, integrating with bloc requires to define the blocs like any usual (using MultiBlocProvider for example):

class MyApp extends StatelessWidget {
  const MyApp({super.key});
 
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flame Game Jam 3.0',
      theme: ThemeData(fontFamily: 'Font'),
      home: Scaffold(
        body: MultiBlocProvider(
          providers: [
            BlocProvider(
              create: (_) => GameStateCubit(),
            ),
          ],
          child: const GameView(),
        ),
      ),
    );
  }
}

We can then provide an instance of the bloc to FlameGame and use FlameMultiBlocProvider (from flame_bloc) to get access to the state:

class GameView extends StatelessWidget {
  const GameView({super.key});
 
  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Positioned.fill(
          child: GameWidget(
            game: MyGame(context.read<GameStateCubit>()),
          ),
        ),
      ],
    );
  }
}
 
class MyGame extends FlameGame {
  MyGame(this.gameStateCubit);
 
  final GameStateCubit gameStateCubit;
 
  @override
  FutureOr<void> onLoad() async {
    await super.onLoad();
 
    await add(
      FlameMultiBlocProvider(
        providers: [
          FlameBlocProvider<GameStateCubit, GameState>.value(
            value: gameStateCubit,
          ),
        ],
      ),
    );
  }
}

Now any component in the game can use the FlameBlocReader mixin to access GameStateCubit:

class Player extends SpriteComponent
    with FlameBlocReader<GameStateCubit, GameState> {
  @override
  Future<void> onLoad() {
    bloc.state /// πŸ‘ˆ Access `bloc` from [FlameBlocReader]
  }
}

This is all you need to get started with flame in flutter:

  • Create any component
  • Compose components like widgets
  • Add events and controls using mixin
  • Define a custom World and camera
  • Embed a flame game in a flutter app
  • State management

The flame ecosystem has also many solutions for other common requirements like audio, physics, tiles, animations, particles, and more.

You have everything you need to start working on your game!

If you are interested to learn more, every week I publish a new open source project and share notes and lessons learned in my newsletter. You can subscribe here below πŸ‘‡

Thanks for reading.

πŸ‘‹γƒ»Interested in learning more, every week?

Every week I build a new open source project, with a new language or library, and teach you how I did it, what I learned, and how you can do the same. Join me and other 700+ readers.