Chapter 5: The Unity3D Physics Engine — Rigidbody, Colliders and Joints

Game DevelopmentMath for Unity3DJune 201830 min read

Introduction to Unity Physics

Unity ships with two completely separate physics engines, both based on the PhysX library from NVIDIA:

  • 3D Physics: uses the Physics class, Rigidbody component, and Collider components (BoxCollider, SphereCollider, etc.).
  • 2D Physics: uses the Physics2D class, Rigidbody2D component, and Collider2D components (BoxCollider2D, CircleCollider2D, etc.).

The two systems are completely independent and do not interact. They share the same conceptual design — almost everything you learn in one maps directly to the other. Throughout this chapter we cover the 3D API. To apply the same knowledge to 2D, simply append "2D" to component names and use the Physics2D static class instead of Physics.

The physics simulation runs in FixedUpdate (by default at 50 Hz, configurable in Project Settings → Time → Fixed Timestep). This is important: all physics interactions — applying forces, moving Rigidbodies, reading velocity — should happen in FixedUpdate, not Update.

The Rigidbody Component

A Rigidbody is the component that hands an object over to the physics engine. Once a Rigidbody is attached, the object will be affected by gravity, forces, collisions, and torques. Without a Rigidbody, a GameObject is invisible to the physics simulation (though its collider can still participate as a static obstacle).

Key Rigidbody Fields

  • Mass: the simulated weight in kilograms. Affects how forces and collisions impact the body relative to others.
  • Drag: linear air resistance. Higher values slow linear movement faster. 0 = no drag.
  • Angular Drag: rotational resistance. Higher values stop spinning faster.
  • Use Gravity: toggles whether gravity is applied. Disable for spacecraft or floating objects.
  • Is Kinematic: when true, the Rigidbody ignores physics forces but still participates in collision detection (see below).
  • Collision Detection Mode: controls how fast-moving objects are handled (see Collision Modes section).
  • Constraints: freeze specific position or rotation axes. Useful for 2.5D games or preventing unwanted rolling.

Applying Forces in Code

using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
public class PhysicsExample : MonoBehaviour
{
    private Rigidbody _rb;
    public float jumpForce = 8f;
    public float thrustForce = 10f;

    void Awake()
    {
        _rb = GetComponent<Rigidbody>();
    }

    void FixedUpdate()
    {
        // Apply continuous force in world space (like a rocket engine)
        if (Input.GetKey(KeyCode.W))
        {
            _rb.AddForce(transform.forward * thrustForce);
        }

        // Apply an impulse (instantaneous push — good for jumps, explosions)
        if (Input.GetKeyDown(KeyCode.Space))
        {
            _rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
        }

        // Apply torque (angular force — makes the object spin)
        _rb.AddTorque(Vector3.up * 2f);
    }
}

ForceMode Options

  • ForceMode.Force (default): continuous force, affected by mass and fixed timestep
  • ForceMode.Acceleration: continuous force, NOT affected by mass (same acceleration regardless of mass)
  • ForceMode.Impulse: instantaneous impulse, affected by mass — use for explosions and jumps
  • ForceMode.VelocityChange: instantaneous velocity change, NOT affected by mass — directly sets velocity delta

Kinematic Rigidbodies

Setting rb.isKinematic = true puts the object in kinematic mode. A kinematic Rigidbody:

  • Is NOT affected by gravity, forces, or collisions from other bodies
  • CAN be moved by setting transform.position or using rb.MovePosition() and rb.MoveRotation()
  • STILL participates in collision detection — it will push dynamic bodies around when it moves
  • Can receive collision callbacks (OnCollisionEnter, OnTriggerEnter)
using UnityEngine;

// Example: a moving platform controlled by script, not physics
[RequireComponent(typeof(Rigidbody))]
public class KinematicPlatform : MonoBehaviour
{
    private Rigidbody _rb;
    public Vector3 pointA;
    public Vector3 pointB;
    public float speed = 2f;

    private float _t = 0f;
    private int _direction = 1;

    void Awake()
    {
        _rb = GetComponent<Rigidbody>();
        _rb.isKinematic = true; // this is a kinematic body
    }

    void FixedUpdate()
    {
        _t += Time.fixedDeltaTime * speed * _direction;

        if (_t >= 1f) { _t = 1f; _direction = -1; }
        if (_t <= 0f) { _t = 0f; _direction =  1; }

        // Use MovePosition instead of transform.position for kinematic bodies
        // This properly interacts with the physics engine (wakes sleeping bodies, etc.)
        _rb.MovePosition(Vector3.Lerp(pointA, pointB, _t));
    }
}

Key rule: use rb.MovePosition() and rb.MoveRotation() instead of setting transform.position directly on kinematic bodies. This correctly integrates with the physics step and ensures other Rigidbodies react properly (e.g., objects resting on the platform move with it).

Colliders

A Collider defines the shape used for collision detection. An object can have a Rigidbody without a Collider (it will be affected by gravity but pass through everything), or a Collider without a Rigidbody (it becomes a static obstacle). For most gameplay objects you need both.

Primitive Colliders

Primitive colliders are computed cheaply by the physics engine and should be your first choice for most objects:

  • BoxCollider: axis-aligned box. Ideal for crates, walls, floors, rectangular shapes.
  • SphereCollider: perfect sphere. Cheapest possible collider — ideal for balls, characters (with a capsule parent), explosions, trigger zones.
  • CapsuleCollider: cylinder with hemispherical ends. Standard for humanoid characters — slides over steps and doesn't catch on corners.

Specialized Colliders

  • MeshCollider: uses the object's mesh geometry exactly. Accurate but expensive, especially for concave meshes. Mark as "Convex" when possible, and never use for fast-moving dynamic objects.
  • WheelCollider: purpose-built for vehicle wheel physics. Handles suspension, friction, and drive force separately.
  • TerrainCollider: automatically adapts to the heightmap data of a Unity Terrain asset.

Collider as Trigger Zone

Any collider can be turned into a trigger zone by enabling its Is Trigger checkbox. Trigger colliders do not physically push objects; instead, they fire events when overlapping:

// Detect when something enters a trigger zone (e.g., a pickup, a damage zone)
void OnTriggerEnter(Collider other)
{
    if (other.CompareTag("Player"))
    {
        Debug.Log("Player entered trigger zone");
        // award points, deal damage, open door, etc.
    }
}

void OnTriggerStay(Collider other)
{
    // Called every FixedUpdate while the collider overlaps
}

void OnTriggerExit(Collider other)
{
    // Called when the collider leaves
}

Collision Callbacks (Physical Collisions)

void OnCollisionEnter(Collision collision)
{
    // 'collision' contains contact points, relative velocity, impulse, etc.
    Debug.Log("Hit: " + collision.gameObject.name);
    Debug.Log("Impact speed: " + collision.relativeVelocity.magnitude);

    // Access contact points
    foreach (ContactPoint contact in collision.contacts)
    {
        Debug.DrawRay(contact.point, contact.normal, Color.red, 2f);
    }
}

void OnCollisionStay(Collision collision)  { }
void OnCollisionExit(Collision collision)  { }

Compound Colliders

Complex objects (a humanoid character, a vehicle, an L-shaped shelf) cannot be approximated well by a single primitive collider. Rather than using a slow MeshCollider, you build a compound collider: multiple primitive colliders attached to child GameObjects, all sharing the same root Rigidbody.

How to Build a Compound Collider

  1. Attach a single Rigidbody to the root GameObject.
  2. Add child empty GameObjects, positioned and scaled to cover different parts of the mesh.
  3. Attach a primitive Collider (Box, Sphere, or Capsule) to each child.
  4. The physics engine automatically combines all child colliders into one compound shape driven by the root Rigidbody.
// You don't need special code for compound colliders — Unity handles them automatically.
// Just make sure there is exactly ONE Rigidbody on the root, and multiple Colliders on children.

// Example hierarchy:
// Robot (Rigidbody, no collider)
//   Body (BoxCollider)
//   Head (SphereCollider)
//   LeftArm (CapsuleCollider)
//   RightArm (CapsuleCollider)

Compound colliders are far more performant than MeshColliders for dynamic objects and provide good collision accuracy for most gameplay purposes.

Collision Detection Modes

By default, Unity's physics engine checks for collisions at each fixed timestep. If an object moves very fast between two timesteps, it can pass through a thin collider entirely — a phenomenon called tunneling. Unity provides four collision detection modes to address this:

  • Discrete (default): checks at each fixed timestep only. Fast but can tunnel through thin surfaces at high speeds. Use for slow-moving or large objects.
  • Continuous: uses a swept volume test between timesteps to detect collisions with static (non-Rigidbody) colliders. More expensive. Use for fast projectiles hitting static geometry.
  • Continuous Dynamic: like Continuous but also tests against other Rigidbodies in Continuous or Continuous Dynamic mode. Most accurate and most expensive. Use for critical fast-moving objects like bullets or fast vehicles.
  • Continuous Speculative: uses speculative CCD (Continuous Collision Detection). Works well with Kinematic Rigidbodies and is generally cheaper than Continuous Dynamic. Good default for Kinematic bodies that move fast.
