Bridging a flutter3d_sim actor
Open the live demo · Read the source · View on GitHub
ActorComponent wraps one flutter3d_sim Actor the same way
Object3dComponent wraps any other bridged transform, plus the one extra hop
an actor needs: its body's simulated position lives on a
CharacterController, stepped by ActorSystem, not on the SceneNode
itself. ActorSystemComponent is the one place a bridged game steps that
shared system, once a frame.
Step 1: A brain that decides #
A game's own brain extends Brain and overrides only the hooks it cares
about. This one always walks towards +x.
/// Walks straight towards +x, every step, for as long as it lives.
final class _WalkBrain extends Brain {
@override
void act(Mind it) => it.steer(Vector3(1.0, 0.0, 0.0));
}
Step 2: An actor with a body #
ActorSystem.spawn gives an entity a CharacterController body and a brain,
and hands back a thin Actor handle onto both.
final world = CollisionWorld();
world.addBox(Vector3(0.0, -0.5, 0.0), Vector3(20.0, 1.0, 20.0));
final system = ActorSystem(world: world, random: GameRandom(1));
final controller = CharacterController(
world: world,
position: Vector3(0.0, 3.0, 0.0),
);
final goblin = system.spawn(
body: controller,
brain: _WalkBrain(),
name: 'goblin',
);
Step 3: Bridge it, then step it #
ActorComponent bridges the actor's body to a SceneNode; ActorSystemComponent
is the only thing that calls ActorSystem.beginStep/step, once a frame —
never the actor component itself, which would double-step a shared system.
final scene = Scene();
final node = MeshNode(
DeviceMesh.upload(device, SphereShape(segments: 16).build()),
Material(name: 'bridged-goblin', baseColor: Vector4(0.5, 0.7, 0.3, 1.0)),
);
scene.add(node);
final actorComponent = ActorComponent(
actor: goblin,
node: node,
scene: scene,
plane: BridgePlane.ground(),
);
final systemComponent = ActorSystemComponent(
system: system,
focus: Vector3.zero,
);
// The system decides where the goblin's body goes; the actor component
// copies that body's position onto the node, then the plane it shares
// carries it onto the Flame side — the same seam `Object3dComponent`
// gives every other bridged transform.
for (var i = 0; i < 90; i++) {
systemComponent.update(1 / 60);
actorComponent.update(1 / 60);
}
Ninety steps of the shared system moved the goblin's real physics body
forward, and the bridged Flame position read back exactly the body's own x
— the mesh followed the actor, through the bridge, with nothing hand-copied
in between.
Step 4: Three goblins on the map #
The same pieces, three times over. Every goblin is an actor of one shared
system; each has a bridge component that copies its body onto its mesh and,
through the plane, onto a Flame position, and the dots on the map are those
Flame positions. One ActorSystemComponent steps the whole system once a
frame. A goblin that walks off the far edge is put back on the near one, so
the walk goes on.
// Three goblins in one shared system, each with a bridge component that
// copies its body onto its node and, through the plane, onto a Flame
// position. One `ActorSystemComponent` steps the system, once a frame; the
// map's dots are those Flame positions.
final CollisionWorld world = CollisionWorld()
..addBox(Vector3(0.0, -0.5, 0.0), Vector3(20.0, 1.0, 20.0));
final ActorSystem system = ActorSystem(world: world, random: GameRandom(1));
final FlameMinimap map = FlameMinimap();
final List<Component> bridges = <Component>[];
final List<CharacterController> bodies = <CharacterController>[];
for (var i = 0; i < 3; i++) {
final CharacterController body = CharacterController(
world: world,
position: Vector3(-_edge + i * 1.5, 1.0, (i - 1) * 2.0),
);
bodies.add(body);
final Actor goblin = system.spawn(
body: body,
brain: _WalkBrain(),
name: 'goblin $i',
);
final MeshNode node = MeshNode(
DeviceMesh.upload(
context.device,
SphereShape(segments: 24, radius: 0.4).build(),
),
Material(
name: 'goblin $i',
baseColor: Vector4(_colors[i].r, _colors[i].g, _colors[i].b, 1.0),
),
name: 'goblin $i',
);
_scene.add(node);
bridges.add(
ActorComponent(
actor: goblin,
node: node,
scene: _scene,
plane: BridgePlane.ground(),
)..add(flameDot(_colors[i])),
);
}
map.world.addAll(bridges);
_game = TransparentFlameGame()
..add(ActorSystemComponent(system: system, focus: Vector3.zero))
// After the system, so the bodies have moved by the time they are read.
..add(_Wrap(bodies))
..add(map);