flutter3d
Showcase Changelog 38 packages API reference

Keeping a Flame position and a flutter3d node in step

since 0.7.0 The Flame bridge

Object3dComponent is the one place a Flame PositionComponent and a flutter3d SceneNode are kept at the same place, on one BridgePlane. A SyncDirection decides which side is authoritative each frame; nothing here infers a direction from which value changed most recently.

Step 1: The scene is authoritative #

The default direction, SyncDirection.sceneToFlame, is what every existing flutter3d system — a rigid body, an actor — already wants: it decides where things are, and the Flame component only ever reads that. Moving the node and calling update carries the move onto the Flame side.

// The flutter3d side moves; `SyncDirection.sceneToFlame` is the default,
// so `Object3dComponent` reads the node and writes the Flame component.
final node = MeshNode(
  DeviceMesh.upload(
    device,
    CuboidShape(size: Vector3(0.4, 0.4, 0.4)).build(),
  ),
  Material(
    name: 'scene-authoritative',
    baseColor: Vector4(0.5, 0.7, 0.9, 1.0),
  ),
);
scene.add(node);
final sceneToFlame = Object3dComponent(
  node: node,
  scene: scene,
  plane: plane,
);
node.setPosition(3.0, 0.0, 2.0);
sceneToFlame.update(1 / 60);
final Vector2 flamePosition = sceneToFlame.position;

Step 2: Flame is authoritative #

SyncDirection.flameToScene reverses it: the Flame component decides, and update writes that position onto the node instead — a Flame-driven prop that should also draw as a 3D object, say.

// The Flame side moves instead; `SyncDirection.flameToScene` makes
// `Object3dComponent` read the Flame component and write the node.
final otherNode = MeshNode(
  DeviceMesh.upload(
    device,
    CuboidShape(size: Vector3(0.4, 0.4, 0.4)).build(),
  ),
  Material(
    name: 'flame-authoritative',
    baseColor: Vector4(0.9, 0.6, 0.3, 1.0),
  ),
);
scene.add(otherNode);
final flameToScene = Object3dComponent(
  node: otherNode,
  scene: scene,
  plane: plane,
  direction: SyncDirection.flameToScene,
);
flameToScene.position = Vector2(4.0, -1.0);
flameToScene.update(1 / 60);
final Vector3 scenePosition = otherNode.readPosition();

Step 3: One BridgePlane, both directions #

Both components above share the same ground plane, so a Flame (x, y) and a flutter3d (x, height, y) mean the same point everywhere in this scene.

// One ground plane, shared by both components below, so a Flame (x, y)
// and a flutter3d (x, height, y) mean the same point everywhere in this
// scene.
final plane = BridgePlane.ground();

A node moved to (3, 0, 2) reads back as Flame (3, 2); a Flame position set to (4, -1) reads back as scene x=4, z=-1.

The node that moved carried its position onto the Flame side, and the Flame position that moved carried its onto the node — both readings came back exactly where the bridge's own plane said they would.

Step 4: Watch them keep step #

On the page the blue cube is the scene-authoritative one: flutter3d moves it round in a circle, and the blue dot on Flame's map follows. The orange cube is the reverse: Flame slides its component along a figure of eight, and the cube in the scene follows. Drag to turn the view and compare the floor with the map.

// The same two components as above, now in a running game: each frame
// Flame updates them and each copies one way. The map in the corner is
// Flame's own view of the ground plane, a metre to 24 pixels.
final BridgePlane plane = BridgePlane.ground();
final Object3dComponent reads = Object3dComponent(
  node: _leader,
  scene: _scene,
  plane: plane,
)..add(flameDot(const Color(0xFF80B3E6)));
final Object3dComponent writes = Object3dComponent(
  node: follower,
  scene: _scene,
  plane: plane,
  direction: SyncDirection.flameToScene,
)..add(flameDot(const Color(0xFFE6994D)));
final FlameMinimap map = FlameMinimap()
  ..world.addAll(<Component>[reads, writes]);
_game = TransparentFlameGame();
_game
  // Before `writes`, so the position is set before it is copied.
  ..add(_Drift(writes))
  ..add(map)
  ..add(flameCaption('blue: the node moves, Flame reads it'))
  ..add(
    flameCaption(
      'orange: Flame moves, the node reads it',
      at: Vector2(16.0, 40.0),
    ),
  );