flutter3d
Showcase Changelog 38 packages API reference

One key, read by both engines

since 0.7.0 The Flame bridge

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),
);