Rigidbody rb = GetComponent<Rigidbody>();

// Set in code if needed
rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;

// Or set on the component in the Inspector — no code needed

As a general rule: use Discrete for most objects, Continuous for fast projectiles, and only escalate to Continuous Dynamic when tunneling is actually observed. Each step up has a performance cost.

Static Colliders

A collider without a Rigidbody is called a static collider. It acts as an immovable obstacle in the physics world. Walls, floors, terrain, and fixed architecture are all static colliders.

Critical rule: never move a static collider at runtime. Moving a static collider forces the physics engine to rebuild its internal spatial structures, which is expensive and can cause artifacts. If you need to move a collider (e.g., a sliding door, a rising platform), give it a Kinematic Rigidbody instead. The performance and stability difference is significant.

// WRONG: Moving a static collider at runtime
void Update()
{
    // Bad! This is a collider with no Rigidbody — moving it is expensive
    transform.position += Vector3.right * Time.deltaTime;
}

// CORRECT: Use a Kinematic Rigidbody for moveable obstacles
void FixedUpdate()
{
    _rb.MovePosition(_rb.position + Vector3.right * Time.fixedDeltaTime);
}

Physics Layer Collision Matrix

By default, every collider can collide with every other collider. In a complex scene with many physics objects, this creates unnecessary collision checks. Unity lets you define which layers collide with which other layers using the Physics Layer Collision Matrix (Edit → Project Settings → Physics → Layer Collision Matrix).

// Query the matrix in code:
bool willCollide = Physics.GetIgnoreLayerCollision(layer1, layer2);

// Ignore collisions between two specific layers:
Physics.IgnoreLayerCollision(LayerMask.NameToLayer("Player"),
                              LayerMask.NameToLayer("PlayerProjectile"));

// Ignore collision between two specific colliders:
Physics.IgnoreCollision(colliderA, colliderB);

Common use cases: make player projectiles ignore the player's own collider, make ghost/spectator mode ignore all physics, separate enemy vs. ally projectile layers.

The CharacterController

The CharacterController is a special Unity component designed for player character movement. Unlike a Rigidbody, it is not a fully physics-simulated body — you control it entirely through code, but it still handles collision detection, slopes, and steps automatically.

When to Use CharacterController vs. Rigidbody

  • CharacterController: when you need precise, deterministic player movement that is not affected by external physics forces (most FPS, platformers, third-person games). Simpler to control, handles stairs and slopes well.
  • Rigidbody-based character: when you want the player to be genuinely affected by physics (ragdoll on hit, vehicle physics, water buoyancy). More complex to implement correctly.

CharacterController Movement

using UnityEngine;

[RequireComponent(typeof(CharacterController))]
public class FPSController : MonoBehaviour
{
    public float moveSpeed = 5f;
    public float jumpHeight = 1.5f;
    public float gravity = -9.81f;

    private CharacterController _cc;
    private Vector3 _velocity;

    void Awake()
    {
        _cc = GetComponent<CharacterController>();
    }

    void Update()
    {
        // IsGrounded returns true when the bottom of the capsule touches the ground
        bool isGrounded = _cc.isGrounded;

        if (isGrounded && _velocity.y < 0f)
            _velocity.y = -2f; // small downward force to keep grounded

        // Read input
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        Vector3 moveDir = transform.right * h + transform.forward * v;

        // Move horizontally
        _cc.Move(moveDir * moveSpeed * Time.deltaTime);

        // Jump
        if (Input.GetButtonDown("Jump") && isGrounded)
        {
            // v = sqrt(2 * g * h)
            _velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
        }

        // Apply gravity
        _velocity.y += gravity * Time.deltaTime;
        _cc.Move(_velocity * Time.deltaTime);
    }
}

CharacterController.Move() vs. SimpleMove()

  • Move(Vector3 motion): moves the character by the given vector. Does NOT apply gravity automatically. You manage gravity in your velocity vector.
  • SimpleMove(Vector3 speed): applies automatic gravity and constrains to the XZ plane for movement speed. Simpler but less flexible — you cannot control jump height or fall speed directly.

Physics Best Practices

A collection of hard-won rules for stable, performant Unity physics:

1. Always Use FixedUpdate for Physics

// WRONG: applying forces in Update (frame-rate dependent)
void Update()
{
    _rb.AddForce(Vector3.forward * 10f);
}

// CORRECT: apply forces in FixedUpdate (fixed timestep, deterministic)
void FixedUpdate()
{
    _rb.AddForce(Vector3.forward * 10f);
}

