Flame Bloc 1.4.0 and the new API

Games
June 15, 2022
and 
June 15, 2022
updated on
June 22, 2022
By 
Guest Contributor

For Flutter developers, using a state management package is a helpful tool in building scalable applications. The same is true for games when it comes to managing game state.

In a previous blog, we wrote about how to use Flame Bloc to manage game state. The article provides code for a game prototype that demonstrates how you can use Flame Bloc to improve the separation of the game logic and communicate between game components and UI widgets.

Flame Bloc is a fairly new package. At VGV we used it most recently in I/O Pinball. While working on this project, we identified a few aspects of Flame Bloc that could be improved, which resulted in a new, revamped API. This article takes a closer look at some of these API changes.

Reasons to change the API

Bloc access through BuildContext in the widget tree.

Although the above seems to be the conventional path for accessing a bloc in a Flutter app, in a Flame game, this isn't that simple.

To put it more simply, BuildContext is only accessible to a Flame game after its attachment lifecycle step and beyond. This was causing a bit of confusion from developers as that limitation wasn't always clear.

Accessing the bloc via BuildContext makes it difficult to scope blocs to a specific Flame component.

For example, imagine we have a player component that uses a bloc to manage its state. The bloc might manage state like whether the player is walking or running, whether the player is invincible, etc. This state is specific to the player component, so ideally the bloc should be scoped to just the player component. In a Flutter app, you could provide the bloc directly to the corresponding widget, but in the previous API this wasn't possible since you were forced to provide the bloc from the widget tree.

The flame_bloc API was too different from flutter_bloc's API

It is expected that people who are familiar with flutter_bloc would try to apply the same concepts and methods to flame_bloc.

This previously wasn't possible. The original flame_bloc API was quite different, as it was heavily based on inheritance instead of composition, which made the adaptation from flutter_bloc to flame_bloc difficult.

With the introduction of the new API, both inheritance and composition are available, so you can choose whichever style you prefer or fits better to the structure of your game.

Difficult to test

Testing components that listen or access blocs was also quite difficult. Most of the challenges were due to how flame_bloc relied on BuildContext to look up blocs in the widget tree. This forced the use of widgetTest, even though no real widgets were being tested, which could get confusing.

The new API

No more FlameBlocGame

Before 1.4.0, to access blocs inside a game, you had to either extend from FlameBlocGame or mix the FlameGame with FlameBloc. This would allow you to use the read method to read blocs from the widget tree, or you could add BlocComponents into the game, which would allow you to listen to state changes.

This approach was removed with the new API. Now to access a bloc within a game, the suggestion is to simply inject it throughout the game constructor (this will be shown in more detail later in this article).

Introduction of FlameBlocProvider and FlameMultiBlocProvider

FlameBlocProvider and FlameMultiBlocProvider are components that you can use to provide blocs and cubits down the component tree. They are roughly equivalent to how BlocProvider and MultiBlocProvider work in flutter_bloc.

So, let's say that we would like to manage the internal state of our PlayerComponent using a bloc. This is how our code would look:

class PlayerComponent extends PositionComponent {
  PlayerComponent() : super(
    children: [
      FlameBlocProvider<PlayerBloc, PlayerState>(
        create: () => PlayerBloc(),
        children: [
          PlayerSprite(),
          PlayerController(),
          // ...
        ],
      ),
    ],
  );
}

Or, maybe we would like to split our player state into two: a PlayerState and a InventoryState. The above example could be transformed into:

class PlayerComponent extends PositionComponent {
  PlayerComponent() : super(
    children: [
      FlameMultiBlocProvider(
        providers: [
          FlameBlocProvider<PlayerBloc, PlayerState>(
            create: () => PlayerBloc(),
          ),
          FlameBlocProvider<InventoryBloc, InventoryState>(
            create: () => InventoryBloc(),
          ),
        ],
        children: [
          PlayerSprite(),
          PlayerController(),
          // ...
        ],
      ),
    ],
  );
}

As we mentioned before, you can't look up blocs from the widget tree anymore. If a bloc needs to be shared between a game and widgets, it needs to be injected somewhere within your game, then be provided down the component tree. We suggest doing this with the game constructor.

Let's take the InventoryBloc from above as an example. If we want to write an inventory panel on our game using Flutter widgets, this is how the code could look:

class GamePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => InventoryBloc(),
      child: Row(
        children: [
          Expanded(child: GameView()),
          InventoryView(),
        ],
      ),
    );
  }
}

class GameView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final inventoryBloc = context.read<InventoryBloc>();
    return GameWidget(game: MyGame(inventoryBloc: inventoryBloc));
  }
}

class MyGame extends FlameGame {
  MyGame({ required this.inventoryBloc });

  final InventoryBloc inventoryBloc;

  @override
  Future<void> onLoad() async {
    await add(
      FlameBlocProvider<InventoryBloc, InventoryState>.value(
        value: inventoryBloc,
        children: [
          PlayerComponent(),
          // other components...
        ],
      ),
    );
  }
}

// InventoryView code omitted...

Introduction of FlameBlocListenable and FlameBlocListener

Previously, the ability to listen to a bloc on a component level was done through the BlocComponent mixin. Once you added that mixin to your component, you could override the onNewState to receive updates on a bloc.

With the new API, you have two options to listen to a bloc. You can either add the FlameBlocListenable mixin to a component or add the FlameBlocListener component with a proper callback to the component tree. Note that FlameBlocListener allows you to listen for changes in multiple blocs within a single component whereas the mixin doesn't.

Let's write an example with both approaches. Below we have an example of a component called PlayerInventoryController which is used to manage the player's inventory.

Using FlameBlocListenable:

class PlayerInventoryController extends Component with FlameBlocListenable<InventoryBloc, InventoryState> {
  @override
  void onNewState(InventoryState state) {
    updatePlayerInventory(state);
  }
}

Using FlameBlocListener:

class PlayerInventoryController extends Component {
  @override
  Future<void> onLoad() async {
    await add(
      FlameBlocListener<InventoryBloc, InventoryState>(
        onNewState: (state) {
          updatePlayerInventory(state);
        },
      ),
    );
  }
}

Introduction of FlameBlocReader

FlameBlocReader is a smaller mixin. Its usage is similar to FlameBlocListenable, but it is meant to be used on components that need to access a bloc without having to listen to it. For example, it can be useful for components that just need to access the state of the bloc on load, or even more common, to trigger events on the bloc.

Flame Bloc examples

Take a look at the following projects for examples on how to use Flame Bloc:

  • Very Good Adventures: We made this demo for the first Flame Bloc article we wrote. It provides a simple example of an RPG game that features a character who can walk through a room, collect items and equip them. The inventory system is fully managed by Bloc and its source code has been updated to the latest Flame Bloc API.
  • I/O Pinball: Highlighted as part of Google I/O 2022, this demo is a thematic pinball table made with Flame and Forge2d. It uses bloc to manage several features, for example, its own game state, including score, remaining balls, and the progress of specific bonuses.
  • Sublight: This is an open source game about a spaceship on a long journey. While still a work in progress, this game is currently being built with Flame Bloc.

The future of Flame Bloc

The old Flame Bloc API will continue to exist (as deprecated) for the foreseeable future, so you will have time to migrate your code.

Active development on Flame Bloc is on hold. The goal for now is to test its features, gather feedback, and improve what needs to be improved or fix what needs to be fixed. Any insight is valuable so feel free to always send us your thoughts and ideas!

Check out the flame_bloc package on pub.dev.
Additional Contributors
No items found.

More Stories