game dev primer

Gravity &
Particle Effects

Physics for the game developer — from a single falling dot to fire, explosions, and orbital systems.

6 sections interactive demos particle physics
Contents
  1. A Particle Is Just a Number§1
  2. Emitters and Particle Pools§2
  3. Forces: Gravity, Wind, and Drag§3
  4. Life, Death, and Color Over Time§4
  5. Collision and Restitution§5
  6. Capstone: Explosions, Fire, and Gravity Wells§6
§ 01

A Particle Is Just a Number

Open a particle system codebase and it can feel overwhelming — emitters, pools, force fields, blend modes. But strip it all away and there's one idea underneath everything:

A particle is a position that changes over time according to a velocity that changes over time according to an acceleration.

That sentence is the whole physics engine. In code, a particle is a struct with three fields. On each frame you apply two updates in order:

velocity += acceleration * dt position += velocity * dt

That's Euler integration — the roughest possible numerical integrator, and the one every game particle system uses because it's fast and the error doesn't matter when particles only live for a second or two.

Gravity is just an acceleration with a fixed downward direction: ay = 9.8 m/s². Nothing more exotic than that. In a 2D canvas where y increases downward, you add to vy each frame. The particle accelerates, moves, and traces a parabolic arc.

// The complete single-particle state
const p = {
  x: 200, y: 50,   // position (pixels)
  vx: 80, vy: -20,  // velocity (pixels/sec)
};

const GRAVITY = 320; // px/s², roughly 9.8 m/s² scaled to screen

function update(dt) {
  p.vy += GRAVITY * dt; // integrate acceleration → velocity
  p.x  += p.vx  * dt;  // integrate velocity → position
  p.y  += p.vy  * dt;
}

Hit the canvas below and watch a single particle launch from your click position. The trail shows its path. Try launching from different spots — the parabola shape never changes, only the starting velocity does.

Demo 1 · Single Particle Click to Launch
click the canvas
320
280

Notice what happens at gravity = 0: the particle drifts in a straight line forever. That's inertia — Newton's first law. Every particle effect you build is just this: particles in inertial motion, nudged by forces.

Why pixels per second, not meters? Games live in screen space. There's no physical "meter" — only pixels. You pick a gravity constant that looks right for your game. A platformer might feel best at 800 px/s², while a space game feels right at 40. The physics don't change; you're just choosing your unit scale.

The trail and time

The fading trail above is drawn by clearing the canvas to a semi-transparent black each frame instead of fully opaque black. The "memory" of previous positions persists as fade-out ghosts. This is a common trick — it costs nothing and gives motion a sense of history.

· · ·
§ 02

Emitters and Particle Pools

One particle is a toy. A hundred is a system. The question shifts from "how does one particle move?" to "how do we manage many?"

The answer in every production particle system is a pool: a pre-allocated array of particle slots, each marked either alive or dead. When the emitter wants to spawn a new particle, it finds the first dead slot and initializes it. When a particle's lifetime expires, it's marked dead — its slot is available for the next spawn. No allocation, no garbage collection, just slot flipping.

const POOL_SIZE = 500;
const pool = Array.from({ length: POOL_SIZE }, () => ({
  alive: false, x: 0, y: 0, vx: 0, vy: 0
}));

function spawn(x, y, vx, vy) {
  const slot = pool.find(p => !p.alive);
  if (!slot) return; // pool exhausted — drop this spawn
  slot.alive = true;
  slot.x = x; slot.y = y;
  slot.vx = vx; slot.vy = vy;
}

The emitter is a point in space that fires new particles at some rate — say, 60 per second. Each spawned particle gets a random velocity spread around some base direction. That spread is what makes a fountain look like a fountain instead of a laser beam.

vx = baseVx + (Math.random() - 0.5) * spread vy = baseVy + (Math.random() - 0.5) * spread
Demo 2 · Particle Emitter Live
0 particles
40
120
2.0

Pull the Lifetime slider to its minimum. The fountain stays the same shape but particles vanish almost immediately. Now pull it high — the same fountain becomes a dense cloud. Lifetime is one of the most powerful controls for the visual feel of an effect.

