Skip to main content

Newton's Cradle

game.ts
const renderLayers = {
foreground: 1 << 0,
};

const gravity = new Vector2(0, -600);

export const createNewtonsCradleGame = async (): Promise<Game> => {
const { game, world, renderContext, time } = createGame('demo-game');

createCamera(world, {
isStatic: true,
cullingMask: renderLayers.foreground,
verticalWorldUnits: DEMO_VERTICAL_WORLD_UNITS,
});

const physicsWorld = new PhysicsWorld({ gravity });

await createCradle(
world,
renderContext,
renderLayers.foreground,
new Vector2(0, DEMO_VERTICAL_WORLD_UNITS * 0.3),
);

// `createRevoluteJointEcsSystem` must run before
// `createPhysicsSyncEcsSystem`, which is what steps `physicsWorld`:
// newly-added joints need to be registered before that step happens (see
// the Revolute Joints guide's registration-order caution).
// `createArmEcsSystem` only needs to run before `createRenderEcsSystem`,
// so its updated arms are reflected in this tick's render.
world.addSystem(createRevoluteJointEcsSystem(physicsWorld));
world.addSystem(createArmEcsSystem());
world.addSystem(createCameraEcsSystem(time));
world.addSystem(createRenderEcsSystem(renderContext));
world.addSystem(createPhysicsSyncEcsSystem(physicsWorld, time));

return game;
};

Five balls are each hinged to their own pivot on a shared frame by a RevoluteJoint, spaced so neighboring balls just touch at rest, exactly like the desk toy. The joints only keep each ball swinging about its own pivot, they play no part in transferring momentum between balls, that comes entirely from the engine's ordinary collision resolution between the balls themselves. The leftmost ball starts pulled back and is released exactly once, when the scene is built, swings in, and the impact carries down the row to pop the rightmost ball out. From there it's left alone, so it settles the same way a real cradle does after a single push.