Skip to content
Work in progress. These docs describe Spawnite at launch, and some parts are still being built.

Devtools panel

useDevtoolsPanel(schema) declares a panel for the entity a component renders. The debugger draws it at the top of the inspector while the entity is selected, and over the entity in the world once the entity is pinned. The game styles nothing: the devtools draw every declared panel in the same card, in their own dark glass.

Call it in the component that renders the Entity, before the return, as Leva’s useControls is called:

import { useState } from "react";
import {
button,
Entity,
Spin,
useDevtoolsPanel,
type EntityProps,
} from "@spawnite/engine";
const startSpeed = 1;
export function Crate({ position = [0, 0, 0] }: Pick<EntityProps, "position">) {
const [speed, setSpeed] = useState(startSpeed);
useDevtoolsPanel({
spinSpeed: { value: speed, onChange: setSpeed, min: 0, max: 10 },
position,
reset: button(() => setSpeed(startSpeed)),
});
return (
<Entity name="Crate" position={position}>
<Spin speed={speed} />
</Entity>
);
}

Each key is one row, in the order written. The label is the key split from camelCase, so spinSpeed reads as Spin speed. Each value is one of the following:

  • A plain value reads out and takes no edit: a number, a word, a flag, or a list or a record of them, such as a position. A whole number reads as it is, and any other number and a list of numbers read to one decimal, as the inspector reads a position.
  • { value, onChange } is a field. A number with min and max is a slider with a box beside it, and step sets its step; a number without them is a box. A flag is a switch. A word is a text field that commits on Enter. A number or a word with options is a select of those options.
  • button(fn) is a button that calls fn when pressed.
  • custom(element) draws the element in a row of its own inside the panel’s card, for a widget the schema cannot draw, such as a small chart.

The component passes the schema on every render, so a readout is as live as the component: a value the component renders with shows as it changes. useInspect exposes state a tool reads without drawing it; the devtools page says how.

  • The inspector draws the panel’s card above the entity’s behaviour cards, whether or not the world panels are on.
  • The world panel stands over a pinned entity while the World panels switch is on. The debugger page says how a pin works.
  • Send to agent carries the readouts’ and the fields’ values under panel in the bundle, and describe(id) returns them, so the agent reads what the creator saw.

Headless, the hook does nothing, since nothing draws a panel there.

Unreal marks a property VisibleAnywhere for a readout, gives a number ClampMin and ClampMax for a slider, and a function CallInEditor for a button. Leva writes the same three as a plain value, { value, min, max } and button(fn), and the schema here takes Leva’s shape, which every React Three Fiber agent has read. The devtools draw the panel rather than Leva, so it matches the rest of the debugger.