■ Normal
■ Compressed
Flip open any physics-heavy game and you will find springs lurking everywhere. The satisfying wobble of a jelly platform, the gentle lag of a camera following a sprinting character, the dramatic snap of a ragdoll collapsing — all of these are spring physics in disguise. Spring simulation is one of the most rewarding mechanics to implement: the math is elegant, the behavior is immediately intuitive, and a handful of lines of code can produce results that look genuinely alive.
In this article we will build up spring physics from first principles. We will start with the foundational law that governs all spring behavior, derive how to simulate two masses connected by a spring, extend that to full chains and cloth, and look at the numerical integration tricks that keep simulations stable. By the end you will have the tools to add springy, physical motion to almost anything in your game.
What Springs Do in Games
Before diving into equations, it helps to survey just how broadly spring physics appears in shipped games:
- Camera systems — A spring between the desired camera position and its actual position gives smooth, organic follow behavior. This is how most third-person games achieve that characteristic lag.
- Hair and cloth — Characters in games like Horizon Zero Dawn and Bayonetta simulate hair as chains of spring-connected particles.
- Rope and chains — Games like Unravel and Teardown build rope entirely from spring physics.
- Soft bodies — Wobbly jelly enemies, inflatable obstacles, and squishy terrain in games like Gang Beasts use spring-mass networks.
- UI animations — Overshoot-and-settle animations in menus, HUD elements bouncing into position, health bars that stretch — all spring physics applied to 2D values.
- Ragdolls — Joint constraints in ragdoll systems often use spring forces to keep limbs within natural ranges.
The underlying math is the same in every case: a restoring force that pulls a displaced object back toward an equilibrium position.
Hooke's Law: The Foundation
Robert Hooke described the behavior of elastic materials in 1676 with a deceptively simple relationship. For a spring stretched or compressed by a displacement $x$ from its natural (rest) length, the restoring force is:
$$F = -kx$$
The constant $k$ is the spring stiffness (also called the spring constant). A high $k$ means a stiff spring that resists deformation strongly; a low $k$ means a soft, compliant spring. The negative sign is crucial — the force always opposes the displacement, pulling the object back toward equilibrium.
This one-dimensional law scales naturally into multiple dimensions. If two masses are connected by a spring with rest length $L_0$, and their current separation vector is $\vec{d}$, the spring force on mass A toward mass B is:
$$\vec{F}_{spring} = k \left(|\vec{d}| - L_0\right)\hat{d}$$
where $\hat{d}$ is the unit vector from A to B. Mass B receives the equal and opposite force. When $|\vec{d}| > L_0$ the spring is stretched and the force pulls both masses together. When $|\vec{d}| < L_0$ the spring is compressed and the force pushes them apart.
The Two-Mass Spring System
The simplest interesting spring system has exactly two masses and one spring between them. Let mass A sit at position $\vec{p}_A$ with velocity $\vec{v}_A$, and mass B at $\vec{p}_B$ with velocity $\vec{v}_B$. Each frame we:
- Compute the displacement vector: $\vec{d} = \vec{p}_B - \vec{p}_A$
- Compute the current length: $L = |\vec{d}|$
- Compute the extension: $e = L - L_0$
- Compute force magnitude: $f = k \cdot e$
- Compute unit direction: $\hat{d} = \vec{d} / L$
- Apply force $f\hat{d}$ to A and $-f\hat{d}$ to B
From Newton's second law ($F = ma$), the acceleration each mass receives is $\vec{a} = \vec{F} / m$. In many game simulations all masses are given equal weight of 1 kg, simplifying things so that $\vec{a} = \vec{F}$ directly.
Numerical Integration: Euler's Method
Physics simulations run in discrete time steps, not continuous time. We need a way to advance positions and velocities from one frame to the next. The simplest approach is explicit (forward) Euler integration:
$$\vec{v}_{new} = \vec{v}_{old} + \vec{a} \cdot \Delta t$$
$$\vec{p}_{new} = \vec{p}_{old} + \vec{v}_{new} \cdot \Delta t$$
Each frame, accumulate all forces acting on a mass, compute the acceleration, update velocity, then update position. Simple and fast — but Euler integration has a notorious flaw with springs: it adds energy to the system at large timesteps. Instead of oscillating and settling, a spring simulated with Euler integration can amplify its own motion until it explodes.
Two practical fixes exist. First, use a fixed small timestep. Rather than passing the raw frame delta (which might be 33ms or worse during a hitch), subdivide the step into multiple smaller substeps:
const SUBSTEPS = 4;
const subDt = deltaTime / SUBSTEPS;
for (let i = 0; i < SUBSTEPS; i++) {
applyForces(subDt);
integrate(subDt);
}Second, use semi-implicit Euler (also called symplectic Euler): update velocity first, then use the new velocity to update position. This small change makes the integration energy-conserving rather than energy-adding, dramatically improving stability.
Damping: Making Springs Settle
Without any damping, a perfect spring oscillates forever. Real springs lose energy to heat and air resistance. In games we model this with a damping force proportional to velocity:
$$\vec{F}_{damping} = -b\vec{v}$$
The coefficient $b$ controls how quickly motion dies out. An equivalent and simpler approach common in games is to multiply velocity by a damping factor slightly less than 1.0 each step:
velocity.x *= 0.985;
velocity.y *= 0.985;This is called velocity damping or linear drag. With a factor of 0.985 per step at 60fps, velocity loses roughly half its magnitude every 45 frames — about 0.75 seconds. Adjusting this single value gives you the full range from an underdamped system (springy, overshoots) to an overdamped system (sluggish, no oscillation) to the critically damped sweet spot (settles as fast as possible without oscillating).
For spring chains you can also apply spring damping — a damping force that resists the relative velocity along the spring axis, not just absolute velocity. This adds more realism but is more expensive to compute:
// Relative velocity along spring axis
const relVx = mb.vx - ma.vx;
const relVy = mb.vy - ma.vy;
const relVAlongSpring = relVx * nx + relVy * ny;
const dampForce = -springDamping * relVAlongSpring;
// Apply dampForce along spring axis to both massesSpring Chains: Simulating Ropes
Extend the two-mass system to $N$ masses each connected to the next by a spring, and you have a simulated rope. The physics loop simply iterates over every spring and applies forces to both connected masses:
function applySpringForces(masses, springs, dt) {
for (const spring of springs) {
const a = masses[spring.indexA];
const b = masses[spring.indexB];
const dx = b.x - a.x;
const dy = b.y - a.y;
const dist = Math.sqrt(dx * dx + dy * dy) || 0.0001;
const stretch = dist - spring.restLength;
const forceMag = spring.k * stretch;
const nx = dx / dist;
const ny = dy / dist;
if (!a.fixed) {
a.vx += forceMag * nx * dt;
a.vy += forceMag * ny * dt;
}
if (!b.fixed) {
b.vx -= forceMag * nx * dt;
b.vy -= forceMag * ny * dt;
}
}
}Pin the first mass in place (mark it as fixed) and the chain hangs under gravity. Pin both endpoints and it forms a catenary curve — the same shape as a real hanging cable. Remove all pins and the whole chain flies through the air as a floppy rope.
Cloth Simulation: Extending to 2D Grids
Cloth is simply a 2D grid of spring-connected masses instead of a 1D chain. A 10×10 grid of masses with springs connecting each mass to its neighbors (horizontal, vertical, and diagonal) produces believable cloth behavior. Three types of springs are typical:
- Structural springs — connect each mass to its immediate horizontal and vertical neighbors. Resist stretching.
- Shear springs — connect diagonal neighbors. Resist shearing deformation.
- Bend springs — connect masses two steps away. Resist sharp bending/folding.
The simulation loop is identical — just more springs. A 10×10 grid has 100 masses and roughly 360 springs, still extremely fast on modern hardware.
Distance Constraints: A More Stable Alternative
Very stiff springs ($k$ approaching infinity) require tiny timesteps to stay stable. For inextensible ropes or rigid constraints, position-based dynamics (PBD) offers a better approach. Instead of applying forces, you directly correct positions after integration:
function satisfyConstraint(a, b, restLength) {
const dx = b.x - a.x;
const dy = b.y - a.y;
const dist = Math.sqrt(dx * dx + dy * dy) || 0.0001;
const correction = (dist - restLength) / dist * 0.5;
if (!a.fixed) {
a.x += dx * correction;
a.y += dy * correction;
}
if (!b.fixed) {
b.x -= dx * correction;
b.y -= dy * correction;
}
}Running this constraint solver multiple times per frame (typically 3–10 iterations) converges toward the correct configuration. PBD is used in Half-Life 2's rope system and is the basis for most cloth solvers in AAA engines. It is unconditionally stable regardless of timestep size, making it far more practical for game use than force-based stiff springs.
Spring Physics in Real Games
Understanding the mechanic opens your eyes to where it appears:
- Angry Birds — The slingshot band is a spring constraint. Pull back, release, and the stored spring energy converts to kinetic energy.
- Ori and the Blind Forest — Character hair and environmental elements like dangling lanterns use spring chains.
- Half-Life 2 — Physics objects, ragdolls, and rope bridges all use spring-mass systems in the Havok physics engine.
- Minecraft — The bouncy animation of dropped items is a simple vertical spring.
- Celeste — The spring dash-pad gives Celeste her vertical launch using a compressed spring metaphor in both gameplay and visuals.
- Suika Game / Watermelon Game — Fruit collisions use soft-body springs to give the satisfying squish on impact.
Tuning Spring Systems
Getting springs to feel right is as much art as science. A few rules of thumb:
- Start with $k = 50$–$200$ for gameplay-scale springs (masses ~1 unit apart).
- Damping around $0.98$–$0.995$ per frame feels natural at 60fps.
- Use 4–8 substeps for stability with high $k$ values.
- For camera follow, $k = 8$–$20$ with heavy damping gives cinematic lag.
- For UI springs (menu elements), $k = 200$–$400$ with damping ~$0.85$ gives snappy, bouncy UI.
The interactive demo below lets you drag masses in a hanging spring chain. Watch how changing nothing but the stiffness and damping constants changes the entire character of the simulation — from a slack wet noodle to a rigid metal rod.
Comments