Physics
YAGE uses Rapier2D under the hood for physics simulation.
The @yagejs/physics package wraps Rapier with a pixel-based API — you never need
to think about meters. All positions, velocities, and forces are in pixels.
PhysicsPlugin Setup
Section titled “PhysicsPlugin Setup”import { PhysicsPlugin } from "@yagejs/physics";
engine.use(new PhysicsPlugin({ gravity: { x: 0, y: 980 }, // pixels/s², default: (0, 980) pixelsPerMeter: 50, // conversion factor, default: 50}));Rigid Bodies
Section titled “Rigid Bodies”RigidBodyComponent wraps a Rapier rigid body. Add it after Transform.
import { RigidBodyComponent } from "@yagejs/physics";
entity.add(new RigidBodyComponent({ type: "dynamic", // "dynamic" | "static" | "kinematic" fixedRotation: true, // prevent rotation gravityScale: 0, // ignore gravity (0 = zero-g, 1 = normal) linearDamping: 5, // velocity drag angularDamping: 1, // rotation drag ccd: true, // continuous collision detection for fast objects lockTranslationX: false, // lock horizontal movement syncRotation: true, // sync physics rotation back to Transform}));Body types:
dynamic— affected by forces, gravity, and collisions. Use for players, projectiles, and physics objects.static— never moves. Use for walls, floors, and platforms.kinematic— moved programmatically, not by forces. Use for moving platforms, elevators, and doors. Move kinematic bodies by setting theTransformposition (e.g.transform.setPosition(x, y)), not by callingrb.setPosition— the physics system automatically syncs Transform → Rapier each frame before stepping.rb.setPositionis a teleport intended for dynamic bodies (e.g. respawning a player).
Colliders
Section titled “Colliders”ColliderComponent defines the collision shape. Add it after RigidBodyComponent.
The required component order is: Transform → RigidBodyComponent → ColliderComponent.
import { ColliderComponent } from "@yagejs/physics";
// Boxentity.add(new ColliderComponent({ shape: { type: "box", width: 64, height: 32 }, restitution: 0.5, // bounciness (0–1) friction: 0.3, density: 1, // affects mass for dynamic bodies}));
// Circleentity.add(new ColliderComponent({ shape: { type: "circle", radius: 16 },}));
// Capsuleentity.add(new ColliderComponent({ shape: { type: "capsule", halfHeight: 20, radius: 10 },}));
// Convex polygonentity.add(new ColliderComponent({ shape: { type: "polygon", vertices: [{ x: 0, y: -20 }, { x: 20, y: 20 }, { x: -20, y: 20 }], },}));All dimensions are in pixels.
Collision Layers
Section titled “Collision Layers”CollisionLayers lets you control which objects can collide using named layer
bitmasks.
import { CollisionLayers } from "@yagejs/physics";
const layers = new CollisionLayers();const LAYER_PLAYER = layers.define("player");const LAYER_WALL = layers.define("wall");const LAYER_COIN = layers.define("coin");
// Player collides with walls and coinsentity.add(new ColliderComponent({ shape: { type: "circle", radius: 16 }, layers: LAYER_PLAYER, mask: LAYER_WALL | LAYER_COIN,}));
// Coin only collides with playercoinEntity.add(new ColliderComponent({ shape: { type: "circle", radius: 10 }, sensor: true, layers: LAYER_COIN, mask: LAYER_PLAYER,}));An object on layer A with mask B will only interact with objects on layer B that also have A in their mask.
Sensors and Trigger Events
Section titled “Sensors and Trigger Events”A sensor collider detects overlaps without producing a physical response. Use sensors for trigger zones, collectibles, and hit detection.
const collider = new ColliderComponent({ shape: { type: "circle", radius: 10 }, sensor: true,});entity.add(collider);
collider.onTrigger((event) => { if (event.entered) { console.log("entered by", event.other.name); } else { console.log("exited by", event.other.name); } // event.otherCollider — the other entity's ColliderComponent});For non-sensor colliders, use onCollision instead:
collider.onCollision((event) => { if (event.started) { console.log("hit", event.other.name); console.log("contact normal:", event.contactNormal); console.log("contact point:", event.contactPoint); }});Both handlers return an unsubscribe function.
Querying Overlapping Entities
Section titled “Querying Overlapping Entities”You can query which entities currently overlap a sensor collider:
const overlapping = collider.getOverlapping();const enemies = collider.getOverlapping({ has: [EnemyTag] });const components = collider.getOverlappingComponents(HealthComponent);Velocity and Forces
Section titled “Velocity and Forces”Prefer setVelocity for direct control. Use applyImpulse or applyForce
for physics-driven movement.
const rb = entity.get(RigidBodyComponent);
// Direct velocity control (px/s) — best for player movementrb.setVelocity(new Vec2(200, 0));rb.setVelocityX(200); // set X, keep Yrb.setVelocityY(-300); // set Y, keep X
// Read velocityconst vel = rb.getVelocity(); // Vec2 in px/s
// Impulse (instant momentum change)rb.applyImpulse(new Vec2(0, -4000));
// Force (continuous, applied per frame)rb.applyForce(new Vec2(100, 0));
// Rotationrb.setAngularVelocity(Math.PI);rb.applyTorque(50);Teleporting
Section titled “Teleporting”Use setPosition to teleport a body without interpolation artifacts:
rb.setPosition(400, 300); // pixelsRaycasting
Section titled “Raycasting”Cast a ray to find the first entity hit:
const world = this.use(PhysicsWorldManagerKey).getOrCreateWorld(this.scene);
const hit = world.raycast( origin, // Vec2Like — start position in pixels { x: 0, y: 1 }, // direction (normalized) 100, // max distance in pixels { filterGroups: CollisionLayers.interactionGroups(LAYER_PLAYER, LAYER_WALL) },);
if (hit) { console.log(hit.entity); // Entity that was hit console.log(hit.point); // Vec2 — hit position in pixels console.log(hit.normal); // Vec2 — surface normal console.log(hit.distance); // number — distance in pixels}A common use case is ground detection for platformers:
const grounded = world.raycast( this.transform.worldPosition, { x: 0, y: 1 }, playerHeight / 2 + 2,) !== null;Gravity Control
Section titled “Gravity Control”Change gravity at runtime via the PhysicsWorld:
const worldMgr = this.use(PhysicsWorldManagerKey);const world = worldMgr.getOrCreateWorld(this.scene);
world.setGravity(0, -980); // flip gravity upwardworld.setGravity(0, 0); // zero gravityPer-body gravity scaling is set via gravityScale in RigidBodyConfig.
Runtime Body Configuration
Section titled “Runtime Body Configuration”Lock or unlock axes at runtime:
rb.setEnabledTranslations(true, false); // lock Y axisrb.lockRotations(true); // lock rotationContinuous Collision Detection
Section titled “Continuous Collision Detection”Enable CCD on fast-moving bodies to prevent tunneling through thin colliders:
entity.add(new RigidBodyComponent({ type: "dynamic", ccd: true,}));CCD adds a small performance cost — only enable it for bullets, fast projectiles, or small objects that move at high speed.
PhysicsWorld Access
Section titled “PhysicsWorld Access”Access the physics world from any component:
import { PhysicsWorldManagerKey } from "@yagejs/physics";
const worldMgr = this.use(PhysicsWorldManagerKey);const world = worldMgr.getOrCreateWorld(this.scene);Each scene gets its own physics world. The PhysicsWorldManager creates and
manages worlds per-scene automatically.