arcade2d
Class

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):

  • dynamic and kinematic-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 write host.position directly; influence the body through RigidBody.velocity, RigidBody.applyImpulse, and friends instead.
  • kinematic-position — the host owns the transform. Set host.position / host.rotation from 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

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.

readonly #
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

#
angularVelocity: number

The body's angular velocity, in radians per second.

readonly #
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.

readonly #
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.

readonly #
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.

readonly #
type: RigidBodyType

This body's RigidBodyType.

#
velocity: Readonly<Point>

The body's linear velocity, in pixels per second, as a fresh Point. Returned as Readonly so the "I mutated it and nothing happened" footgun is a compile error — assign a new value through the setter to change the body's velocity.

readonly #
world: World

The World the host WorldObject lives in. Shorthand for this.host.world.

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

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

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

  • torqueImpulse number

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

  • __namedParameters RigidBodyDeps

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

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

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

  • rotation number

Returns

void
#
wake(): void

Forces an asleep body awake so it participates in the next step even without an external disturbance.

Returns

void
ESC