Accepting Player Input – Game Development with Flame and Flutter
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):
-
TapGestureRecognizer
– Recognizes taps. -
MultiTapGestureRecognizer
– Recognizes taps on a per-pointer basis. -
DoubleTapGestureRecognizer
– Recognizes when the user has tapped the screen at the same location twice in quick succession.
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.