Accepting Player Input – Game Development with Flame and Flutter

Thu, March 28, 2019
4 minutes

By far the most common question asked by developers starting to use Flame and Flutter (that I know of) is how to accept player input like taps and drag gestures.

For some reason, this basic seemingly simple task when developing games is giving everyone a hard time.

If you don’t know what Flame is, it’s a minimalist game engine for the Flutter framework. You can find out more about it here and learn how to use it by reading the documentation.

First, let’s talk about how the guys at FireSlime did it with BGUG (Break Guns Using Gems).

Hybrid setup

BGUG uses what I would call a hybrid setup. The hybrid setup is a game that is contained inside a MaterialApp.

As a matter of fact, it doesn’t have to be a MaterialApp. If a Flame game is heavily reliant on the Flutter widget tree, I’d call it a hybrid setup.

Regarding player input, the code they have is the following block inside the main Dart function:

Flame.util.addGestureRecognizer(new TapGestureRecognizer()
  ..onTapDown = (TapDownDetails details) {...}
  ..onTapUp = (TapUpDetails details) {...});

Note: I truncated the body of the handler functions to {...} for brevity.

Apparently, this works perfectly as can be seen in the actual game that you can install from Google Play store.

But for some reason, people writing games (including myself) using Flame is having trouble making this work.

I figured out later that I was calling addGestureRecognizer before I called runApp inside the main function. To fix it, I just needed to call addGestureRecognizer after runApp.

Stand-alone game.widget

Another setup is what I would call a stand-alone Flame game. This setup feeds a Game instance’s .widget property into the runApp call making it the root widget.

This setup is perfect for simple games with just a couple of screens like Langaw, a game I’m developing.

This could also work for a larger, more complex game. Surely you’d have more freedom but I imagine it would be a lot more work.

A call to Flame.util.addGestureRecognizer is perfect for this scenario. The game has a global TapGestureRecognizer that forwards player gestures into handlers that you define.

Using this method is simple, you can see it in action in this file on GitHub.

Flame.util.addGestureRecognizer(new TapGestureRecognizer()
  ..onTapDown = (TapDownDetails details) {
    // call a game handler for the tap down event here
  }
  ..onTapUp = (TapUpDetails details) {
    // call a game handler for the tap up event here
  })
  // handler called a tap is cancelled
  ..onTapCancel = () {};

There are other gesture recognizers and most of them can be passed into addGestureRecognizer.

Here’s a list of the ones I find are most useful (with descriptions taken from the Flutter documentation):

Important Note: As I’ve learned after converting my game to widget tree setup, Flame.util.addGestureRecognizer must be called after runApp.

Material App (widget tree)

The last setup (that I know of) for Flame games is called the widget-tree setup.

This setup includes the widget property of a Game instance as a child of a Flutter widget tree. The setup could be a MaterialApp that handles the routing between screens.

The widget tree could also be a complicated layout (like a Stack) with the game at the bottom and a UI/HUD on top of it.

An example would be the following widget tree (the full example file can be found in this gist):

runApp(MaterialApp(
  home: Scaffold(
    body: Stack(
      children: [
        GestureDetector(
          behavior: HitTestBehavior.opaque,
          onTapDown: (TapDownDetails d) {
            // place your tap down handler code here
          },
          onTapUp: (TapUpDetails d) {
            // place your tap up handler code here
          },
          child: game.widget,
        ),
        Text('This is the HUD UI'),
      ],
    ),
  ),
));

The advantage of this setup is you’ll have a per-view control on the gestures you accept.

You could also wrap the GestureDetector widget with a WillPopScope widget to handle if the player presses the phone’s back button.

Note: The behavior property of the GestureDetector must be set to HitTestBehavior.opaque.

Conclusion

The way a developer writes how the game accepts input is his decision. As discussed above, there are at least three ways to do this.

Some games are simple and only composed of one view with one global input handler. Other games require a different handler for every view.

Whatever method is chosen, it must be based on the needs of the game.

PS: Support needed

This would probably be a good time to let everyone know that I started a Patreon page. If you find my tutorials helpful, supporting me by being a patron would be much appreciated. Just click the button below.