Phaser AE

Rope physics

Free packv3.0.0-02D and 3DPhysics

Ropes, cords and chains that swing, drape, stretch, snap and get cut. A rope carries weight, ties to fixed points, moving hooks or rigid bodies, and is cut by a swipe, in 2D or 3D. Rope games, from rope-cutting puzzles to bridges and cranes, build on it.

This is a Free pack. Every phaser.io account can use it in their games.

Released October 19, 2026

Rope physics: a screenshot of its demo
A note about these graphics

Expansion packs don't come with graphics. This screenshot is one way of drawing what the pack does, made for its demo: none of the sprites, tiles or backgrounds in it are in the pack.

That's by design. A pack is a game's logic, mechanics and effects, and it works with whatever art you give it. Some packs draw effects, backdrops or 3D scenes of their own, but none carries a game's sprites.

So the look is yours. Make the art on phaser.io, bring your own, or let your agent draw it in code: the same pack can look like anything.

The pack

physicsRope

function physicsRope(options: PhysicsRopeOptions = {}): Pack<{ ropes: RopeWorld }>

The physics-rope pack: game.ropes, a rope world stepped every fixed step, after physics-2d, physics-box2d and physics-3d so a rope tied to a body sees where the body went this step. Gravity defaults to 900 units/s² down in a 2D world and 9.81 m/s² down (y up) in a 3D one.

Functions

createRopeWorld

function createRopeWorld(options: RopeWorldOptions = {}): RopeWorld

Make a rope world. See RopeWorldOptions.

ropeContact

function contact(c: RopeCollider, x: number, y: number, z: number, r: number): Contact | null

The overlap of a ball at (x, y, z) of radius r with a shape, or null when they do not touch.

segmentsCross

function segmentsCross(p1x: number, p1y: number, p2x: number, p2y: number, q1x: number, q1y: number, q2x: number, q2y: number): boolean

Do the 2D segments p1-p2 and q1-q2 cross? Touching counts.

Classes

RopeWorld

class RopeWorld

The rope world. Make one with createRopeWorld, or compose the physics-rope pack for game.ropes.

  • readonly gravity: { x: number; y: number; z: number } — Gravity, live: change it whenever.
  • constructor(options: RopeWorldOptions = {})
  • addLoad(spec: RopeLoadSpec): RopeLoad — Add a load ropes can carry. Returns its handle.
  • load(id: string): RopeLoad | undefined — A load by id, or undefined.
  • removeLoad(id: string): void — Remove a load. Ropes tied to it are cut at that end.
  • addRope(spec: RopeSpec): Rope — Make a rope between two ends. Returns its handle.
  • rope(id: string): Rope | undefined — A rope by id, or undefined.
  • ropes(): Rope[] — Every rope still in the world.
  • removeRope(id: string): void — Remove a rope. Its loads stay.
  • cutAlong(path: ReadonlyArray<RopeVec>): { rope: string; segment: number }[] — Cut every rope segment the swipe crosses, this step or the last, so a fast swipe cannot pass between frames of a thin rope. The swipe is a polyline in the xy plane. Returns what was cut.
  • nearest(point: RopeVec, within = Infinity): { rope: string; segment: number; distance: number } | null — The nearest uncut rope segment to a point, within within units, or null.
  • addCollider(c: RopeCollider): string — Add a static collider. Returns its id.
  • removeCollider(id: string): boolean — Remove a collider.
  • colliders(): RopeCollider[] — Every collider.
  • step(dt: number): void — Advance by dt seconds of game time, in fixed steps. Call once per game step.
  • drain(): RopeEvent[] — What happened since the last drain, oldest first.
  • get dropped(): number — How many events were dropped because nobody drained them (past limits.events).
  • diagnostics(): RopeDiagnostics — The state of the last step.
  • snapshot(): RopeSnapshot — The whole world as plain JSON. follow and link anchors are recorded where they are; re-attach them after restore.
  • restore(snap: RopeSnapshot): void — Return to a snapshot. Throws on anything that is not one, leaving the world as it was.

Types

PhysicsRopeOptions

type PhysicsRopeOptions = RopeWorldOptions

The physics-rope options: the world's, with gravity defaulting to the composed world's convention.

Rope

interface Rope