The burst mode is just the same emitter fired all at once (rate = ∞ for one frame, then 0). Click anywhere on the canvas to trigger one. Explosions are just bursts.

Exercise

Reverse the gravity mentally

The fountain is shooting upward and gravity pulls it back down. What happens if you set gravity to 0? You'd get a cone expanding outward forever — like stars ejected from a supernova. What if gravity is negative (upward)? You'd get a drain effect: particles swirling upward, narrowing as they accelerate. The same emitter, the same spread math — only the force changes the story.

· · ·
§ 03

Forces: Gravity, Wind, and Drag

So far particles have one force: gravity, a constant downward acceleration. Real effects layer multiple forces. Each force just adds an acceleration contribution each frame.

Wind is a horizontal acceleration — same idea as gravity, different direction. The particles drift sideways. Combine wind and gravity and you get the slanting rain of every storm scene.

Drag (air resistance) is the force that makes smoke billow instead of shooting off like bullets. It's a force that opposes velocity, proportional to speed. The formula is simple:

acceleration_drag = -velocity * drag_coefficient

Or equivalently, you multiply velocity by a factor slightly less than 1 each frame:

// Drag: exponential velocity decay
p.vx *= (1 - drag * dt);
p.vy *= (1 - drag * dt);

// Wind: constant horizontal push
p.vx += wind * dt;

// Gravity: constant downward pull
p.vy += gravity * dt;

The interaction of drag and gravity is what makes particles reach a terminal velocity — the speed at which drag exactly cancels gravity. Heavy particles (low drag) fall fast; light ones (high drag) drift slowly. Adjust both sliders below to feel the difference.

Demo 3 · Forces Live
200
0
0.5
0

The turbulence slider adds a small random velocity jitter each frame — not a real physical force, just noise. But it's what separates smoke from confetti. Physical accuracy isn't the goal; visual plausibility is. Games use cheap tricks everywhere.

In games, the question is never "is this physically accurate?" It's "does the player believe it?" Those are surprisingly different questions.

Point attractors Another common force is an attractor: a point in space that pulls (or repels) particles. The force magnitude falls off with distance — usually as 1/r² in real gravity, but games often use 1/r (softer) or even a constant (simpler). Black holes, magnets, drain effects, and orbital systems are all attractor forces.
· · ·
§ 04

Life, Death, and Color Over Time

Every particle has a limited lifespan. Tracking that lifespan lets you do one of the most powerful things in particle design: make properties evolve over the particle's lifetime.

Each particle carries a life value that starts at 1.0 and counts down to 0.0 over its lifetime. This single float is the key to everything that changes:

// Each frame:
p.life -= dt / p.maxLife;   // counts 1.0 → 0.0
if (p.life <= 0) p.alive = false;

// Use life to derive everything else:
const t = p.life;                          // 1 = just born, 0 = about to die
const alpha = t;                           // fade out linearly
const size  = lerp(0, 6, t);             // shrink over time
const color = lerpColor(endColor, startColor, t);

The lerp function is everywhere in particle work: lerp(a, b, t) = a + (b-a)*t. It interpolates between two values based on the lifetime progress. Color lerping is just three lerps, one per channel.

The demo below shows the same fountain with different color-over-lifetime presets. Notice how much personality each one adds — the same physics, radically different feel.

Demo 4 · Life & Color Live
2.5
5

The "Smoke" preset is instructive: particles start small and dark, grow larger and lighter over their lives. This mimics how real smoke expands and fades. The physics are identical to the fire particles — only the color and size curves differ. The artist's job in a particle system is almost entirely about tuning these curves.

Easing curves

Linear lerp produces mechanical, robotic motion. Game particle systems usually use easing curves on the lifetime — squaring the value (ease-in), or using 1 - (1-t)² (ease-out). Squaring the alpha produces a particle that stays bright for most of its life and fades quickly near the end — much more natural than a linear fade.

alpha = t * t // slow bright-to-dim at end alpha = 1-(1-t)*(1-t) // fast dim-to-bright at birth alpha = t < 0.5 ? 2t² : 1-2(1-t)² // smooth step
· · ·
§ 05

Collision and Restitution

Particles that pass through floors and walls look wrong instantly. Adding collision detection is straightforward for flat surfaces: check if the particle has crossed the boundary, move it back, and reflect its velocity.

