useBeforePhysicsStep to write data.ctrl each frame and renders null.
The useIkController() hook follows this same pattern. You can use it, swap in your own IK solver, or write your own controller from scratch.
Pattern: Simple Keyboard Bindings
For robots where arm control comes from<IkGizmo />, the controller only adds extra bindings (gripper, etc.):
<MujocoCanvas>:
Pattern: Custom Physics-Step Control
For more complex control (IK solvers, velocity control, state machines), useuseBeforePhysicsStep to write directly to data.ctrl each frame.
Keyboard State
Read keyboard input via window event listeners and a ref:Config-Driven Arm Controller
A generic hook that accepts a static config object makes it easy to support multiple robots. Each robot is a different config:Custom IK Solvers
Three options for IK:1. Use the built-in solver
The defaultuseIkController() uses Damped Least-Squares:
2. Plug in your own solver
PassikSolveFn to replace the built-in solver while keeping the gizmo, reset handling, and context:
3. Skip useIkController entirely
Solve IK yourself insideuseBeforePhysicsStep with full access to the model and data:
Pattern: Reusable Plugins with createController
For reusable controllers with typed config and default merging, use the createController factory. It handles config merging, display names, and metadata.
createController API
config (merged with defaults) and optional children. It also exposes static metadata: MyController.controllerName and MyController.defaultConfig.
Providing Context to Children
Controllers can provide state to descendants via React context:Listening for Resets
Register a callback to reset your controller state when the simulation resets:useIkController() hook demonstrates all these patterns: reset handling, useBeforePhysicsStep for solving, and useFrame for gizmo animation.
Coexisting with IK Gizmo
When a robot supports both gizmo drag and keyboard control, the controller needs to:- Accept
ikas a prop (theIkContextValuefromuseIkController()) - Sync state on transition: when the user switches from gizmo to keyboard, read
data.ctrlto avoid a position jump - Disable IK via
ik.setIkEnabled(false)when taking over
ik value from useIkController() to the controller as a prop.
The gizmo re-enables IK automatically when dragged.
Composing Controllers in Your Scene
Controllers are React children. Swap them based on state:Performance Tips
- Use
forloops instead of.forEach/.mapinuseBeforePhysicsStep(it runs every physics tick) - Store keyboard state in a
useRef, notuseState(avoids re-renders at 60fps) - Cache actuator IDs once (not every frame) using
findActuatorByName - Keep the callback closure stable; avoid creating new functions each render