When a scene repeats one shape, such as coins or trees, make it one InstancedMesh in flutter_scene. Add a fixed pool of copies once, park unused ones off screen, and move each with setInstanceTransform. The engine treats the whole pool as one item.

This is for Flutter developers whose 3D scene slows down as props are added. The code matches flutter_scene 0.20.0, the version in the GrowApps template, and the same calls exist in 0.24.3 (October 2026). Our running example is 24 spinning coins in a runner game.

In short:

  • About 200 separate nodes cost us 95 ms of CPU per frame.
  • One instanced group is one render item and one cull test.
  • Fixed pools avoid adding and removing objects mid-game.
  • Parking unused copies far below the floor hides them cheaply.

Separate nodes cost CPU time each frame

Every node is work, even if it is tiny. The engine has to update, cull and submit each one every frame. In our reference runner, about 200 separate prop nodes cost about 95 ms per frame of CPU, before any effects.

Halving the render resolution changed that cost by only 2%. So the pixels were not the problem. The number of objects was. The InstancedMesh docs describe the fix: the whole set becomes "one render item, one pipeline, and one cull test rather than one node per copy."

Create the group once with a fixed pool

An InstancedMesh takes one geometry and one material. You attach it to an empty node with an InstancedMeshComponent, then add every copy you will ever need:

InstancedMesh makePool(Scene scene, Geometry shape,
                  Material material, int count) {
                final mesh = InstancedMesh(geometry: shape, material: material);
                scene.add(Node()..addComponent(InstancedMeshComponent(mesh)));
                final parked = vm.Matrix4.translation(vm.Vector3(0, -9000, 0));
                for (var i = 0; i < count; i++) {
                  mesh.addInstance(parked);
                }
                return mesh;
              }
              

The key line is the parked transform. Every copy starts 9,000 units below the floor, where the camera never looks. Bringing a coin into play is moving it, not creating it.

Move each copy every frame

For our coins, the shape is a TorusGeometry with a shiny gold material. Each frame, the game moves every coin toward the camera and spins it:

for (var i = 0; i < coinCount; i++) {
                coinZ[i] -= speed * dt;
                if (coinZ[i] < -3) coinZ[i] += coinCount * spacing; // recycle
                final lane = (i % 3 - 1) * 2.2;
                coins.setInstanceTransform(
                  i,
                  vm.Matrix4.translation(vm.Vector3(lane, 0.8, coinZ[i])) *
                      vm.Matrix4.rotationY(spin),
                );
              }
              

The recycle line is the pool at work. A coin that passes behind the camera jumps back to the far end of the track. With 24 coins spaced 2 units apart, the player sees an endless stream.

Hide a collected coin by parking it

When the player collects a coin, set its transform back to the parked position. Do not remove the instance. Removing and re-adding changes the indexes of other copies, which makes bookkeeping fragile. Keep a simple list of which slots are active, skip inactive slots in the per-frame update so a collected coin stays parked, and reuse parked slots when new coins spawn.

The same pattern works for obstacles, lamps, trees on a hill or crates in a warehouse. Give each prop type its own pool, sized for the most copies that can be on screen at once.

Pick what to instance

Instancing helps when many copies share geometry and material. It helps less for one-offs. Use this rule of thumb.

Object Instance it? Why
Coins, sleepers, lamps Yes Dozens of identical copies
Trees with two variants Yes, two pools One pool per shape
The player character No Only one, and it animates
A unique boss model No One copy

Load a model you reuse, such as an enemy GLB, once and clone() it, rather than loading the file again per copy.

What the GrowApps 3D starter does

The 3D starter scene in the GrowApps builder uses exactly this pattern. Its 24 coins are one instanced torus, with unused slots parked at y = -9000 and transforms set each frame.

The builder agent is told to make every repeated prop an instanced pool. That is how our reference runner went from 202 separate objects to 6 instanced groups. You can build a 3D runner with GrowApps and ask for repeated props to use instanced pools, then measure frame time on your target devices.

Count your nodes now

Add a debug print of how many nodes your scene creates for props. If any prop type appears more than about ten times as separate nodes, convert it to a pool with the makePool function above. Then measure again with the method in the performance budget for 3D on phones.

Frequently asked questions

Can instanced copies have different colours?

They share one material, so the same colour by default. Version 0.24 adds per-instance attributes for custom materials, which 0.20.0 lacks. On 0.20.0, use one pool per colour.

Sources and further reading