Wishlist on Steam
No.30

Projectile Trajectories & Ballistics

From grenade arcs in Halo to artillery shells in World of Tanks, projectile ballistics is one of gaming's most fundamental physics mechanics. Learn the kinematic equations, inverse aiming solutions, and arc visualization techniques that power it all.

g
Published
Reading time
8 min
Demos
One to play with
Author
Kieren Hovasapian
Interactive 3D · Drag to orbit · scroll to zoomOpen full screen

Every time a grenade arcs perfectly through a doorway in Halo, every time an arrow drops gracefully in Zelda, every time a mortar shell crests its apex and plunges toward a target in Hell Let Loose, all of it follows the same physics: projectile trajectories and ballistics.

Ballistic motion describes an object that has been given an initial velocity and then moves under gravity alone. It's fundamental to physics simulation. Master it, and you unlock everything from simple thrown grenades to sophisticated artillery aiming systems, predictive enemy AI, and arc-preview UI. The underlying math is beautiful and surprisingly compact.

The Core Insight: Independent Components

The key insight in projectile motion is that horizontal and vertical motion are completely independent of each other. A bullet fired horizontally from a gun hits the ground at exactly the same time as a bullet dropped from the same height. Gravity pulls them down identically regardless of any horizontal velocity.

This means you can break any launch into two separate 1D problems, solve each one, and combine the results. Given a launch angle (measured from the horizontal) and initial speed , the initial velocity components are:

Horizontal velocity stays constant throughout the flight (ignoring air resistance). Vertical velocity decreases at a rate of (gravitational acceleration, roughly on Earth, though most games use a higher value for snappier feel).

The Kinematic Equations

With those components in hand, position at any time is given by the standard kinematic equations:

The horizontal equation is linear—constant-speed motion. The vertical equation is quadratic—constant-acceleration motion. Together they trace out a parabola.

These two equations are your entire ballistics engine. Everything else—time of flight, range, peak height, inverse aiming—is derived from them.

Time of Flight

When does the projectile return to launch height? Set and solve:

The non-trivial solution (excluding ) is:

Maximum Height

The peak occurs when vertical velocity reaches zero: , giving . Substituting back:

Horizontal Range

Range is how far the projectile travels before returning to launch height:

Range is maximized when , which means . A 45-degree launch always achieves maximum range for a given speed on flat terrain. Complementary angles (e.g., 30° and 60°) produce identical ranges. The low arc and high arc land in the same spot.

Implementing Ballistics in Code

The simplest runtime implementation uses a velocity-integration loop. Each frame, apply gravity to the vertical velocity, then move the projectile:

JavaScript
class Projectile {
  constructor(x, y, angleDeg, speed) {
    const a = angleDeg * Math.PI / 180;
    this.x  = x;
    this.y  = y;
    this.vx = speed * Math.cos(a);
    this.vy = speed * Math.sin(a);
    this.alive = true;
  }

  update(dt, gravity = 9.8) {
    this.vy -= gravity * dt;  // gravity reduces upward velocity
    this.x  += this.vx * dt;
    this.y  += this.vy * dt;

    if (this.y <= 0) {
      this.y     = 0;
      this.alive = false;      // hit ground
    }
  }
}

This is Euler integration. It's cheap and accurate enough for short-lived game projectiles. For very long flights or high precision (long-range sniper simulation, space games), consider Verlet or RK4 integration.

Pre-computing the Arc Analytically

Rather than stepping through the simulation frame by frame, you can use the kinematic equations to compute any point on the arc instantly. This is essential for drawing arc-preview lines, running AI aiming calculations every frame, and checking whether a target is reachable.

JavaScript
function computeArc(x0, y0, angleDeg, speed, gravity, numPoints) {
  const a  = angleDeg * Math.PI / 180;
  const vx = speed * Math.cos(a);
  const vy = speed * Math.sin(a);

  // Time until y returns to y0 (quadratic formula)
  const T = (vy + Math.sqrt(vy * vy + 2 * gravity * y0)) / gravity;
  const dt = T / numPoints;

  const points = [];
  for (let i = 0; i <= numPoints; i++) {
    const t = i * dt;
    points.push({
      x: x0 + vx * t,
      y: y0 + vy * t - 0.5 * gravity * t * t
    });
  }
  return points;
}

This runs in time with no integration error. Every point is computed exactly from the closed-form equations.

Inverse Ballistics: Solving for the Launch Angle

Here's a common problem: given a target at position relative to the launcher, what angle do I need to fire at?

Substituting the kinematic equations and rearranging produces the general inverse formula:

The gives two solutions: a low arc (shallow, fast, direct) and a high arc (steep, slow, lobbed). Both land at the same target. The discriminant under the square root determines whether the target is reachable. If it goes negative, the target is outside maximum range.

JavaScript
function solveAimAngle(dx, dy, speed, gravity) {
  const g    = gravity;
  const v2   = speed * speed;
  const disc = v2 * v2 - g * (g * dx * dx + 2 * dy * v2);

  if (disc < 0) return null; // out of range

  const sqrtDisc  = Math.sqrt(disc);
  const lowAngle  = Math.atan2(v2 - sqrtDisc, g * dx);
  const highAngle = Math.atan2(v2 + sqrtDisc, g * dx);

  return { lowAngle, highAngle };
}

In gameplay terms, a sniper or direct-fire weapon uses the lowAngle for speed. A mortar or grenade launcher deliberately uses the highAngle to arc over walls. Checking disc < 0 lets you show an "Out of Range" indicator before the player fires.

Adding Air Resistance

Real projectiles experience drag. The physical drag force is:

where is air density, the drag coefficient, and the cross-sectional area. In games this becomes a simple damping multiplier applied each frame:

JavaScript
update(dt) {
  const drag = 0.98; // retain 98% of velocity each second
  this.vx *= Math.pow(drag, dt);
  this.vy *= Math.pow(drag, dt);

  this.vy -= gravity * dt;
  this.x  += this.vx * dt;
  this.y  += this.vy * dt;
}

Once drag is added, closed-form solutions no longer work. Arc previews must be computed by running a fast simulation loop. AI aiming must use iterative search (binary search on angle, or Newton's method). The trade-off in feel is worth it for many games—dragged projectiles feel more weighty and realistic.

3D Ballistics

Extending to 3D is straightforward. Gravity only affects one axis (typically Y in a Y-up coordinate system). The other two axes behave like the horizontal component—constant velocity.

JavaScript
class Projectile3D {
  constructor(origin, directionNormalized, speed) {
    this.pos = origin.clone();
    this.vel = directionNormalized.clone().multiplyScalar(speed);
  }

  update(dt, gravity = 14.0) {
    this.vel.y -= gravity * dt;           // only Y is affected
    this.pos.addScaledVector(this.vel, dt);
  }
}

// Launching toward a 3D target:
const dir = new THREE.Vector3(dx, 0, dz).normalize();
const launchDir = new THREE.Vector3(
  dir.x * Math.cos(angle),
  Math.sin(angle),
  dir.z * Math.cos(angle)
);
const proj = new Projectile3D(launcher.position, launchDir, speed);

The horizontal aim direction is a yaw angle on the XZ plane. The vertical launch angle is computed separately from the inverse ballistics formula using the 2D horizontal distance and vertical offset to the target.

Arc Visualization

Angry Birds made the arc preview famous, but it's used across genres: artillery games, golf simulators, tactical RPGs, physics puzzlers. Showing the predicted trajectory gives the player clear feedback and makes aiming feel like a skill rather than guesswork.

The implementation is straightforward. Compute 40–80 arc points analytically, update a line geometry each frame as the player adjusts aim, and render it as a dotted or dashed line. In Three.js:

JavaScript
function refreshArcLine(arcLine, angle, speed, gravity) {
  const pts  = computeArc(0, 0, angle, speed, gravity, 60);
  const attr = arcLine.geometry.attributes.position;

  pts.forEach((p, i) => {
    attr.setXYZ(i, p.x, p.y, 0);
  });

  attr.needsUpdate = true;
  arcLine.geometry.setDrawRange(0, pts.length);
}

This runs every frame while the player aims. The analytic computation is fast enough to be effectively free, even on mobile hardware.

Predictive Aiming for AI

AI that fires at moving players must predict where the player will be at impact time, not where they are now. The algorithm iterates to convergence:

JavaScript
function predictiveAim(shooter, targetPos, targetVel, projSpeed, gravity) {
  let predicted = targetPos.clone();

  // Iterate 3 times — converges quickly
  for (let i = 0; i < 3; i++) {
    const dist         = predicted.distanceTo(shooter);
    const timeEstimate = dist / projSpeed;  // rough flight time
    predicted = targetPos.clone().addScaledVector(targetVel, timeEstimate);
  }

  const dx = predicted.x - shooter.x;
  const dy = predicted.y - shooter.y;
  return solveAimAngle(dx, dy, projSpeed, gravity);
}

Three iterations are almost always sufficient. The first pass dramatically improves on firing at the current position, and subsequent passes refine the estimate to near-perfect accuracy.

Bounce and Ricochet

For bouncing projectiles—grenades, rubber bullets, pinballs—handle surface collisions with a velocity reflection. When hitting a surface with outward normal :

Multiply by a restitution coefficient to model energy loss per bounce:

A coefficient of 1.0 is a perfect elastic bounce (no energy loss). A coefficient of 0.0 means the projectile stops dead on impact. Grenades in most games use to 0.5—bouncy enough to feel right without rolling forever.

Games That Use This

Angry Birds: The entire game is ballistics. The arc preview teaches players the relationship between angle and range experientially.

World of Tanks / World of Warships: Both use full 3D ballistics with shell velocity, shell drop, and armor penetration. Experienced players learn to lead fast targets and elevate aim at long range to compensate for drop.

Halo (grenades): Requires precise arc throws in 3D space. Bouncing a plasma grenade around a corner or sticking it through a narrow gap is a high-skill move entirely enabled by ballistic physics.

Golf games (Mario Golf, Hot Shots Golf): Ballistics extended with significant spin, wind, and lift (Magnus effect). The launch angle and power meter map directly to the kinematic equations.

Spelunky / Hollow Knight: Arrow and dart traps use simple ballistic models. Once fired, they follow a clean parabola until impact, creating predictable hazards the player can learn to read and dodge.

Scorched Earth / Worms: Pure ballistics puzzle games. Players mentally solve inverse ballistics to land shots over terrain features and compensate for wind.

Performance Notes

When simulating hundreds or thousands of projectiles (bullet-hell games, large artillery battles), per-object simulation stays cheap. Ballistic projectiles require only a velocity update and a position update—two vector additions per frame. The bottleneck is usually collision detection, not the physics math itself.

For deterministic networked gameplay, ballistic projectiles are ideal. Position is fully determined by initial state and elapsed time, so any client can reproduce the exact trajectory from the launch parameters alone. This makes synchronization trivial compared to contact-physics objects.

The parabola is everywhere. In grenades arcing around corners, in basketballs sailing through air, in artillery shells curving across a battlefield. Once you see it, you can't unsee it.

Mechanics Library

The live demo from Verlet Integration.

No.27Physics & simulation

Verlet Integration

Verlet integration is a numerically stable physics simulation technique that powers cloth, rope, and ragdoll physics in games. This article breaks down the position Verlet formula, Thomas Jakobsen's constraint relaxation method, and how a three-line integration loop can produce convincing

4 demos10 min read

The live demo from Separating Axis Theorem (SAT).

No.24Physics & simulation

Separating Axis Theorem (SAT)

The Separating Axis Theorem is the mathematical backbone behind precise convex-shape collision detection in countless games. Learn how it works, how to compute the Minimum Translation Vector for physics resolution, and see it in action with a fully interactive demo.

4 demos10 min read

The live demo from Water Simulation.

No.22Physics & simulation

Water Simulation

Water simulation is one of the most visually striking — and mathematically rich — techniques in game development. Learn how Gerstner waves, FFT-based ocean rendering, and the shallow water equations work together to create convincing real-time water.

1 demo9 min read

The live demo from Rigid Body Physics.

No.14Physics & simulation

Rigid Body Physics

Rigid body physics powers every tumbling crate, bouncing ball, and ragdoll in games. Learn how impulse-based collision response works—the math behind every major physics engine—and explore it live in an interactive 3D simulation.

1 demo7 min read

Esc