2. Never Set transform.position on a Dynamic Rigidbody

// WRONG: teleports the object, breaks physics state, misses collisions
transform.position = new Vector3(5f, 0f, 0f);

// CORRECT: use Rigidbody methods
_rb.MovePosition(new Vector3(5f, 0f, 0f)); // for kinematic bodies
_rb.position = new Vector3(5f, 0f, 0f);    // instant teleport (use sparingly)

3. Sleep Mode

The physics engine automatically puts idle Rigidbodies to "sleep" to save computation. A sleeping body does not simulate until a collision or force wakes it. This is normally invisible, but if you modify a sleeping body through transform.position, you may see it not react until it is woken. Use rb.WakeUp() if needed.

4. Adjust Solver Iterations for Complex Stacking

// Increase solver iterations for accurate stacking of many physics objects
// (Project Settings → Physics → Default Solver Iterations, or per-Rigidbody)
_rb.solverIterations = 16; // default is 6

Joints

Joints connect two Rigidbodies (or a Rigidbody to a fixed world point) and constrain their relative movement. Unity provides several joint types, all under the Joint base class.

HingeJoint

A HingeJoint allows rotation around a single axis — like a door hinge or an axle. It can optionally have a motor (for powered rotation) and limits (to constrain the rotation angle).

// Access a HingeJoint motor through code:
HingeJoint hinge = GetComponent<HingeJoint>();

JointMotor motor = hinge.motor;
motor.force = 100f;
motor.targetVelocity = 200f; // degrees per second
motor.freeSpin = false;
hinge.motor = motor;
hinge.useMotor = true;

FixedJoint

A FixedJoint welds two Rigidbodies together — they move and rotate as one unit but are separate physics objects. Useful for attachments that can break (the joint can be destroyed when the force exceeds a threshold).

// Programmatically create a FixedJoint at runtime (e.g., grabbing a physics object)
public void AttachObject(Rigidbody objectToAttach)
{
    FixedJoint joint = gameObject.AddComponent<FixedJoint>();
    joint.connectedBody = objectToAttach;
    joint.breakForce = 500f;  // joint breaks if force exceeds this
    joint.breakTorque = 500f;
}

void OnJointBreak(float breakForce)
{
    Debug.Log("Joint broke with force: " + breakForce);
}

SpringJoint

A SpringJoint connects two bodies with a spring: it pulls them toward a target distance from each other and allows oscillation. Use for elastic ropes, shock absorbers, or jelly-like connections.

ConfigurableJoint

The most powerful joint type — you can configure freedom or constraint on all six degrees of motion (X/Y/Z position and X/Y/Z rotation) independently. Used for ragdolls, character IK rigs, and custom vehicle suspensions.

Exercise: Building a Catapult

A catapult demonstrates HingeJoint with a motor, force application, and projectile spawning. The arm swings on a hinge until it hits a stop, launching the payload.

Scene Setup

  • CatapultBase: a static, heavy Rigidbody (or static collider) — the base of the catapult
  • CatapultArm: a Rigidbody with a HingeJoint connected to the base
  • LaunchPoint: empty child of CatapultArm marking where the projectile spawns
  • Projectile: a prefab with a Rigidbody and a SphereCollider

CatapultController.cs

using UnityEngine;

public class CatapultController : MonoBehaviour
{
    [Header("References")]
    public Rigidbody armRigidbody;
    public Transform launchPoint;
    public GameObject projectilePrefab;

    [Header("Settings")]
    public float launchForce = 20f;
    public float armTorque = 500f;
    public float resetDelay = 3f;

    private HingeJoint _hinge;
    private bool _launched = false;

    void Awake()
    {
        _hinge = armRigidbody.GetComponent<HingeJoint>();
        ConfigureHinge();
    }

    void ConfigureHinge()
    {
        // Set hinge limits: arm can swing from -90 (loaded position) to +45 (release position)
        JointLimits limits = _hinge.limits;
        limits.min = -90f;
        limits.max = 45f;
        _hinge.limits = limits;
        _hinge.useLimits = true;
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space) && !_launched)
        {
            Launch();
        }
    }

    void Launch()
    {
        _launched = true;

        // Apply torque to swing the arm upward
        armRigidbody.AddTorque(transform.right * armTorque, ForceMode.Impulse);

        // Spawn the projectile at the launch point
        GameObject proj = Instantiate(projectilePrefab, launchPoint.position, launchPoint.rotation);
        Rigidbody projRb = proj.GetComponent<Rigidbody>();

        // Give the projectile velocity in the launch direction
        projRb.velocity = launchPoint.up * launchForce;

        // Auto-reset after a delay
        Invoke(nameof(Reset), resetDelay);
    }

    void Reset()
    {
        // Reset arm to loaded position
        armRigidbody.velocity = Vector3.zero;
        armRigidbody.angularVelocity = Vector3.zero;
        armRigidbody.MoveRotation(Quaternion.Euler(0f, 0f, -90f));
        _launched = false;
    }
}

