ALL_LAYERSvariablenacatamalon
ALL_LAYERS: numberEvery layer at once, which is what a collider hits unless a scene says otherwise.
The body format the engine defines and the seam an adapter registers through. The simulation lives in the adapter.
45 symbols
ALL_LAYERS: numberEvery layer at once, which is what a collider hits unless a scene says otherwise.
createPhysicsBody2d(options: { id, name, body, collider } & Partial<TPhysicsSurface>) => TPhysicsBody2dA complete flat collider from a partial description.
The factory exists so that "a rectangular collider" is one call instead of six fields somebody can forget: every record that reaches a document is complete by construction, which is what lets the format promise that a scene means the same thing whoever simulates it.
options{ id, name, body, collider } & Partial<TPhysicsSurface>What is known: at least its shape. Everything left out takes its default.
createPhysicsBody3d(options: { id, name, body, collider, offset } & Partial<TPhysicsSurface>) => TPhysicsBody3dA complete collider in three dimensions from a partial description: the sibling of
createPhysicsBody2d, filling in the same defaults.
options{ id, name, body, collider, offset } & Partial<TPhysicsSurface>What is known: at least its shape. Everything left out takes its default.
PHYSICS_LAYERS: 16How many collision layers a scene has: the smaller of the two backends, not a number chosen for taste.
Rapier2D packs what a body is and what it hits into one 32-bit number, 16 bits each, so 16 is its
hard ceiling; the 3D side could carry far more. The format takes the smaller, because a scene
using layer 20 would simulate in three dimensions and collide wrongly in two without a word said,
which is the backend-dependent meaning TPhysicsSurface exists to prevent.
Sixteen is also more than a game of this era uses: most projects name fewer than eight.
usePhysicsBody2d(options: TPhysicsBody2dOptions) => TPhysicsBody2dGives this object a flat shape to collide with.
It does two things, and that is the whole design. It writes down what the object is made of, so a scene saved from code says it and comes back with it; and it hands that to whatever is installed to simulate, so it moves. One way in, whether the scene was typed by a person or opened from a file, which is what makes those two behave the same.
Where it is comes from the object, never from here: the object's own placement if it has one, and
otherwise the placement of what it draws, which is where you put it with createSprite.
With nothing installed to simulate, this is still worth calling: the shape is recorded, the scene opens and saves without losing it, and it sits still. That is what a tool wants while somebody is building a level.
optionsTPhysicsBody2dOptionsIts kind ('dynamic', 'static' or 'kinematic'), its shape, and what it is
made of.
const BROWN = getColor('#8b5a2b');
const Crate = (x: number, y: number) => {
createSprite({ tint: BROWN, width: 24, height: 24, transform: { x, y } });
usePhysicsBody2d({ body: 'dynamic', collider: { shape: 'rect', width: 24, height: 24 }, restitution: 0.2 });
};usePhysicsBody3d(options: TPhysicsBody3dOptions) => TPhysicsBody3dGives this object a shape in three dimensions to collide with. The sibling of
usePhysicsBody2d; see it for why declaring and simulating are one call.
offset is where the shape sits inside the object, which is what a model imported standing on
its own feet needs so that its collider is not buried half in the floor.
optionsTPhysicsBody3dOptionsIts kind ('dynamic', 'static' or 'kinematic'), its shape, and what it is
made of.
usePhysicsWorld2d(options: { id, gravity }) => TPhysicsWorld2dOpens the flat simulation for this scene, and says how hard things fall.
Called in the scene's body, because gravity belongs to the scene and not to anything in it: a scene seen from above has none and a platformer does. Called anywhere else it is recorded on that object and nothing reads it, which is said out loud rather than ignored.
gravity is in pixels per second squared with +y pointing down, which is the way a sprite's
y axis grows, so earth-ish is about 900 and positive.
Like a collider, this is worth calling with nothing installed to simulate: the scene records what it wants and comes back with it.
options{ id, gravity }How hard things fall (gravity, in pixels per second squared, +y down), and an
id for a scene with more than one.
export const Level: TSceneFn = () => {
usePhysicsWorld2d({ gravity: { x: 0, y: 900 } });
// ...then objects with usePhysicsBody2d, which fall in it.
return createScene();
};usePhysicsWorld3d(options: { id, gravity }) => TPhysicsWorld3dOpens the simulation in three dimensions for this scene. See usePhysicsWorld2d.
gravity is in metres per second squared with +y up, so earth is -9.81: the opposite sign
from the flat one, because there a placement's y grows downwards. The two disagree on purpose.
options{ id, gravity }How hard things fall (gravity, per second squared), and an id for a scene
with more than one.
TCollider2d: { shape, width, height } | { shape, radius } | { shape, vertices }The shape a flat body collides with, centred on where the object is.
Centred and not cornered, because that is where this engine puts a picture too: a sprite's
anchor is its middle unless somebody says otherwise, so a shape the same size as the art lines
up with it without anybody working out an offset. An object drawn from its corner (anchor at
zero, which is how a floor is usually written) is the case where the two differ, and there the
placement is the corner and the shape is still around it.
The set is small and closed on purpose. It is what any 2D physics engine has in common, not a copy of one library's list: the document has to outlive whatever is simulating it, and a shape no backend can build would be a field that does nothing.
TCollider3d: { shape, size } | { shape, radius } | { shape, radius, height } | { shape, geometry } | { shape, geometry }The shape a body in three dimensions collides with, around the object's origin unless it carries
an offset.
TPhysicsBody: TPhysicsBody2d | TPhysicsBody3dEither kind of collider, told apart by _type.
Two types and not one with a flag, because their shapes do not overlap (a circle is not a sphere) and everything reading them would otherwise have to unwrap one discriminant inside another.
TPhysicsBody2d: { _type, id, name, body, collider } & TPhysicsSurfaceA flat collider on an object: what it is made of, and how it is driven.
Where it is is not here. The object owns that, by the standing rule that a component carries no placement of its own, which is also why moving the object moves its collider.
TPhysicsBody2dOptions: { id, name, body, collider } & Partial<TPhysicsSurface>What a flat body is made from: how it moves, its shape, and how it feels to touch. id and
name are only for keeping the identity a scene document gave it.
TPhysicsBody3d: { _type, id, name, body, collider, offset } & TPhysicsSurfaceA collider in three dimensions. See TPhysicsBody2d for why the placement is not here.
TPhysicsBody3dOptions: { id, name, body, collider, offset } & Partial<TPhysicsSurface>The same for a body in space.
TPhysicsBodyType: 'static' | 'dynamic' | 'kinematic'How a body is driven, and what it lets happen to it.
'static' never moves and nothing can push it: walls, the ground, the level. 'dynamic' is
driven by gravity, forces and contacts, and is what "a physics object" usually means.
'kinematic' is driven by you and pushes others without being pushed back: a moving platform, a
lift, a boss on rails.
What a collider is made of: how a contact settles, and whether it settles at all.
Every field is required, and that is the point. This is the written-down truth of a scene, and an absent value would mean "whatever the adapter does", which would make the meaning of a document depend on who reads it. A scene that behaves differently under a different backend is exactly the coupling this format exists to prevent.
restitutionnumberBounciness: how much of the speed it arrived with it leaves with, 0 to 1.
It belongs to the pair and not to one body: an engine mixes the two colliders' values
(usually by averaging), so a ball asking for 0.8 landing on a floor left at 0 bounces at
0.4. Set both sides.
frictionnumberdensitynumberWeight comes from this and the shape's size, and is never set directly.
sensorbooleanNotices overlaps without settling them, so things pass straight through: a checkpoint, a pickup, a place that hurts.
layernumberWhich layer this body is on, 0 to 15.
One layer, and the restraint is deliberate: "what is this thing" has one answer in every game that has ever needed layers, and the moment something is on two, a table of what hits what stops being readable by eye. What varies is what it collides with.
collidesWithnumberA mask of the layers this body collides with: 1 << n per layer, so ALL_LAYERS is
everything and (1 << 0) | (1 << 2) is layers 0 and 2.
It is mutual, and that is the rule that surprises people. Two bodies meet only if each one's mask includes the other's layer, so a bullet that lists walls still passes through them unless walls also list bullets. Both backends work that way, so the format says so instead of papering over it: a one-sided rule would have to be faked on both sides and still would not mean the same thing in each.
TPhysicsWorld: TPhysicsWorld2d | TPhysicsWorld3dEither simulation's settings. A scene has at most one, and it lives on the scene's root: gravity is a property of the scene and not of anything in it (a scene seen from above has none, a platformer does), and the root is an object like any other, so it needs no new home.
The flat simulation's settings for one scene.
gravity is in pixels per second squared with +y pointing DOWN, because that is the way a
sprite's y axis grows and the simulation writes straight onto a placement. So earth-ish gravity is
about 900, positive: the opposite sign from three dimensions. The disagreement is deliberate,
because turning 2D into metres would put a pixels-per-metre factor into every flat game and every
push, and buy a tidiness no author benefits from, since no scene mixes the two.
_type'physics-world-2d'idstringIts own id, like every other component. A world has no name (neither does a camera), and it still has to be addressable: a tool selects and removes it by id.
gravity{ x, y }The simulation's settings in three dimensions. gravity is in metres per second squared with
+y UP, so earth is { x: 0, y: -9.81, z: 0 }. See TPhysicsWorld2d for why the two
disagree on purpose.
_type'physics-world-3d'idstringgravity{ x, y, z }