Skip to content

Physics

The @shell/physics package provides a small ECS-native 2D physics stack. It includes linear and angular velocity and force integration, gravity, circle and axis-aligned box colliders, collision response, overlap queries, raycasts, collision events, distance and spring joints, and a patrol.

Register PhysicsPlugin to install the complete stack:

import { PhysicsPlugin } from '@shell/physics';
world.addPlugin(PhysicsPlugin);

Configure gravity and the spatial hash cell size when needed:

world.addPlugin(
PhysicsPlugin.config({
gravity: { x: 0, y: 500 },
cellSize: 32,
}),
);
OptionTypeDefaultDescription
gravity{ x: number; y: number }{ x: 0, y: 9.81 }Acceleration applied to dynamic rigid bodies
cellSizenumber64Cell size used by the collider spatial hash

Choose a cellSize near the typical collider size. Smaller cells reduce the number of broad-phase candidates but make large colliders occupy more cells.

The aggregate package provides bundles for the three common body types. DynamicBody includes linear and angular motion, force and torque accumulators, and a dynamic RigidBody. KinematicBody includes motion components with a kinematic RigidBody, while StaticBody contains only Position and a static RigidBody.

import { Define } from '@shell/ecs';
import { CircleCollider, DynamicBody } from '@shell/physics';
import { Position } from '@shell/transform';
const PlayerPrefab = Define.prefab()
.with(DynamicBody)
.with(Position, { x: 100, y: 100, z: 0 })
.with(CircleCollider, { radius: 16 });
const player = world.get('commands').spawn(PlayerPrefab).root;

Body bundles do not choose a collider shape. Compose them with CircleCollider or BoxCollider as needed. Adding a collider also adds the required CollisionLayer component with layer: 1 and mask: 1 unless the entity already has one.

PhysicsPlugin exposes the physicsForces resource as the primary gameplay API for changing a dynamic body’s motion:

const physicsForces = world.get('physicsForces');
physicsForces.applyForce(player, { x: 600, y: 0 });
physicsForces.applyImpulse(player, { x: 0, y: -200 });
physicsForces.applyTorque(player, 30);
physicsForces.applyAngularImpulse(player, Math.PI / 2);

applyForce requires Force, applyImpulse requires Velocity, applyTorque requires Torque, and applyAngularImpulse requires AngularVelocity. Every target also needs RigidBody.

Calls are queued until fixed-step force integration. Multiple calls accumulate, each call is consumed once, and static or kinematic bodies are ignored. Call applyForce or applyTorque again each fixed step for persistent effects.

PhysicsPlugin combines five plugins:

PluginProvides
CorePluginRigid bodies, gravity, force and torque accumulation, the physicsForces resource, and force integration
KinematicPluginLinear and angular velocity integration
CollidersPluginCollider indexing, collision detection and response, overlap queries, and raycasts
JointsPluginDistance and spring joints between two entities
PatrolPluginA walk back and forth on a line

The aggregate package re-exports the public APIs from @shell/physics-core, @shell/kinematics, @shell/colliders, @shell/joints, @shell/patrol, and @shell/physics-debug. Import from @shell/physics unless you intentionally install only part of the stack.

Both CorePlugin and the aggregate PhysicsPlugin bind physicsForces as a PhysicsForces instance. See Bodies & Forces for detailed behavior and component requirements.

Physics runs in fixed schedules. The system sets enforce this order:

clear force and torque
-> run gameplay force producers
-> drain queued forces and impulses
-> integrate linear and angular forces
-> set the speed of patrollers
-> integrate position and rotation
-> solve joints
-> rebuild the spatial index
-> detect collisions
-> resolve collisions

Use PhysicsSet to place gameplay systems at the correct point in this pipeline. Force-producing systems belong in PhysicsSet.Forces, after force and torque are cleared. Queued PhysicsForces calls are drained once after those producers and before force integration.

  • Bodies & Forces - Configure rigid bodies, velocity, gravity, forces, torque, and impulses
  • Colliders - Add shapes, collision layers, and collision response
  • Spatial Queries - Find nearby colliders and cast rays
  • Collision Events - React when contacts start, remain active, or stop
  • Joints - Hold two entities at a distance with a rope, a rod, or a spring
  • Patrol - Walk an entity back and forth on a line
  • Debug Visualization - Render collider outlines and joint lines with gizmos
  • Physics operates in 2D using Position.x, Position.y, and rotation around Rotation.z.
  • Box colliders remain axis-aligned. Rotation is ignored by collisions, overlap queries, and raycasts.
  • Collision response is intentionally simple. It applies linear restitution and positional correction, but does not generate angular impulses and does not currently use RigidBody.friction.