Exercise: Building a Chain with Joints

A chain is made of multiple Rigidbody links, each connected to the next by a HingeJoint (or ConfigurableJoint). This is a classic physics demonstration and useful for hanging bridges, pendulums, and decorative physics props.

ChainBuilder.cs — Procedural Chain Generation

using UnityEngine;

public class ChainBuilder : MonoBehaviour
{
    [Header("Chain Settings")]
    public int linkCount = 10;
    public float linkLength = 0.4f;
    public float linkMass = 0.5f;
    public GameObject linkPrefab; // a capsule with a Rigidbody

    [Header("Anchor")]
    public bool anchorToWorld = true; // fix the top link to the world

    private Rigidbody[] _links;

    void Start()
    {
        BuildChain();
    }

    void BuildChain()
    {
        _links = new Rigidbody[linkCount];

        for (int i = 0; i < linkCount; i++)
        {
            // Place link below the previous one
            Vector3 position = transform.position + Vector3.down * (i * linkLength);
            GameObject linkObj = Instantiate(linkPrefab, position, Quaternion.identity);
            linkObj.name = "Link_" + i;

            Rigidbody rb = linkObj.GetComponent<Rigidbody>();
            rb.mass = linkMass;
            _links[i] = rb;

            // Connect to previous link (or to world for the first link)
            HingeJoint hinge = linkObj.AddComponent<HingeJoint>();

            if (i == 0 && anchorToWorld)
            {
                // First link: anchor to a fixed world point (no connectedBody)
                hinge.connectedBody = null;
                hinge.connectedAnchor = transform.position;
            }
            else if (i > 0)
            {
                // Subsequent links: connect to the previous link
                hinge.connectedBody = _links[i - 1];
                hinge.connectedAnchor = Vector3.up * (linkLength * 0.5f);
                hinge.anchor = Vector3.up * (linkLength * 0.5f);
            }

            // Configure the hinge axis (swing side to side)
            hinge.axis = Vector3.right;
        }
    }
}

The key insight is that each link only needs to know about its immediate neighbor. The chain behavior (swinging, tangling, collisions) emerges automatically from the physics simulation once the joints are in place.

Physics Performance Tips

Unity physics can be a significant CPU cost if not managed carefully. Keep these guidelines in mind as your scene grows:

  • Prefer primitive colliders over MeshColliders for all dynamic (Rigidbody) objects.
  • Use compound colliders to approximate complex shapes without MeshCollider cost.
  • Let objects sleep: avoid applying forces every frame to objects that do not need them. Sleeping bodies cost almost nothing.
  • Use layer collision matrix to eliminate unnecessary layer-pair collision checks.
  • Increase Fixed Timestep (lower frequency) to reduce physics computation — but this makes simulation less accurate and can cause tunneling.
  • Profile first: use Unity's Profiler (Window → Analysis → Profiler) to measure physics cost before optimizing. Common premature optimization is adding complexity where no problem exists.

Summary

The Unity physics engine gives you a powerful, mature simulation system with minimal setup. Understanding the component model — Rigidbody for dynamic simulation, Collider for shape, CharacterController for player movement, and Joints for constraints — gives you the tools to build everything from simple falling boxes to complex mechanical systems.

  • Rigidbody: makes objects physics-simulated. Use AddForce and FixedUpdate.
  • Kinematic Rigidbody: script-controlled movement that still participates in collision. Use MovePosition().
  • Primitive Colliders: Box, Sphere, Capsule — fast and sufficient for most needs.
  • Compound Colliders: multiple primitives on children sharing one root Rigidbody — best balance of accuracy and performance.
  • Static Colliders: Colliders without Rigidbody — never move at runtime.
  • Collision Modes: use Continuous Detection for fast-moving objects to prevent tunneling.
  • CharacterController: the right tool for player movement in most games.
  • Joints: connect bodies with physical constraints — HingeJoint for single-axis rotation, FixedJoint for welding, SpringJoint for elasticity.
Unity3DPhysicsRigidbodyColliderCharacterControllerJointsC#

Need help with Unity3D development?

I'm a senior developer with 16+ years experience, including AAA projects at Ubisoft. Let's discuss how I can help with your game or interactive project.

Start a Conversation