RigidBody
physics/rigid-body.ts:89 Extends AbstractWorldObjectComponent<RigidBodyDeps>
Gives a WorldObject a physical presence: a Rapier rigid body and one or more colliders that fall under gravity, collide with the rest of the world's bodies, and (for dynamic bodies) drive the host's transform.
RigidBody is the per-object half of the physics subsystem; it declares a
dependency on the world's PhysicsWorld and registers itself there.
Add at least one collider through RigidBodyOptions.collider or
RigidBodyOptions.colliders — a body with no collider has no shape to
collide with and is rejected.
Who owns the transform
Which direction the transform flows depends on RigidBodyOptions.type (see RigidBodyType for the full table):
dynamicandkinematic-velocity— the simulation owns the transform. Each frame, after PhysicsWorld steps, this component copies the body's position and rotation back onto the host in its pre-update phase. Don't writehost.positiondirectly; influence the body through RigidBody.velocity, RigidBody.applyImpulse, and friends instead.kinematic-position— the host owns the transform. Sethost.position/host.rotationfrom your own code and the component feeds them into the body as its next kinematic target, so dynamic bodies collide against the scripted motion.fixed— never moves; the transform is read once at attach time.
The body is seeded from the host's transform when the component is added, so position the WorldObject before attaching this.
See PhysicsWorld for the frame-pipeline rationale behind reading the transform back in pre-update (short version: it keeps graphics from lagging the simulation by a frame, with no dependency on component order).
Example
import {
CircleGraphics, Circle, RigidBody, Prefab,
} from '@arcade2d/engine';
export const BallPrefab = new Prefab({
name: 'ball',
components: {
graphics: ({ object }) => new CircleGraphics(object, new Circle(16)),
body: ({ object }) =>
new RigidBody(object, {
type: 'dynamic',
collider: { shape: new Circle(16), restitution: 0.6 },
}),
},
});Constructors
constructor(host: WorldObject, options: RigidBodyOptions): RigidBody Parameters
-
hostWorldObject -
optionsRigidBodyOptions
Throws
EngineError with code
ErrorCode.PHYSICS_NO_COLLIDER when neither collider nor a
non-empty colliders is supplied.
Properties
enabled: boolean Per-component gate on the three update hooks (onPreUpdate,
onUpdate, onPostUpdate). When explicitly false, the engine skips
all three for this component during the host's tick — useful for
temporarily pausing behaviour (e.g. a freeze powerup) without removing
the component and losing its internal state.
Does not gate onAdded or onDestroy; those always fire so a host can
never end up with a half-attached component.
host: WorldObject The host this component is attached to. Stored read-only;
subclasses access it as this.host directly, or through the
tier-appropriate aliases (this.world, this.game) on the per-tier
abstract bases.
Accessors
game: Game The Game the host's world belongs to. Always non-null —
the world's game field is a mandatory construction argument, not
an option.
isSleeping: boolean Whether the body is currently asleep — Rapier rests bodies that have been still long enough, excluding them from simulation until something disturbs them.
raw: RigidBody Direct access to the underlying Rapier RigidBody instance.
Use with care. raw is an intentional escape hatch for cases the
arcade2d API doesn't cover — joints, per-collider mass overrides,
fine-grained sleep control, anything we haven't decided how to model yet.
Code that touches raw is coupled to Rapier's public API and may break
when:
- arcade2d upgrades Rapier (including minor versions).
- Rapier itself ships a breaking change.
- arcade2d swaps Rapier for a different physics engine.
None of those will be treated as breaking changes to arcade2d's own
surface. Prefer the typed methods on this component; reach for raw only
when no equivalent exists, and isolate the access behind your own helper
so the coupling is in one place.
Methods
applyForce(force: PointPrimitive): void Adds a continuous force to the body for the next step, in pixel·mass units
per second squared. Unlike RigidBody.applyImpulse, a force
accelerates the body gradually; re-apply it every frame to sustain it.
Has no effect on fixed or kinematic bodies.
Parameters
-
forcePointPrimitive
Returns
void applyImpulse(impulse: PointPrimitive): void Applies an instantaneous linear impulse to the body's centre of mass, in
pixel·mass units per second, immediately changing its velocity. Use this
for one-off kicks (a jump, a bullet hit). Has no effect on fixed or
kinematic bodies.
Parameters
-
impulsePointPrimitive
Returns
void applyTorqueImpulse(torqueImpulse: number): void Applies an instantaneous angular impulse, immediately changing the body's
spin. Positive values spin clockwise (the engine's y-down convention).
Parameters
-
torqueImpulsenumber
Returns
void onAdded(__namedParameters: RigidBodyDeps): void Creates the Rapier body and its colliders, seeded from the host transform, and registers them with the world's PhysicsWorld.
Parameters
-
__namedParametersRigidBodyDeps
Returns
void onDestroy(): void Removes the body (and its attached colliders, which Rapier frees with it) from the simulation, and unregisters its collider handles from the PhysicsWorld event lookup. Safe to call after a partial construction where the body was never created.
Returns
void onPreUpdate(): void Bridges the host transform and the simulated body once the world step has run (this is an object-scoped pre-update, so PhysicsWorld's world-scoped step already executed this frame).
kinematic-position: pushes the host transform into the body as its next kinematic target (host drives body).dynamic/kinematic-velocity: copies the body's transform onto the host (body drives host).fixed: nothing — it never moves.
Returns
void onUpdate(): void Delivers this frame's collisions to the body's listeners. Runs in the object update phase — after PhysicsWorld stepped and drained the event queue in the world pre-update, and after this component's own pre-update readback — so listeners see settled transforms. Skipped entirely for bodies with no listener.
Because this is a normal component update hook, the host wraps it in the
world's per-component error isolation: a listener that throws is reported
through World.reportError (or the world's onError) without
derailing the rest of the tick.
Returns
void resolveDependencies(resolver: WorldObjectDependencyResolver): RigidBodyDeps Declares the cross-tier dependency on the world's PhysicsWorld.
Parameters
-
resolverWorldObjectDependencyResolver
Returns
RigidBodyDeps Throws
EngineError with code ErrorCode.WORLD_COMPONENT_DEPENDENCY_MISSING when the world has no PhysicsWorld component to register with.
setPosition(position: PointPrimitive): void Teleports the body to a world position, in pixels, bypassing the
simulation. This is the supported way to respawn, warp, or reset a body
— a dynamic body's transform is owned by the simulation, so writing
host.position does nothing for it and the influence verbs
(RigidBody.velocity, RigidBody.applyImpulse) can't place
a body at an exact spot. The move is instantaneous and generates no
contacts along the way; existing linear velocity is preserved (zero it
via RigidBody.velocity for a clean stop). Wakes the body so the
change takes effect on the next step.
Parameters
-
positionPointPrimitive
Returns
void setRotation(rotation: number): void Teleports the body to an absolute rotation, in radians, bypassing the simulation — the angular counterpart to RigidBody.setPosition. Existing angular velocity is preserved; the body is woken.
Parameters
-
rotationnumber
Returns
void wake(): void Forces an asleep body awake so it participates in the next step even without an external disturbance.
Returns
void