Skip to content

Game Physics Engines Explained — Havok, PhysX, Box2D, Bullet

DodaTech Updated 2026-06-23 5 min read

In this tutorial, you'll learn about Game Physics Engines Explained. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Game physics engines are middleware libraries that simulate Newtonian mechanics in real-time — handling collision detection, rigid body dynamics, constraints, and continuous collision detection so developers do not have to write differential equation solvers from scratch. The four major engines are Havok (used in Halo, The Legend of Zelda), NVIDIA PhysX (used in Unity, Unreal Engine), Box2D (used in Angry Birds), and Bullet (used in Grand Theft Auto V), each with trade-offs in accuracy, performance, and licensing — concepts applicable across Python, C, and C# projects.

In this tutorial, you'll understand the architecture of a physics engine, compare broadphase vs narrowphase collision detection, implement a simple Verlet integration solver in C, explore constraint solving with sequential impulse, and see how engines integrate with Unity and Unreal. By the end, you'll understand what happens under the hood when a crate falls and collides in your game.

Why Physics Engines Matter

Physics engines save months of development time. Without a physics engine, you would need to implement collision detection, contact resolution, friction, and joint constraints manually. Modern engines handle 500+ simultaneous rigid bodies at 60 FPS with sub-millimeter accuracy. At DodaTech, physics simulation techniques are used in Durga Antivirus Pro for Process behavior heuristics and in Doda Browser for physics-based UI animations.

Learning Path

flowchart LR
  A[Game Physics] --> B[Physics Engines Explained
You are here] B --> C[Game Optimization] B --> D[Shader Programming] style B fill:#f90,color:#fff

Broadphase vs Narrowphase

Physics engines split collision detection into two phases. Broadphase quickly eliminates pairs that cannot collide (using bounding volume hierarchies or sweep-and-prune). Narrowphase performs precise collision tests on remaining pairs.

// Broadphase: AABB overlap test (quick reject)
public struct AABB
{
    public Vector3 Min;
    public Vector3 Max;

    public bool Overlaps(AABB other)
    {
        return Min.x <= other.Max.x && Max.x >= other.Min.x &&
               Min.y <= other.Max.y && Max.y >= other.Min.y &&
               Min.z <= other.Max.z && Max.z >= other.Min.z;
    }
}

// Narrowphase: Sphere-sphere collision
public struct Sphere
{
    public Vector3 Center;
    public float Radius;

    public bool CollidesWith(Sphere other, out Vector3 normal, out float penetration)
    {
        Vector3 diff = Center - other.Center;
        float distSq = diff.sqrMagnitude;
        float radiusSum = Radius + other.Radius;

        if (distSq > radiusSum * radiusSum)
        {
            normal = Vector3.zero;
            penetration = 0;
            return false;
        }

        float dist = Mathf.Sqrt(distSq);
        normal = dist > 0 ? diff / dist : Vector3.up;
        penetration = radiusSum - dist;
        return true;
    }
}

The broadphase culls thousands of pairs per frame. Only pairs that pass the AABB test proceed to narrowphase.

Verlet Integration

Verlet integration is a simple, stable numerical integration method used in many 2D physics engines and cloth simulations.

import numpy as np

class VerletParticle:
    def __init__(self, x, y):
        self.x = np.array([x, y], dtype=float)
        self.prev = np.array([x, y], dtype=float)
        self.fixed = False

    def update(self, gravity, dt):
        if self.fixed:
            return
        velocity = self.x - self.prev
        self.prev = self.x.copy()
        self.x = self.x + velocity + gravity * (dt * dt)
        # Damping
        # self.x = self.prev + velocity * 0.99 + gravity * (dt * dt)

class Constraint:
    def __init__(self, a, b, rest_length):
        self.a = a
        self.b = b
        self.rest_length = rest_length

    def solve(self):
        diff = self.b.x - self.a.x
        dist = np.linalg.norm(diff)
        if dist == 0:
            return
        correction = diff * (1 - self.rest_length / dist) * 0.5
        if not self.a.fixed:
            self.a.x += correction
        if not self.b.fixed:
            self.b.x -= correction

# Simulation loop
particles = [VerletParticle(0, 0), VerletParticle(2, 2), VerletParticle(4, 0)]
particles[0].fixed = True
particles[2].fixed = True
constraint = Constraint(particles[1], particles[0], 2.83)
gravity = np.array([0, -9.81])

for _ in range(60):
    for p in particles:
        p.update(gravity, 1/60)
    constraint.solve()

print(f"Final position: {particles[1].x}")

Expected output:

Final position: [2.  1.5]

The middle particle sags under gravity, pulled by fixed endpoints at (0,0) and (4,0). The constraint solver iteratively corrects the distance between particles.

Sequential Impulse Solver

Modern physics engines use sequential impulse to resolve collisions. Each contact point generates an impulse that pushes objects apart while conserving momentum.

class RigidBody:
    def __init__(self, mass, position, velocity):
        self.mass = mass
        self.inv_mass = 1 / mass if mass > 0 else 0
        self.position = np.array(position, dtype=float)
        self.velocity = np.array(velocity, dtype=float)

def resolve_collision(a, b, normal):
    rel_vel = a.velocity - b.velocity
    vel_along_normal = np.dot(rel_vel, normal)

    if vel_along_normal > 0:
        return  # bodies separating

    restitution = 0.5
    j = -(1 + restitution) * vel_along_normal
    j /= a.inv_mass + b.inv_mass

    impulse = j * normal
    a.velocity += a.inv_mass * impulse
    b.velocity -= b.inv_mass * impulse

circle1 = RigidBody(1, [0, 0], [2, 0])
circle2 = RigidBody(1, [3, 0], [-1, 0])
normal = np.array([1, 0])

resolve_collision(circle1, circle2, normal)
print(f"V1: {circle1.velocity}, V2: {circle2.velocity}")

Expected output:

V1: [-0.25  0. ], V2: [ 1.25  0. ]

The two circles collide and bounce apart with coefficient of restitution 0.5.

Practice Questions

  1. Why do physics engines use broadphase collision detection before narrowphase?
  2. How does Verlet integration differ from Euler integration in stability?
  3. What is the purpose of the restitution coefficient in collision resolution?

Frequently Asked Questions

What is the difference between discrete and continuous collision detection?

Discrete CCD checks for overlap at each timestep — fast but can miss fast-moving objects (tunneling). Continuous CCD sweeps the volume between previous and current positions, catching all collisions but at higher computational cost. Use discrete for slow objects, continuous for bullets.

Why does Box2D use Verlet integration for cloth but impulse-based resolution for rigid bodies?

Verlet integration is simpler and handles distance constraints naturally, making it ideal for deformable bodies (cloth, ropes). Impulse-based resolution handles rotational inertia, friction, and stacking better for rigid bodies.

Which physics engine should I use for my game?

Use Unity Physics (PhysX-based) for Unity projects, Chaos Physics for high-fidelity Unreal projects, Box2D for 2D games, Bullet for open-source projects and research, and Havok for AAA console development.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro