Quick Start
This page builds a complete, keyboard-controlled character: a Jolt world, a
floor, a controller, a capsule mesh, and a render loop that steps physics and
draws the result. It assumes you already have a Three.js scene, camera, and
renderer set up.
1. World and floor
Section titled “1. World and floor”import { World, Shape } from "jolt-ts";
const world = await World.create({ gravity: [0, -9.81, 0] });
world.createBody({ type: "static", shape: Shape.box({ halfExtents: [25, 0.5, 25] }), position: [0, -0.5, 0], layer: "static", friction: 0.8,});2. Create the controller
Section titled “2. Create the controller”Create the capsule body yourself so you can rotation-lock it (a walking
character shouldn’t topple) and give it friction: 0 (the controller drives its
own ground friction). Then scale the float spring to the body’s mass — the
one step you must not skip.
import { Shape } from "jolt-ts";import { CharacterController } from "jolt-ts-character-controller";
const body = world.createBody({ type: "dynamic", shape: Shape.capsule({ halfHeight: 0.45, radius: 0.35 }), position: [0, 1.1, 0], layer: "moving", friction: 0, // the controller manages its own ground friction allowSleeping: false, allowedDofs: ["translation-x", "translation-y", "translation-z"], // no tipping motionQuality: "linearCast", // sweep against thin/fast geometry (CCD)});
// springK/dampingC are NOT mass-normalized — scale them to the real body mass.const massScale = body.mass() / 0.283; // 0.283 kg = the reference capsule massconst controller = new CharacterController({ world, body, capsuleHalfHeight: 0.45, capsuleRadius: 0.35, useCustomForward: true, // treat the forward vector as a world axis autoBalance: false, // rotation is DOF-locked; nothing to balance springK: 80 * massScale, dampingC: 6 * massScale, fallingGravityScale: 1,});3. A mesh to draw it
Section titled “3. A mesh to draw it”The controller exposes the resolved capsule dimensions, so the render mesh matches the physics shape exactly.
import { CapsuleGeometry, Mesh, MeshStandardMaterial } from "three";
const { capsuleRadius, capsuleHalfHeight } = controller.options;const mesh = new Mesh( new CapsuleGeometry(capsuleRadius, capsuleHalfHeight * 2), new MeshStandardMaterial({ color: 0x87cefa }),);scene.add(mesh);4. Gather keyboard input
Section titled “4. Gather keyboard input”The controller never reads the keyboard — you keep a plain input object and hand
it to setMovement. Fields you omit keep their previous value, so it’s fine to
update the object in place.
import type { MovementInput } from "jolt-ts-character-controller";
const input: MovementInput = { forward: false, backward: false, leftward: false, rightward: false, run: false, jump: false,};
const keymap: Record<string, keyof MovementInput> = { KeyW: "forward", KeyS: "backward", KeyA: "leftward", KeyD: "rightward", ShiftLeft: "run", Space: "jump",};
addEventListener("keydown", (e) => { const key = keymap[e.code]; if (key) input[key] = true;});addEventListener("keyup", (e) => { const key = keymap[e.code]; if (key) input[key] = false;});5. The render loop
Section titled “5. The render loop”Each frame: point the controller forward, feed it the input, step the controller, step the world, then copy the body’s pose onto the mesh.
const FIXED_DT = 1 / 60;
function frame() { // Face +Z. With useCustomForward, this is the world-space forward axis. controller.setForwardDirection({ x: 0, y: 0, z: 1 }); controller.setMovement(input);
controller.step(FIXED_DT); // apply movement/jump/float impulses world.step(FIXED_DT); // integrate the world
// Sync the render mesh from the body after the world has advanced. const p = controller.body.translation(); const r = controller.body.rotation(); mesh.position.set(p.x, p.y, p.z); mesh.quaternion.set(r.x, r.y, r.z, r.w);
renderer.render(scene, camera); requestAnimationFrame(frame);}
requestAnimationFrame(frame);That’s a fully playable character: WASD to move, Shift to run, Space to jump.
Where to go next
Section titled “Where to go next”- Overview — how a tick actually works and how to structure a fixed-timestep loop.
- Movement & Input — camera-relative movement, joysticks, run toggling, and locked facing.
- Configuration — every tuning option and its default.
- Animation State Machine — turn controller state into locomotion animation.