One key, read by both engines
Open the live demo · Read the source · View on GitHub
FlameInputBridge does not invent its own key-to-action map. It looks a
Flame key event up in the very same Bindings table flutter3d_game's own
DesktopInput reads, and writes into the very same InputState — so a
player who rebinds a key in one build keeps that rebind in the other.
Step 1: One table, one shared state #
// The same table and the same state a rebind screen edits and
// `DesktopInput` reads natively — a bridged game and a native one share
// one rebinding UI and one saved binding file, not two.
final bindings = Bindings()
..bind(
InputSource.key(LogicalKeyboardKey.keyD.keyId),
GameAction.moveRight,
);
final inputState = InputState();
final bridge = FlameInputBridge(bindings: bindings, inputState: inputState);
Step 2: A Flame key event, translated #
onKeyEvent returns false when it consumed the key — the opposite polarity
of KeyEventResult, but the one KeyboardHandler itself expects.
// `onKeyEvent` returns false when it consumed the key, matching
// `KeyboardHandler`'s own polarity.
final bool consumed = !bridge.onKeyEvent(
const KeyDownEvent(
physicalKey: PhysicalKeyboardKey.keyD,
logicalKey: LogicalKeyboardKey.keyD,
timeStamp: Duration.zero,
),
const <LogicalKeyboardKey>{},
);
Step 3: Both sides read the same answer #
Nothing here asks which engine saw the key first. The flutter3d side reads
held off the identical InputState the Flame-side bridge just wrote into.
// The Flame side already knows it consumed the key; the flutter3d side
// reads the very same `InputState` the press just wrote into, the way an
// actor's own movement would.
final bool held = inputState.held(GameAction.moveRight);
final double actorX = held ? 1.5 : 0.0;
The Flame handler reported the key consumed, and the flutter3d side reading
the same InputState a moment later found the action already held — one
press, one shared answer, read by two engines that never spoke to each
other directly.
Step 4: Press a key, walk the sphere #
Four keys, bound once in a table both engines read. Flame's side is a component that hands every key event to the bridge; it also draws four keycaps that light while the shared state holds their action. flutter3d's side is the sphere: each frame it reads the same state and walks. Click the scene and press W A S D, or use Hold D for me if there is no keyboard to hand.
// Four keys in the one table both engines read. The Flame side is a
// component that hands each key event to the bridge; it draws four
// keycaps that light while their action is held. The flutter3d side is
// `update` below, which reads the same state and walks the sphere.
final Bindings bindings = Bindings()
..bind(
InputSource.key(LogicalKeyboardKey.keyW.keyId),
GameAction.moveForward,
)
..bind(
InputSource.key(LogicalKeyboardKey.keyA.keyId),
GameAction.moveLeft,
)
..bind(
InputSource.key(LogicalKeyboardKey.keyS.keyId),
GameAction.moveBack,
)
..bind(
InputSource.key(LogicalKeyboardKey.keyD.keyId),
GameAction.moveRight,
);
_input = InputState();
_bridge = FlameInputBridge(bindings: bindings, inputState: _input);
_game = _InputGame()
..add(_Keys(_bridge))
..add(_KeyCap('W', GameAction.moveForward, _input, Vector2(56.0, 72.0)))
..add(_KeyCap('A', GameAction.moveLeft, _input, Vector2(16.0, 112.0)))
..add(_KeyCap('S', GameAction.moveBack, _input, Vector2(56.0, 112.0)))
..add(_KeyCap('D', GameAction.moveRight, _input, Vector2(96.0, 112.0)));
The sphere's half is one read of moveAxis per frame; nothing in it knows the
keys came through Flame.
// The flutter3d side of the same key: it reads the state Flame wrote.
final Vector2 axis = _input.moveAxis;
final Vector3 at = _walker.readPosition();
_walker.setPosition(
(at.x + axis.x * _speed * dt).clamp(-3.5, 3.5),
at.y,
(at.z - axis.y * _speed * dt).clamp(-3.5, 3.5),
);