guide

The World provider

<World> sets up the canvas, lighting, physics, and controls — every component lives inside it.

⌖ Explore the library in 3D → · View as Markdown

<World> is the root of every Runek scene. It wires up the React Three Fiber canvas, a default light rig, the Rapier physics world, and keyboard controls, then exposes scene-wide settings to its children through context.

import { World } from '@runek/core'
import { Bookshelf } from './runek/Bookshelf'
import { Player } from './runek/Player'

<World>
  <Bookshelf position={[0, 1, 0]} seed={7} />
  <Player />
</World>

What it sets up

Props

Prop Type Default Description
unit number 1 World scale: 1 unit = 1 meter. Read it via useWorld().
gravity [x, y, z] [0, -9.81, 0] Physics gravity.
lights boolean true Render the default lights. Set false to use your own.
palette Partial<WorldPalette> built-in Override color slots; every component re-themes at once.
controls Record<string, string[]> built-in Remap input bindings (action → key codes); serializable.
fog { color, near, far } off Linear distance fog; pair the color with the sky’s horizon.
input boolean true Read the keyboard. Set false to ignore it, e.g. while a modal is open.
paused boolean false Stop rendering and physics until set back to false.
debug boolean false Draw Rapier collider wireframes.

Controls

Input bindings are a world setting, not a hardcode. The defaults move with WASD and steer the camera with the arrow keys (Left/Right turn, Up/Down look — in first and third person, composing with mouse-drag). Override any subset; the rest keep their defaults:

<World controls={{ forward: ['KeyW', 'KeyZ'], leftward: ['KeyA', 'KeyQ'] }}>
  {/* AZERTY-friendly: ZQSD moves too */}
</World>

Keys are KeyboardEvent.code names. Bind an action to [] to disable it, or add your own action names — they become bindings any component can read with drei’s useKeyboardControls. The resolved map is available to components at useWorld().controls. For a completely custom drei map there’s still the low-level keyboardMap prop, which wins verbatim when given.

Sharing the page

A world often sits beside ordinary UI: a chat box, a settings form, a modal. Keys typed into a text field (input, textarea, select, or anything contenteditable) are left to the page, so typing “wasd” into a form never walks the avatar. To ignore the keyboard entirely, pass input={false}. Keys held at that moment are released, and so are keys held when the window loses focus. Mouse look listens on the canvas only, so anything drawn over the canvas already takes the pointer.

When the world isn’t on screen (a background tab of your app, a hidden panel), paused stops the frame loop and the physics steps so the world costs nothing:

<World paused={!visible} input={!modalOpen}>
  {/* … */}
</World>

On resume, animations carry on from where they stopped, and anything driven by the wall clock (a walking Person) is already where it should be. WorldRenderer and WorldEditor take both props too.

The palette

Components default their colors to named slots — wood, wall, foliage, waterDeep, and friends — read from context. Override any subset on <World> and the whole scene re-themes; explicit color props on a component still win:

<World palette={{ wood: '#7a5a40', foliage: '#557d3c' }}>
  <Bookshelf seed={7} />          {/* frame picks up the new wood */}
  <Trees seed={4} />              {/* leaves pick up the new foliage */}
  <Chair color="#222" />          {/* explicit prop beats the palette */}
</World>

The full slot list is the WorldPalette interface in @runek/core.

useWorld()

Components read the shared scale from the provider so they size consistently:

import { useWorld } from '@runek/core'

function MyThing() {
  const { unit, palette } = useWorld()
  const width = 2 * unit // 2 meters, whatever the world's scale
  const color = palette.wood // themed with the rest of the world
  // …
}

While a Player is mounted, useWorld().player?.current is its avatar (an Object3D at eye height), so a component can react to where the player is rather than where the camera is. In third person those differ: the camera trails a few meters behind. Read it inside useFrame, and fall back to the camera when it’s null (no Player, e.g. an orbit view).

Next: the component contract — the rules every component follows.