Physics
Combined rigid body and collider component backed by Rapier. values.rigidbody.type is one of dynamic, fixed, kinematicPosition, or kinematicVelocity. Per-axis translation/rotation locks constrain motion (e.g. lock Y rotation for top-down characters). Supported collider shapes: cuboid, ball, capsule, cylinder, trimesh. Set values.collider.isSensor to receive collision events without producing a physical response.
Managed by:
PhysicsSyncSystem
Editor inspector

Properties
| Property | Type | Description |
|---|---|---|
values | PhysicsState | Authoring state containing rigidbody and collider sub-states. Saved with the project. |
values.rigidbody | RigidbodyState | Rigid body authoring state (type, gravity scale, locks, etc.). |
values.collider | ColliderState | Collider authoring state (shape, size, sensor flag, etc.). |
rigidBody | RapierRigidBody | null | Live Rapier rigid body. Recreated when the project loads; null until physics is initialized. |
collider | RapierCollider | null | Live Rapier collider. Recreated when the project loads; null until physics is initialized. |
API reference
- Class:
PhysicsComponent - Source:
core/components/Physics.ts
Scripting examples
Component access patterns
Use this.getComponent(X) to access a component on the entity this script is attached to. Use this.world.getEntityByName to find another entity by name, then this.world.getComponent to read its component:
// Component on the entity this script is on
const physics = this.getComponent(PhysicsComponent);
// Component on another entity, found by its scene name
const other = this.world.getEntityByName("Crate");
if (other) {
const physics = this.world.getComponent(other.entityId, PhysicsComponent);
}Apply a jump impulse
Apply a one-shot upward impulse to a dynamic rigid body when the player presses Space.
import { Behaviour, PhysicsComponent, KeyboardKeys } from "@relu-interactives/spatial-ecs";
import type { FloatInput } from "@relu-interactives/spatial-ecs";
export default class Jumper extends Behaviour {
data = {
jumpForce: 5 as FloatInput,
};
protected onUpdate() {
const physics = this.getComponent(PhysicsComponent);
if (!physics?.rigidBody) return;
if (this.world.getKeyDown(KeyboardKeys.Space)) {
physics.rigidBody.applyImpulse({ x: 0, y: this.data.jumpForce, z: 0 }, true);
}
}
}Move a kinematic body with arrow keys
Displace a kinematicPosition body each frame using the Rapier setNextKinematicTranslation API.
import { Behaviour, PhysicsComponent, KeyboardKeys } from "@relu-interactives/spatial-ecs";
import type { FloatInput } from "@relu-interactives/spatial-ecs";
export default class KinematicMover extends Behaviour {
data = {
speed: 3 as FloatInput,
};
protected onUpdate() {
const physics = this.getComponent(PhysicsComponent);
if (!physics?.rigidBody) return;
const pos = physics.rigidBody.translation();
let dx = 0;
let dz = 0;
if (this.world.getKey(KeyboardKeys.ArrowLeft)) dx -= this.data.speed * this.deltaTime;
if (this.world.getKey(KeyboardKeys.ArrowRight)) dx += this.data.speed * this.deltaTime;
if (this.world.getKey(KeyboardKeys.ArrowUp)) dz -= this.data.speed * this.deltaTime;
if (this.world.getKey(KeyboardKeys.ArrowDown)) dz += this.data.speed * this.deltaTime;
physics.rigidBody.setNextKinematicTranslation({
x: pos.x + dx,
y: pos.y,
z: pos.z + dz,
});
}
}Via ComponentInput in the inspector
You can also pick this component from the inspector using a ComponentInput field. Assign any entity in the inspector and the field resolves to the live PhysicsComponent instance at runtime.
import {
Behaviour,
PhysicsComponent,
type ComponentInput,
} from "@relu-interactives/spatial-ecs";
export default class Example extends Behaviour {
data = {
targetPhysics: {
type: "component",
value: null,
} as unknown as ComponentInput,
};
protected onUpdate() {
const physics = this.data.targetPhysics.value as PhysicsComponent | null;
if (!physics) return;
// Use the live component instance.
}
}Via EntityInput in the inspector
Alternatively, pick an entity from the inspector using an EntityInput field and then read the component off that entity via world.getComponent:
import {
Behaviour,
PhysicsComponent,
type EntityInput,
type WorldEntityView,
} from "@relu-interactives/spatial-ecs";
export default class Example extends Behaviour {
data = {
targetEntity: { type: "entity", value: null } as unknown as EntityInput,
};
protected onUpdate() {
const entity = this.data.targetEntity.value as WorldEntityView | null;
if (!entity) return;
const physics = this.world.getComponent(
entity.entityId,
PhysicsComponent,
) as PhysicsComponent | null;
if (!physics) return;
// Use the component on the picked entity.
}
}Rigid body type
Set values.rigidbody.type to "kinematicPosition" in the inspector for the second example. Applying impulses requires a "dynamic" rigid body.

