Astral Republic

✱ LABORATORY · Interactive

How Does a Web Wobble and Trap Prey — Verlet Spider Web

A few dots and threads, one rule — 'keep your distance' — repeated every frame, and you get a real-feeling wobbling web. Spin your own and trap the enemies.

🔍 What is this?

It started from a tiny game prototype I saw on X. A spider spins thread to build a web, runs from the red dots (enemies), and widens the web to lure and trap them. When an enemy hits a strand, the strand wobbles and stretches, and anything tangled in it gets caught.

The surprising part: this wobbling web has no real “physics engine” behind it. It’s just a handful of points plus one rule — “keep your distance from each other” — repeated every frame. This is called Verlet integration.

⚙️ How it works — motion from position alone

Most physics stores and updates velocity. Verlet is different. It doesn’t store velocity — it only remembers the “previous position.”

new position = current position + (current − previous) + acceleration
                                   └──── this IS velocity ────┘

current − previous automatically plays the role of velocity. That keeps the code simple and, unlike Euler integration, it rarely blows up (it’s stable). Webs, cloth, rope, and hair all use this.

A web is just two repeated steps:

  1. Integrate — nudge every point forward one step via Verlet (with a touch of gravity).
  2. Constraint — if the distance between two connected points drifts from its rest length, pull/push them back. Repeat this several times and the strand gets stiffer (= stiffness).

The outer points are pinned (anchors) so they don’t move; only the strands between them wobble.

▶️ Try it yourself

● LIVE DEMO↑↓←→ 거미줄 타고 주행 · 클릭으로 실 발사
점수 0/50 · 실 5/10 · 0s · 적 0
HP
8/8

방향키 ↑↓←→ (또는 WASD)로 거미줄을 타고 주행. 클릭하면 그 방향으로 실을 쏴 새 줄을 잇는다 — 붙을 줄이 없으면 빈 곳에 앵커를 박는다(지금 탄 줄과 거의 평행한 방향만 실패). 적(빨강)은 줄에 걸리면 한동안 발버둥치다 죽는다 — 발버둥 중 거미에 닿으면 HP가 깎인다(노랑·주황 링 = 강한 적, 2~3 데미지). 죽어 회보라색이 된 적 위를 거미가 지나가면 회수돼 점수 +1, HP도 회복. HP 0이 되면 게임 오버. 커터가 매달린 줄을 모두 끊으면 거미가 추락해 게임 오버 — ↺로 다시 시작.

  • ↑↓←→ (or WASD): the spider (green) rides the web in that direction. At intersections it smoothly hops onto another strand that matches its heading. The camera follows the spider.
  • Click: the spider shoots thread in that direction. If there’s a strand it can attach to, it links there; strands too parallel to attach to are passed through; if nothing’s there, it extends to a set distance and drives an anchor into empty space. The only failure is a direction nearly parallel (±15°) to the strand it’s currently on.
  • Enemies (red dots) flicker into being near the spider and ride the web toward it. Caught on a strand, they thrash to escape (red→white) and slowly die; if the spider is nearby, the strand drags in and damages the spider. Once an enemy dies and turns purple-gray, the spider passing over it collects it for +1 point and HP recovery. At 0 HP the spider turns scarlet — game over.
  • Some enemies are “cutters.” If a cutter severs the strand the spider hangs from at every anchor, the spider loses all support and falls endlessly → after a brief drop, it’s death by falling (game over). Press ↺ to start a new game. But if thread remains while falling, you can shoot into empty space and hang on to survive — a very spider-like last move.
  • The background is a night sky — every so often a meteor (shooting star) streaks across.

💡 Play with the physics via the sliders. Raising web stiffness makes strands taut; raising gravity makes them sag. Catch force is how hard the strand shakes when an enemy is caught, and restore force is how fast a strand holding a dead enemy straightens back out.

🛠 My reproduction

The heart of it is just two functions. After integrating, the constraint repeats stiffness times.

// ① Verlet integration — velocity = current − previous
for (const n of nodes) {
  if (n.pinned) continue;
  const vx = n.x - n.px, vy = n.y - n.py;
  n.px = n.x; n.py = n.y;
  n.x += vx;  n.y += vy + gravity;
}

// ② distance constraint — pull both sides halfway by the error (repeat several times)
for (const e of edges) {
  const a = nodes[e.a], b = nodes[e.b];
  const dx = b.x-a.x, dy = b.y-a.y, d = Math.hypot(dx,dy);
  const diff = (d - e.rest) / d;      // rest = original length
  a.x += dx*0.5*diff;  a.y += dy*0.5*diff;
  b.x -= dx*0.5*diff;  b.y -= dy*0.5*diff;
}

The enemy deformation needed nothing extra. When an enemy catches on a strand, I insert a node at that spot (splitting the strand in two) and just tug that node in its travel direction — and the next frame’s distance constraint handles the wobble and recovery on its own. The “push → get pulled → spring back” elasticity comes for free. Studying the original video frame by frame, enemies pulled slowly like a weight, not in jerky bursts — so I apply a very small force on catch (catchForce) with heavy damping so it settles smoothly.

It’s a three-stage catch → death → collect. A caught enemy thrashes and slowly dies (red→white); meanwhile the strand drags toward the spider and damages it on contact. On death it becomes a corpse hanging in place, collected when the spider passes. A strand holding a dead enemy would look wrong kinked, so I pull it slightly toward the midpoint of its neighbors via relaxForce, straightening it gradually — with gravity it settles into a gently sagging curve.

// slowly straighten a strand holding a dead enemy (tension restore).
const mid = midpoint(neighborA, neighborB);
node.x += (mid.x - node.x) * relaxForce;
node.y += (mid.y - node.y) * relaxForce;

Firing thread casts a ray from the spider in the click direction and attaches to the first strand it hits (extending to a set distance if empty). The spider’s movement is expressed as a position value t (0–1) along a strand (edge); reaching an end, it hops onto another strand matching the input direction. Facing is interpolated toward the actual travel direction so it turns smoothly at intersections.

📝 Takeaways

It’s striking how one idea — “don’t store velocity” — makes such a difference. With no complex spring formulas, just repeating a keep-your-distance constraint yields wobbling cloth and webs. It’s intuitive that a single repeat count becomes the material’s stiffness.

The original was a game prototype thrown together with Gemini, but the real kernel inside was this tiny physics loop. The same engine can build capes, flags, rope bridges, and hair — nearly every wobbling cloth in games is a cousin of this.

📤Share𝕏Reddit

🧪 Related experiments