A grapple-hook mechanic: fire a rope at an anchor point and free-fall becomes a pendulum swing. Reel the rope in or out mid-swing to trade arc for height, and release at the top of the arc to sling off with full momentum. The demo is a small gap-crossing arena — three girders strung across a pit, cleared by chaining swings rather than jumping.
Reuse
The mechanic lives in src/grapple-swing.js as a framework-free ES module:
createGrappler(opts)— returns a state object; callupdate(dt, input)each frame.opts:gravity,airControl,maxAirSpeed,minRope,maxRope,reelSpeed,releaseBoost.attach(anchor)/release()— the module doesn’t pick anchors or know about a level; the caller decides what’s grappleable (nearest in range, line-of-sight, whatever fits) and callsattach({x, y}).release()keeps whatever velocity the swing built up, timesreleaseBoost.input:{ moveX: -1..1, reel: -1..1 }— air control while free or swinging, reel in/out while attached.- State exposes
pos,vel,attached,anchor,ropeLength— draw the rope as a line fromanchortopos, however fits the project.
The core swing step: once attached, if the distance to the anchor exceeds
ropeLength, the position is projected back onto that circle and the
outward-radial component of velocity is stripped, leaving only the tangential
component. That’s the whole pendulum — no trig, no rope segments.
Port notes for DragonRuby: update() is ~30 lines of pure state math with no
DOM dependencies — translate directly into a tick method; the rope draws as
one line primitive from anchor to pos.
Gotchas
- The constraint only fires when the player is outside the rope radius —
swinging in tighter than
ropeLength(e.g. after reeling in) is unconstrained until it stretches taut again, which is correct pendulum behavior but can surprise you if you expected a rigid rod. release()is momentum-conserving by design; tunereleaseBoostdown to1.0if a project wants a pure sling with no extra pop.- The demo in
demo/bundles its own copy of the module (self-contained by contract). If you touchsrc/, re-copy it intodemo/.