A rope as the world holds it. Read it freely; change it through its methods.

  • readonly id: string — Its id.
  • readonly radius: number — Its thickness, a radius.
  • readonly segments: number — How many segments it has.
  • readonly length: number — Its rest length now (after any pay-out or reel), the sum of its segments' rest lengths.
  • readonly cuts: ReadonlyArray<boolean> — Which segments are cut.
  • readonly removed: boolean — Has it been removed from the world?
  • tension(segment: number): number — The pull on each segment in the last step, in force units (mass times length per second squared).
  • points(): number[][] — The particle positions along it, [x, y, z] each, from the first end.
  • runs(): number[][] — The uncut stretches of it as polylines, [x0, y0, z0, x1, y1, z1, …]: what to draw.
  • cut(segment: number): void — Cut a segment (0 is next to the first end). Its momentum is kept on both sides.
  • payOut(d: number): void — Let out d units of rope (negative reels in), between a fifth and four times the length it was made with.
  • attach(end: 'from' | 'to', to: RopeEnd): void — Tie an end to something else, or set it free (a point).
interface RopeBodyLink

A rigid body a rope end is tied to, from any physics pack: the rope pulls it and it pulls the rope. Give its mass, where it is, and how to push it; see the page for physics-2d, physics-box2d and physics-3d adapters.

  • readonly mass: number — The body's mass, in the same units as rope and load masses.
  • position(): RopeVec — Where the tie point is now, in world units.
  • push(ix: number, iy: number, iz: number): void — Apply an impulse (mass times velocity change) at the tie point.

RopeCollider

type RopeCollider = /** A circle in 2D, a sphere in 3D (give `z`). */ | { kind: 'circle'; id?: string; x: number; y: number; z?: number; radius: number; friction?: number } /** A segment with thickness: a peg, a bar, a branch. */ | { kind: 'capsule'; id?: string; ax: number; ay: number; az?: number; bx: number; by: number; bz?: number; radius: number; friction?: number } /** * A rectangle centred on (x, y), turned by `angle` radians about z. Give `depth` (and `z`) for a box in 3D; * without it the box reaches through every z. */ | { kind: 'box'; id?: string; x: number; y: number; z?: number; width: number; height: number; depth?: number; angle?: number; friction?: number } /** A convex polygon in the xy plane, its points in either winding. */ | { kind: 'polygon'; id?: string; points: ReadonlyArray<readonly [number, number]>; friction?: number } /** Thin walls along a polyline in the xy plane, closed or open, with an optional thickness. */ | { kind: 'chain'; id?: string; points: ReadonlyArray<readonly [number, number]>; closed?: boolean; radius?: number; friction?: number } /** Everything on the far side of a line (2D) or plane (3D): the points where n·p < offset are inside. */ | { kind: 'plane'; id?: string; nx: number; ny: number; nz?: number; offset: number; friction?: number }

A static shape ropes and loads collide with. friction (default 0.3) slows sliding along it, 0 is ice, 1 grips.

RopeContact

interface RopeContact

Where a ball overlaps a shape: the unit normal out of the shape and how deep it is.

  • nx: number — The unit normal pointing out of the shape, towards the ball: x.
  • ny: number — The normal's y.
  • nz: number — The normal's z (0 in 2D).
  • depth: number — How far the ball must move along the normal to just touch.

RopeDiagnostics

interface RopeDiagnostics

What the world reports about its last step.

  • time: number — Simulated seconds so far.
  • substeps: number — Substeps the last fixed step was split into.
  • saturated: number — Fixed steps so far that needed more than maxSubsteps: fast motion the solver had to run short.
  • maxStretch: number — The worst stretch of any segment beyond its rest length, as a fraction.
  • maxTension: number — The largest segment tension.
  • kineticEnergy: number — Kinetic energy of everything that moves.
  • settled: boolean — True when nothing moves faster than a tenth of a radius per second.

RopeEnd

type RopeEnd = /** Hanging free, starting here. */ | RopeVec /** Fixed at a point: a hook, a nail, a ceiling. */ | { fixed: RopeVec } /** Tied to a load made with `addLoad`, which the rope then carries. */ | { load: string } /** Moving with whatever the function says each step: a rotating hook, a sliding rail, a hand. */ | { follow: () => RopeVec } /** Tied to a body of a physics pack, pulling and pulled. */ | { link: RopeBodyLink }

What a rope end is tied to.

RopeEvent

type RopeEvent = | { kind: 'cut'; rope: string; segment: number; cause: 'call' | 'swipe' | 'tear' } | { kind: 'contact'; load: string; collider: string }

Something that happened in a step, for a game to react to.

RopeLoad

interface RopeLoad

A load as the world holds it: live position and velocity.

  • readonly id: string — Its id.
  • readonly x: number — Where it is, across.
  • readonly y: number — Where it is, along y.
  • readonly z: number — Where it is, along z (0 in 2D).
  • readonly vx: number — How fast it is moving along x, units per second.
  • readonly vy: number — How fast it is moving along y.
  • readonly vz: number — How fast it is moving along z.
  • readonly radius: number — Its radius.
  • gravityScale: number — Its gravity multiplier; set it to make a bubble rise (negative) or float (0).
  • impulse(ix: number, iy: number, iz?: number): void — Push it: an impulse, mass times velocity change.
  • place(x: number, y: number, z?: number, vx?: number, vy?: number, vz?: number): void — Put it somewhere, at rest or with a velocity.

