Skip to main content
← All demos

Torque and Motors

␣

Hold to thrust

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

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

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

// No gravity system is registered: these flywheels only ever spin in
// place, so nothing needs to pull them downward.
const thrustInput = new HoldAction('thrust');
const inputManager = registerInputs(world, time, {
holdActions: [thrustInput],
});
const keyboardInputSource = new KeyboardInputSource(inputManager);

keyboardInputSource.holdBindings.add(
new KeyboardHoldBinding(thrustInput, keyCodes.space),
);

const { x: width, y: height } = calculateVisibleWorldSize(
renderContext.width,
renderContext.height,
DEMO_VERTICAL_WORLD_UNITS,
);
const columnWidth = width / 2;

await createThrusterScenario(
world,
renderContext,
renderLayers.foreground,
{ x: -columnWidth / 2, y: height * 0.1 },
thrustInput,
);

await createMotorScenario(world, renderContext, renderLayers.foreground, {
x: columnWidth / 2,
y: height * 0.1,
});

// `createThrusterEcsSystem` and `createGustEcsSystem` change
// `RigidBodyEcsComponent.angularVelocity` directly for this tick, and
// `createAngularVelocityMotorEcsSystem` reads/corrects it, so all three
// must run before whatever system integrates velocity into position
// (`createEulerIntegrationEcsSystem`).
world.addSystem(createThrusterEcsSystem(time));
world.addSystem(createGustEcsSystem(time));
world.addSystem(createAngularVelocityMotorEcsSystem(time));
world.addSystem(createCameraEcsSystem(time));
world.addSystem(createRenderEcsSystem(renderContext));
world.addSystem(createEulerIntegrationEcsSystem(time));

return game;
};

This demo showcases the two ways to spin a RigidBody with torque. On the left, a flywheel carries a demo-specific ThrusterEcsComponent: while Space is held, createThrusterEcsSystem calls RigidBody.applyTorque on it directly every tick, spinning it up, and releasing lets a small angularDrag on the body gradually spin it back down, since nothing drives it once the torque stops. There's no engine-provided component for this one-shot/manual case, a game is expected to write a small system like this itself. On the right, a flywheel carries an AngularVelocityMotorEcsComponent, a built-in engine component that holds a steady target angular velocity on its own, no input needed, with no angularDrag of its own; a demo-only gust periodically knocks its speed off course, and the motor spends its limited maxTorque budget correcting back towards the target every tick afterwards.