Flutter Intermediate Lesson 107 of 135 3 min read
Input in Flame: Tap, Drag and Keyboard Controls
Handle input in Flutter Flame games: tap with TapCallbacks, drag with DragCallbacks, and arrow keys with KeyboardHandler. Runnable examples.
On this page
Games need controls. Flame sends input to components through mixins:
| Mixin | Events |
|---|---|
TapCallbacks | onTapDown, onTapUp, onLongTapDown, onTapCancel |
DragCallbacks | onDragStart, onDragUpdate, onDragEnd |
KeyboardHandler (+ HasKeyboardHandlerComponents on the game) | onKeyEvent |
A component only receives taps and drags inside its own area.
Tap: pop the balloons
import 'dart:math';
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
void main() => runApp(GameWidget(game: BalloonGame()));
class BalloonGame extends FlameGame {
final random = Random();
int popped = 0;
late final TextComponent label;
@override
Future<void> onLoad() async {
camera.viewfinder.anchor = Anchor.topLeft;
label = TextComponent(text: 'Popped: 0', position: Vector2(16, 16), priority: 10);
world.add(label);
for (var i = 0; i < 6; i++) {
spawn();
}
}
void spawn() {
world.add(Balloon(
position: Vector2(
40 + random.nextDouble() * (size.x - 80),
80 + random.nextDouble() * (size.y - 160),
),
color: Colors.primaries[random.nextInt(Colors.primaries.length)],
));
}
void onPopped() {
popped++;
label.text = 'Popped: $popped';
spawn();
}
}
class Balloon extends CircleComponent with TapCallbacks, HasGameReference<BalloonGame> {
Balloon({required super.position, required Color color})
: super(radius: 30, anchor: Anchor.center, paint: Paint()..color = color);
@override
void onTapDown(TapDownEvent event) {
removeFromParent();
game.onPopped();
}
}
Tap (or click) a balloon: it disappears, the score goes up and a new balloon appears.
Drag: move the player
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
void main() => runApp(GameWidget(game: DragGame()));
class DragGame extends FlameGame {
@override
Future<void> onLoad() async {
camera.viewfinder.anchor = Anchor.topLeft;
world.add(Paddle()..position = Vector2(size.x / 2, size.y - 60));
}
}
class Paddle extends RectangleComponent with DragCallbacks, HasGameReference<DragGame> {
Paddle()
: super(
size: Vector2(140, 24),
anchor: Anchor.center,
paint: Paint()..color = Colors.tealAccent,
);
@override
void onDragUpdate(DragUpdateEvent event) {
position.x = (position.x + event.localDelta.x).clamp(size.x / 2, game.size.x - size.x / 2);
}
}
event.localDelta is how far the finger moved since the last update.
Keyboard: arrow keys
The game needs HasKeyboardHandlerComponents, and each component that listens needs KeyboardHandler.
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(GameWidget(game: KeysGame()));
class KeysGame extends FlameGame with HasKeyboardHandlerComponents {
@override
Future<void> onLoad() async {
camera.viewfinder.anchor = Anchor.topLeft;
world.add(Knight()..position = size / 2);
world.add(TextComponent(text: 'Click here, then use the arrow keys or WASD', position: Vector2(16, 16)));
}
}
class Knight extends RectangleComponent with KeyboardHandler, HasGameReference<KeysGame> {
Knight() : super(size: Vector2.all(50), anchor: Anchor.center, paint: Paint()..color = Colors.amber);
final direction = Vector2.zero();
static const speed = 250.0;
@override
bool onKeyEvent(KeyEvent event, Set<LogicalKeyboardKey> keysPressed) {
direction.setZero();
if (keysPressed.contains(LogicalKeyboardKey.arrowLeft) || keysPressed.contains(LogicalKeyboardKey.keyA)) direction.x -= 1;
if (keysPressed.contains(LogicalKeyboardKey.arrowRight) || keysPressed.contains(LogicalKeyboardKey.keyD)) direction.x += 1;
if (keysPressed.contains(LogicalKeyboardKey.arrowUp) || keysPressed.contains(LogicalKeyboardKey.keyW)) direction.y -= 1;
if (keysPressed.contains(LogicalKeyboardKey.arrowDown) || keysPressed.contains(LogicalKeyboardKey.keyS)) direction.y += 1;
return true; // handled
}
@override
void update(double dt) {
super.update(dt);
if (!direction.isZero()) {
position += direction.normalized() * speed * dt;
position.clamp(size / 2, game.size - size / 2);
}
}
}
Store the direction in onKeyEvent and move in update. That keeps movement smooth while a key is held down, and diagonal movement (two keys) is not faster thanks to normalized().
Taps on the whole game
To react to a tap anywhere (for example “tap to jump”), add TapCallbacks to the game itself:
class JumpGame extends FlameGame with TapCallbacks {
@override
void onTapDown(TapDownEvent event) {
// event.canvasPosition is where the player tapped
}
}
Phones without keyboards
For mobile, add on-screen controls: Flame has JoystickComponent and HudButtonComponent, or simply place Flutter buttons over the game with overlays.
Try it yourself
In the keyboard example, press Space to change the knight’s colour. (Tip: check event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.space.)