RopeLoadSpec

interface RopeLoadSpec

A load a rope can carry: Cut the Rope's candy, a crate, a hanging lamp.

  • id: string — A unique id.
  • x: number — Where it starts, across.
  • y: number — Where it starts, along y.
  • z?: number — Where it starts, along z (3D). Default 0.
  • radius: number — Its radius, for colliders and other loads.
  • mass?: number — Its mass. Default 10.
  • gravityScale?: number — Gravity multiplier: 0 floats, negative rises (a bubble). Default 1.
  • vx?: number — Its starting velocity along x, units per second. Default 0.
  • vy?: number — Its starting velocity along y. Default 0.
  • vz?: number — Its starting velocity along z (3D). Default 0.

RopeSnapshot

interface RopeSnapshot

A whole world as plain JSON, from snapshot. Functions (follow, link) are not saved: re-attach them after restore.

  • format: 'phaser-ae-rope' — Always 'phaser-ae-rope'.
  • version: 1 — The snapshot format's version, 1.
  • time: number — Seconds simulated.
  • accumulator: number — Time carried over to the next fixed step, seconds.
  • saturated: number — How many steps ran short of substeps so far.
  • particles: { x: number[]; y: number[]; z: number[]; vx: number[]; vy: number[]; vz: number[]; inv: number[]; radius: number[]; gravity: number[]; load: (string | null)[] } — Every particle, as parallel arrays: position, velocity, inverse mass, radius, gravity scale, and its load id.
  • ropes: { id: string; particles: number[]; base: number[]; scale: number; cuts: boolean[]; radius: number; stretch: number; bend: number | null; tear: number | null; removed: boolean }[] — tear is null for a rope that never tears (JSON has no Infinity).
  • anchors: { particle: number; kind: 'fixed' | 'follow' | 'link'; x: number; y: number; z: number }[] — Every held particle and where it was held (a follow or link end is restored as fixed there).
  • colliders: RopeCollider[] — The colliders.
  • nextId: number — The counter that names ropes made without an id.

RopeSpec

interface RopeSpec

A rope to make.

  • id?: string — A unique id. Default one is made.
  • from: RopeEnd — The first end.
  • to: RopeEnd — The second end.
  • length?: number — The rope's rest length. Default: the distance between the ends as made, times 1 + slack, so a rope made between two points hangs from them.
  • slack?: number — Extra length beyond the distance between the ends, as a fraction. Default 0: taut. Ignored when length is given.
  • radius?: number — The thickness, a radius, in world units. Default 2 (pixel-sized 2D worlds); a 3D game in metres wants about 0.02.
  • segments?: number — How many segments. Default: enough that each is about two radii long, which keeps colliders from slipping through.
  • mass?: number — The rope's whole mass. Default 1. Loads are usually much heavier.
  • stretch?: number — Stretch compliance, 0 inextensible (default 0), larger is elastic: about 1e-4 is a bungee.
  • bend?: number — Bend compliance: how easily the rope folds. Leave it out for a rope that folds freely, as a rope or a chain does (the default): a slack rope then holds nothing up. Give one for a cord or cable that resists folding: smaller is stiffer, 0 a rod; about 0.02 suits a cord of mass 1, and a lighter rope needs a larger value to be as floppy.
  • tear?: number — A segment pulled harder than this (force units) snaps. Default Infinity: never.

RopeVec

interface RopeVec

A position. z is 0 when left out, which is every 2D game.

  • x: number — Across.
  • y: number — Down in a 2D game (y down), up in a 3D one (y up).
  • z?: number — Depth, 3D only. Default 0.

RopeWorldOptions

interface RopeWorldOptions

How a rope world behaves.

  • gravity?: RopeVec — Gravity, units per second squared. Default { x: 0, y: 900 }: a 2D world, y down. A 3D world in metres wants { x: 0, y: -9.81, z: 0 }.
  • step?: number — Fixed step, seconds. Default 1/120.
  • iterations?: number — Constraint passes per substep. Default 8; more is stiffer and costs more.
  • maxSubsteps?: number — The most substeps one fixed step may take. Default 16.
  • damping?: number — Velocity damping per second (air). Default 0.2.
  • ropeContact?: boolean — Do ropes collide with each other and with loads? Default false (Cut the Rope's ropes pass through each other).
  • flat?: boolean — Keep everything in the z = 0 plane. Default true unless gravity has a z.
  • limits?: { particles?: number; ropes?: number; colliders?: number; events?: number } — Resource bounds, so a runaway level fails loudly: particles (default 8192), ropes (512), colliders (1024), events kept (256).