The key quantity is the restitution coefficient (bounciness) — a value from 0 (perfectly inelastic, dead stop) to 1 (perfectly elastic, no energy lost). Multiply the outgoing velocity component by this value after reflection:

// Floor collision at y = floorY
if (p.y >= floorY) {
  p.y  = floorY;          // push back above floor
  p.vy = -p.vy * restitution; // reflect and lose energy
  p.vx *= friction;       // friction slows horizontal motion
}

// Wall collisions are the same idea, horizontal axis
if (p.x <= 0 || p.x >= width) {
  p.vx = -p.vx * restitution;
}

At restitution = 0, particles splat on the floor — think mud, blood, wet paint. At restitution = 0.9, they bounce energetically for a long time. At restitution = 1.0, they never stop — physically correct but visually exhausting in games. Most real effects use values between 0.2 and 0.7.

Demo 5 · Collision Live
0.50
0.85
400

The "Hot Sparks" preset uses a high restitution with small particles, adding a short wake trail. The sparks bounce and scatter like metal sparks off a grinder. The same physics but very different character than the rain — because rain has restitution near zero (drops don't bounce; they splat).

Sleep thresholds Bouncing particles with non-zero restitution technically bounce forever, just with smaller and smaller bounces. In practice you add a sleep threshold: if the particle's speed falls below some minimum, kill it or freeze it. This is critical for performance — without it you can end up with thousands of technically-alive particles barely twitching on the floor.
Exercise

Drag and collision

Try adding a mental drag force to the rain demo. With drag, raindrops fall at terminal velocity and don't accelerate all the way down. Real raindrops hit the ground at around 9 m/s regardless of how high they started — because drag limits their speed. In the marbles preset, drag would make them feel like they're falling through oil rather than air. The gravity and restitution values are the same; drag is what changes the narrative.

· · ·
§ 06

Capstone: Explosions, Fire, and Gravity Wells

Everything from the previous five sections combines into three canonical game effects. Each is built from the same primitives — pools, emitters, forces, lifetime curves — just configured differently.

Explosion

An explosion is a single burst emission: all particles spawn simultaneously from one point with velocities spread in all directions. The key parameter is the velocity distribution — a uniform circle gives a clean ring; a gaussian distribution gives a denser center with falloff; adding an outward bias gives the classic shockwave look.

A shockwave layer is often added as a separate ring-shaped effect with a very short lifetime that expands rapidly outward — separate from the particle burst but timed together.

Fire

Fire is a continuous emitter, not a burst. Particles spawn from a base area, drift upward (negative gravity or a strong upward base velocity), and use a fire color curve: white-hot at birth, yellow, orange, then fade to red and transparent at death. Adding slight horizontal turbulence makes it flicker. Smoke particles often emit from the top of the fire region with their own inverse color curve — dark at birth, lighter gray as they rise and expand.

Gravity Well

A gravity well applies a point-attractor force: at each frame, compute the vector from each particle to the well center, normalize it, and add a scaled version of it to the particle's velocity. The force falls off with distance (usually 1/r² or 1/r). Particles spiral inward and either vanish when they get too close, or orbit if their tangential velocity is high enough.

The demo below lets you switch between all three. In gravity well mode, click to reposition the well center.

Demo 6 · Capstone Effects Live
1.0
0.50

Each of these effects has hundreds of parameters you could tune. But the underlying system is the same six lines of physics from §1: position, velocity, acceleration, and a force list. The visual variety comes entirely from how you configure the emitter and color curves.

A great particle artist doesn't write different physics for every effect. They write one physics loop and spend their craft on the configuration.

What comes next

Everything here runs on the CPU — fine for a few hundred particles. At tens of thousands you move to GPU particles: particles stored in textures, physics computed in a fragment shader (using the same UV math from §2 and §3 of any shader primer), and the draw step handled by instanced rendering. The physics equations don't change at all. Only the hardware they run on does.

Sub-emitters (particles spawning particles — sparks off of an explosion leaving smoke trails), sprite sheets for non-point particles, and collision with arbitrary shapes are natural next steps. But they're all built on the same foundation you've been playing with here.