Fully typed, tree-shakeable 2D physics engine — a modern TypeScript rewrite of the Nape Haxe physics engine.
Homepage & Interactive Demos | API Reference | Examples | Multiplayer Demo
Cookbook | Troubleshooting | Anti-Patterns
- Originally created in Haxe by Luca Deltodesco
- Ported to TypeScript by Istvan Krisztian Somoracz
This repo is an npm-workspaces monorepo:
| Package | What it is |
|---|---|
@newkrok/nape-js |
The physics engine itself — rigid bodies, constraints, collision, fluids, deterministic multiplayer. |
@newkrok/nape-pixi |
PixiJS v8 integration — BodySpriteBinding, FixedStepper (render interpolation), PixiDebugDraw, WorkerBridge. |
npm install @newkrok/nape-js
# optional: PixiJS v8 integration
npm install @newkrok/nape-pixi pixi.jsimport { Space, Body, BodyType, Vec2, Circle, Polygon } from "@newkrok/nape-js";
// Create a physics world with downward gravity
const space = new Space(new Vec2(0, 600));
// Static floor
const floor = new Body(BodyType.STATIC, new Vec2(400, 550));
floor.shapes.add(new Polygon(Polygon.box(800, 20)));
floor.space = space;
// Dynamic box
const box = new Body(BodyType.DYNAMIC, new Vec2(400, 100));
box.shapes.add(new Polygon(Polygon.box(40, 40)));
box.space = space;
// Dynamic circle
const ball = new Body(BodyType.DYNAMIC, new Vec2(420, 50));
ball.shapes.add(new Circle(20));
ball.space = space;
// Game loop
function update() {
space.step(1 / 60);
for (const body of space.bodies) {
console.log(`x=${body.position.x.toFixed(1)} y=${body.position.y.toFixed(1)}`);
}
}Full API documentation: TypeDoc Reference
| Class | Description |
|---|---|
Space |
Physics world — add bodies, step simulation, deterministic mode for rollback/prediction |
Body |
Rigid body with position, velocity, mass |
Vec2 |
2D vector — pooling, clone(), equals(), lerp(), fromAngle() |
Vec3 |
3D vector for constraint impulses — clone(), equals() |
AABB |
Axis-aligned bounding box — clone(), equals(), fromPoints() |
Mat23 |
2×3 affine matrix — clone(), equals(), transform, inverse |
Ray |
Raycasting — clone(), fromSegment(), spatial queries |
| Class | Description |
|---|---|
Circle |
Circular shape |
Polygon |
Convex polygon (with Polygon.box(), Polygon.rect(), Polygon.regular()) |
Capsule |
Capsule shape (Capsule.create(), Capsule.createVertical()) |
Shape |
Base class with material, filter, sensor support |
| Class | Description |
|---|---|
Material |
Elasticity, friction, density |
BodyType |
STATIC, DYNAMIC, KINEMATIC |
InteractionFilter |
Bit-mask collision/sensor/fluid filtering |
FluidProperties |
Density, viscosity for fluid shapes |
| Class | Description |
|---|---|
PivotJoint |
Pin two bodies at a shared point |
DistanceJoint |
Constrain distance between anchors |
WeldJoint |
Fix relative position and angle |
AngleJoint |
Constrain relative angle |
MotorJoint |
Apply angular velocity |
LineJoint |
Slide along a line |
PulleyJoint |
Constrain combined distances |
| Class | Description |
|---|---|
InteractionListener |
Collision/sensor/fluid events |
BodyListener |
Body wake/sleep events |
ConstraintListener |
Constraint events |
PreListener |
Pre-collision filtering |
CbType |
Tag interactors for filtering |
CbEvent |
BEGIN, ONGOING, END, WAKE, SLEEP, BREAK |
| Class | Description |
|---|---|
NapeList<T> |
Iterable list with for...of support |
MatMN |
Variable-sized M×N matrix — clone(), equals(), multiply, transpose |
Full physics state snapshot/restore — suitable for save/load, replay, and multiplayer server↔client synchronization.
import "@newkrok/nape-js";
import { spaceToJSON, spaceFromJSON } from "@newkrok/nape-js/serialization";
// Serialize
const snapshot = spaceToJSON(space);
const json = JSON.stringify(snapshot);
// Restore (e.g. on another machine / after network transfer)
const restored = spaceFromJSON(JSON.parse(json));
restored.step(1 / 60);The /serialization entry point is tree-shakeable — it does not pull in the engine
bootstrap when unused. The snapshot captures bodies, shapes, materials, interaction
filters, fluid properties, all constraint types (except UserConstraint), and compounds.
Arbiters and broadphase tree state are reconstructed automatically on the first step.
Run physics off the main thread for smooth rendering even with hundreds of bodies.
import "@newkrok/nape-js";
import { PhysicsWorkerManager } from "@newkrok/nape-js/worker";
const mgr = new PhysicsWorkerManager({ gravityY: 600, maxBodies: 256 });
await mgr.init();
const id = mgr.addBody("dynamic", 100, 50, [{ type: "circle", radius: 20 }]);
mgr.start();
// Read transforms on the main thread (zero-copy with SharedArrayBuffer)
function render() {
const t = mgr.getTransform(id);
if (t) drawCircle(t.x, t.y, t.rotation);
requestAnimationFrame(render);
}
render();Uses SharedArrayBuffer for zero-copy transform sharing when COOP/COEP headers are
present, with automatic postMessage fallback otherwise.
- Zero-friction tunneling — Bodies with zero-friction material and horizontal
velocity may tunnel through floors. This affects all shape types (circles,
polygons, capsules). Workaround: use small friction values (e.g.
0.01).
npm install
npm run build # tsup → packages/*/dist/ (ESM + CJS + DTS)
npm test # vitest — 5275 engine tests + 71 pixi-adapter tests
npm run benchmark # Performance benchmarksReleases are automated: see docs/guides/workflow.md
for the per-package auto-release pipeline. Run
node scripts/ci/release.mjs --dry-run from the repo root to preview what
would publish on the next master merge.
MIT