Back to Gaming

Unity Game Engine Series Part 4: Physics & Collisions

March 31, 2026 Wasil Zafar 42 min read

Bring your game world to life with realistic physics. Master Rigidbody dynamics, collider shapes, raycasting for detection, force-based movement, joint constraints, and ragdoll systems -- from the foundations of PhysX to production-ready physics gameplay inspired by Celeste and Angry Birds.

Table of Contents

  1. Unity Physics Engines
  2. Colliders & Triggers
  3. Forces & Movement
  4. Collision Detection
  5. Raycasting
  6. Advanced Physics
  7. Case Studies
  8. Exercises & Self-Assessment
  9. Physics Config Generator
  10. Conclusion & Next Steps

Introduction: The Invisible Hand of Game Feel

Series Overview: This is Part 4 of our 16-part Unity Game Engine Series. Having covered the editor, C# scripting, and GameObjects in Parts 1-3, we now dive into the physics systems that make game worlds feel tangible -- from gravity and collisions to raycasting and ragdolls.

Physics is the invisible hand that transforms rendered meshes into a world that feels real. When Mario lands and you feel the weight, when a bowling ball scatters pins in satisfying chaos, when a ragdoll tumbles down stairs -- that is physics at work. It is arguably the single most important system for game feel.

Unity provides two complete physics engines: NVIDIA PhysX for 3D and Box2D for 2D. Both are battle-tested engines used in thousands of shipped titles. In this part, we will master both, learn when to use each approach, and build the foundation for physics-driven gameplay that players can feel.

Key Insight: Great physics in games is rarely about realism -- it is about responsiveness and game feel. Celeste's jump defies real-world physics entirely (variable gravity, coyote time, input buffering), but it feels perfect. Mastering physics means knowing when to follow the simulation and when to break the rules.

1. Unity Physics Engines

1.1 PhysX 3D & Box2D

Unity runs two completely independent physics simulations. A Rigidbody2D will never collide with a Rigidbody, and a BoxCollider will never detect a BoxCollider2D.

Feature3D Physics (PhysX)2D Physics (Box2D)
RigidbodyRigidbodyRigidbody2D
CollidersBox, Sphere, Capsule, MeshColliderBox2D, Circle2D, Capsule2D, Polygon2D
GravityDefault: (0, -9.81, 0)Default: (0, -9.81)
JointsHinge, Spring, Fixed, ConfigurableHinge2D, Spring2D, Fixed2D, Distance2D
Use Cases3D games, VR, simulations2D platformers, top-down, mobile
Critical Rule: Never mix 2D and 3D physics components on the same GameObject. If you are making a 2.5D game (3D visuals, 2D gameplay), use 3D physics and constrain the Z axis.

1.2 Rigidbody Fundamentals

The Rigidbody component is the gateway to physics simulation. Without it, a collider is just a static obstacle. With it, an object gains mass, responds to gravity, and participates in force-based interactions.

PropertyDefaultPurpose
Mass1 kgAffects how forces act on the body. Does NOT affect gravity fall speed.
Drag0Air resistance. Higher values slow linear movement.
Angular Drag0.05Resistance to rotation. Without it, spinning objects never stop.
Use GravitytrueWhether global gravity affects this body.
Is KinematicfalseWhen true, not driven by physics -- you control it via MovePosition. Still generates collision events. Perfect for moving platforms.
InterpolationNoneSmooths visual movement between physics steps. Use Interpolate for players.
Collision DetectionDiscreteDiscrete is cheap but can miss fast objects. Continuous prevents tunneling.
ConstraintsNoneFreeze position/rotation on specific axes. Essential for 2.5D games.
// Rigidbody configuration for a player character
using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
public class RigidbodySetup : MonoBehaviour
{
    private Rigidbody rb;

    private void Awake()
    {
        rb = GetComponent<Rigidbody>();
        rb.mass = 70f;
        rb.drag = 1f;
        rb.angularDrag = 5f;
        rb.useGravity = true;
        rb.isKinematic = false;
        rb.interpolation = RigidbodyInterpolation.Interpolate;
        rb.collisionDetectionMode = CollisionDetectionMode.Continuous;

        // Prevent the player capsule from tipping over
        rb.constraints = RigidbodyConstraints.FreezeRotationX
                       | RigidbodyConstraints.FreezeRotationZ;
    }
}

1.3 CharacterController

Unity offers an alternative to Rigidbody: the CharacterController with built-in slope handling, step climbing, and collision resolution without full physics simulation.

AspectRigidbodyCharacterController
MovementForce-based (AddForce, velocity)Direct (Move, SimpleMove)
GravityAutomatic via physics engineMust be implemented manually
Slopes/StepsSlides on slopes, stuck on stepsBuilt-in slope limit and step offset
Best ForPhysics-heavy games, vehiclesFPS controllers, platformers
// CharacterController-based first-person movement
using UnityEngine;

[RequireComponent(typeof(CharacterController))]
public class FPSController : MonoBehaviour
{
    [SerializeField] private float walkSpeed = 6f;
    [SerializeField] private float jumpForce = 8f;
    [SerializeField] private float gravity = -20f;
    [SerializeField] private LayerMask groundMask;

    private CharacterController controller;
    private Vector3 velocity;
    private bool isGrounded;

    private void Awake()
    {
        controller = GetComponent<CharacterController>();
        controller.slopeLimit = 45f;
        controller.stepOffset = 0.4f;
    }

    private void Update()
    {
        isGrounded = Physics.CheckSphere(
            transform.position + Vector3.down * (controller.height / 2f),
            0.3f, groundMask);

        if (isGrounded && velocity.y < 0) velocity.y = -2f;

        float h = Input.GetAxisRaw("Horizontal");
        float v = Input.GetAxisRaw("Vertical");
        Vector3 moveDir = (transform.right * h + transform.forward * v).normalized;
        controller.Move(moveDir * walkSpeed * Time.deltaTime);

        if (Input.GetButtonDown("Jump") && isGrounded) velocity.y = jumpForce;

        velocity.y += gravity * Time.deltaTime;
        controller.Move(velocity * Time.deltaTime);
    }
}

2. Colliders & Triggers

Colliders define the physical shape of an object for the physics engine -- the simplified "hitbox" used instead of the detailed visual mesh.

2.1 Collider Types

ColliderShapePerformanceBest Use Case
BoxColliderRectangular boxVery fastCrates, walls, platforms
SphereColliderPerfect sphereFastestBalls, projectiles, proximity
CapsuleColliderCylinder + rounded endsFastCharacters, humanoids
MeshColliderExact mesh geometryExpensiveTerrain, complex static geometry
WheelColliderSpecialized wheelModerateVehicle wheels with suspension
Performance Rule: Always use the simplest collider that approximates your shape. A CapsuleCollider for a character is hundreds of times faster than a MeshCollider. Use compound colliders (multiple primitives on child objects) for complex shapes before reaching for MeshCollider.

2.2 Trigger vs Collision

AspectCollision (isTrigger = false)Trigger (isTrigger = true)
Physical response?Yes -- objects bounce, push, blockNo -- objects pass through
CallbacksOnCollisionEnter/Stay/ExitOnTriggerEnter/Stay/Exit
AnalogyA solid wallA motion sensor beam
Common usesFloors, walls, projectile hitsPickups, area detection, checkpoints
Collision Matrix Rule: For any collision or trigger event to fire, at least one of the two objects must have a Rigidbody (kinematic is fine). Two static colliders will never generate events. This is the most common "why isn't my trigger working?" bug.
// Trigger zone for collecting items
using UnityEngine;

public class CoinPickup : MonoBehaviour
{
    [SerializeField] private int coinValue = 10;
    [SerializeField] private AudioClip pickupSound;
    [SerializeField] private GameObject sparkleEffect;

    private void OnTriggerEnter(Collider other)
    {
        if (!other.CompareTag("Player")) return;

        ScoreManager.Instance.AddScore(coinValue);
        AudioSource.PlayClipAtPoint(pickupSound, transform.position);

        if (sparkleEffect != null)
            Instantiate(sparkleEffect, transform.position, Quaternion.identity);

        Destroy(gameObject);
    }
}

2.3 Physics Materials

A Physic Material controls surface properties -- how bouncy and how frictional a collider is.

PropertyRangeExample
Dynamic Friction0 - 1Ice = 0.05, Rubber = 0.8
Static Friction0 - 1Ice = 0.1, Rubber = 0.9
Bounciness0 - 1Clay = 0, Super Ball = 1.0
Friction CombineAvg/Min/Max/MultiplyAverage is usually best
Bounce CombineAvg/Min/Max/MultiplyMax ensures bouncy balls always bounce
// Creating physics materials at runtime
using UnityEngine;

public class SurfaceMaterialManager : MonoBehaviour
{
    public void ApplyIceSurface(Collider col)
    {
        PhysicMaterial ice = new PhysicMaterial("Ice");
        ice.dynamicFriction = 0.05f;
        ice.staticFriction = 0.1f;
        ice.bounciness = 0f;
        ice.frictionCombine = PhysicMaterialCombine.Minimum;
        col.material = ice;
    }

    public void ApplyBouncySurface(Collider col)
    {
        PhysicMaterial bouncy = new PhysicMaterial("Bouncy");
        bouncy.dynamicFriction = 0.4f;
        bouncy.staticFriction = 0.4f;
        bouncy.bounciness = 0.95f;
        bouncy.bounceCombine = PhysicMaterialCombine.Maximum;
        col.material = bouncy;
    }
}

3. Forces & Movement

3.1 AddForce Modes

The ForceMode parameter fundamentally changes how force is applied. Understanding these modes is the key to making physics feel right.

ForceModeMass?Time?Use Case
ForceYesContinuousEngines, thrusters, wind -- continuous push
ImpulseYesInstantJumps, explosions, bullet impacts -- one-time kick
AccelerationNoContinuousGravity modifiers, uniform acceleration
VelocityChangeNoInstantInstant speed changes regardless of mass
// Demonstrating all four ForceMode types
using UnityEngine;

public class ForceDemo : MonoBehaviour
{
    [SerializeField] private float thrustPower = 10f;
    [SerializeField] private float jumpImpulse = 5f;
    private Rigidbody rb;

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

    private void FixedUpdate()
    {
        // ForceMode.Force -- continuous (rocket engine). Heavier = slower.
        if (Input.GetKey(KeyCode.W))
            rb.AddForce(transform.forward * thrustPower, ForceMode.Force);

        // ForceMode.Acceleration -- uniform (like gravity). Mass-independent.
        if (Input.GetKey(KeyCode.G))
            rb.AddForce(Vector3.down * 15f, ForceMode.Acceleration);
    }

    private void Update()
    {
        // ForceMode.Impulse -- instant kick. Heavier = less velocity.
        if (Input.GetKeyDown(KeyCode.Space))
            rb.AddForce(Vector3.up * jumpImpulse, ForceMode.Impulse);

        // ForceMode.VelocityChange -- instant, mass-independent.
        // Consistent jump height regardless of character mass.
        if (Input.GetKeyDown(KeyCode.V))
            rb.AddForce(Vector3.up * jumpImpulse, ForceMode.VelocityChange);
    }
}
Design Decision

Impulse vs VelocityChange for Jumping

Impulse: A 100 kg knight and a 50 kg rogue get the same force -- the rogue jumps twice as high (velocity = impulse / mass). Realistic but potentially frustrating.

VelocityChange: Both characters jump identically regardless of mass. Consistent and fair for gameplay.

Recommendation: Use VelocityChange for character jumping. Consistent jump height matters more than realism.

Game Feel Realism vs Fun

3.2 Velocity, Gravity & Drag

// Advanced platformer physics with custom gravity curves
using UnityEngine;

public class PlatformerPhysics : MonoBehaviour
{
    [Header("Movement")]
    [SerializeField] private float maxSpeed = 8f;
    [SerializeField] private float accelForce = 40f;

    [Header("Custom Gravity")]
    [SerializeField] private float fallGravityMultiplier = 2.5f;
    [SerializeField] private float lowJumpGravityMultiplier = 2f;

    [Header("Jump Assists")]
    [SerializeField] private float jumpVelocity = 12f;
    [SerializeField] private float coyoteTime = 0.12f;
    [SerializeField] private float jumpBufferTime = 0.15f;

    private Rigidbody rb;
    private float coyoteTimer, jumpBufferTimer;
    private bool isGrounded;

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

    private void FixedUpdate()
    {
        float inputX = Input.GetAxisRaw("Horizontal");
        Vector3 vel = rb.velocity;

        // Accelerate toward target speed
        float targetVelX = inputX * maxSpeed;
        vel.x = Mathf.MoveTowards(vel.x, targetVelX, accelForce * Time.fixedDeltaTime);

        // --- Custom Gravity (the secret to great jump feel) ---
        // Faster fall = snappier descent
        if (vel.y < 0)
            vel.y += Physics.gravity.y * (fallGravityMultiplier - 1) * Time.fixedDeltaTime;
        // Variable jump height: release button early = short hop
        else if (vel.y > 0 && !Input.GetButton("Jump"))
            vel.y += Physics.gravity.y * (lowJumpGravityMultiplier - 1) * Time.fixedDeltaTime;

        rb.velocity = vel;

        // Coyote time: jump briefly after leaving a ledge
        coyoteTimer = isGrounded ? coyoteTime : coyoteTimer - Time.fixedDeltaTime;

        // Jump buffer: remember input pressed just before landing
        jumpBufferTimer = Input.GetButtonDown("Jump")
            ? jumpBufferTime : jumpBufferTimer - Time.fixedDeltaTime;

        if (jumpBufferTimer > 0 && coyoteTimer > 0)
        {
            vel = rb.velocity;
            vel.y = jumpVelocity;
            rb.velocity = vel;
            jumpBufferTimer = 0;
            coyoteTimer = 0;
        }
    }
}
Key Insight: Custom gravity curves are the most important platformer physics technique. Real gravity creates a symmetrical, floaty arc. The best platformers use faster fall gravity (1.5-3x) and variable jump height (extra gravity on early button release) for snappy, responsive jumps.

3.3 MovePosition & Kinematic Bodies

// Moving platform using kinematic Rigidbody
using UnityEngine;

public class MovingPlatform : MonoBehaviour
{
    [SerializeField] private Transform pointA, pointB;
    [SerializeField] private float speed = 2f;

    private Rigidbody rb;
    private Vector3 target;

    private void Awake()
    {
        rb = GetComponent<Rigidbody>();
        rb.isKinematic = true;
        rb.interpolation = RigidbodyInterpolation.Interpolate;
        target = pointB.position;
    }

    private void FixedUpdate()
    {
        Vector3 newPos = Vector3.MoveTowards(
            rb.position, target, speed * Time.fixedDeltaTime);
        rb.MovePosition(newPos);

        if (Vector3.Distance(rb.position, target) < 0.01f)
            target = (target == pointA.position) ? pointB.position : pointA.position;
    }

    private void OnCollisionEnter(Collision col)
    {
        if (col.gameObject.CompareTag("Player")) col.transform.SetParent(transform);
    }

    private void OnCollisionExit(Collision col)
    {
        if (col.gameObject.CompareTag("Player")) col.transform.SetParent(null);
    }
}

4. Collision Detection

4.1 Continuous vs Discrete

ModeHow It WorksCostUse For
DiscreteChecks position at each step onlyCheapestSlow objects, most enemies
ContinuousSweeps against static collidersModeratePlayer character
Continuous DynamicSweeps against static + dynamicExpensiveFast projectiles (bullets)
Continuous SpeculativePredicts contacts speculativelyModerateGood general alternative
The Tunneling Problem: A bullet at 500 m/s moves 10m per physics step (at 50 Hz). A 0.5m wall gets teleported through entirely. Continuous detection sweeps the collider along its trajectory, catching collisions at every point.

4.2 Collision & Trigger Callbacks

// Complete collision and trigger callback reference
using UnityEngine;

public class CollisionHandler : MonoBehaviour
{
    [SerializeField] private GameObject impactEffect;
    [SerializeField] private float damageMultiplier = 1f;

    // --- COLLISION (isTrigger = false) ---
    private void OnCollisionEnter(Collision collision)
    {
        // Rich contact data: impact force, contact points, surface normal
        float impactForce = collision.impulse.magnitude;
        ContactPoint contact = collision.GetContact(0);

        Instantiate(impactEffect, contact.point,
            Quaternion.LookRotation(contact.normal));

        if (collision.gameObject.CompareTag("Enemy"))
        {
            var health = collision.gameObject.GetComponent<HealthComponent>();
            health?.TakeDamage(impactForce * damageMultiplier);
        }
    }

    private void OnCollisionStay(Collision collision)
    {
        // Every physics frame while touching. Use sparingly (expensive).
    }

    private void OnCollisionExit(Collision collision)
    {
        // Once when objects separate. Reset ground state, end effects.
    }

    // --- TRIGGERS (isTrigger = true) ---
    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player"))
            Debug.Log("Player entered the zone");
    }

    private void OnTriggerStay(Collider other)
    {
        // Every physics frame inside trigger. Good for DOT zones.
    }

    private void OnTriggerExit(Collider other)
    {
        if (other.CompareTag("Player"))
            Debug.Log("Player left the zone");
    }
}

5. Raycasting

Raycasting fires an invisible ray and reports what it hits -- a laser pointer that detects walls, enemies, and surfaces without any physical object moving through the scene.

5.1 Physics.Raycast & SphereCast

MethodShapeUse Case
Physics.RaycastThin lineBullets, line-of-sight, ground detection
Physics.SphereCastSwept sphereMelee attacks, aim assist, ground checks
Physics.BoxCastSwept boxEdge detection, wide scans
Physics.OverlapSphereStationary sphereExplosions, proximity, area checks
Physics.RaycastAllLine (all hits)Piercing bullets, multi-target
// Practical raycasting: weapon, ground check, explosion
using UnityEngine;

public class RaycastExamples : MonoBehaviour
{
    [SerializeField] private float weaponRange = 100f;
    [SerializeField] private float weaponDamage = 25f;
    [SerializeField] private LayerMask hitLayers;
    [SerializeField] private Camera playerCamera;

    // Hitscan weapon (instant bullet)
    public void FireWeapon()
    {
        Ray ray = playerCamera.ScreenPointToRay(
            new Vector3(Screen.width / 2f, Screen.height / 2f, 0));

        if (Physics.Raycast(ray, out RaycastHit hit, weaponRange, hitLayers))
        {
            if (hit.collider.TryGetComponent<HealthComponent>(out var health))
            {
                float mult = hit.collider.CompareTag("Head") ? 3f : 1f;
                health.TakeDamage(weaponDamage * mult);
            }
            Debug.DrawLine(ray.origin, hit.point, Color.green, 0.5f);
        }
    }

    // Ground check (SphereCast is more forgiving than Raycast)
    public bool IsGrounded()
    {
        return Physics.SphereCast(transform.position, 0.3f,
            Vector3.down, out _, 1.1f, hitLayers);
    }

    // Explosion radius
    public void Explode(Vector3 center, float radius, float force, float damage)
    {
        Collider[] hits = Physics.OverlapSphere(center, radius, hitLayers);
        foreach (var hit in hits)
        {
            float falloff = 1f - Vector3.Distance(center, hit.transform.position) / radius;

            if (hit.TryGetComponent<HealthComponent>(out var health))
                health.TakeDamage(damage * Mathf.Max(0, falloff));
            if (hit.TryGetComponent<Rigidbody>(out var rb))
                rb.AddExplosionForce(force, center, radius, 1f, ForceMode.Impulse);
        }
    }
}

5.2 LayerMask & Filtering

// LayerMask usage patterns
using UnityEngine;

public class LayerMaskGuide : MonoBehaviour
{
    [SerializeField] private LayerMask groundLayers;  // Inspector dropdown

    private void Examples()
    {
        // By name
        int mask = LayerMask.GetMask("Ground", "Platform", "Terrain");

        // Combine layers with bitwise OR
        int enemies = LayerMask.GetMask("Enemy");
        int destructibles = LayerMask.GetMask("Destructible");
        int combined = enemies | destructibles;

        // Invert: everything EXCEPT these layers
        int ignorePlayer = ~LayerMask.GetMask("Player", "PlayerProjectile");

        // Check object's layer
        if (gameObject.layer == LayerMask.NameToLayer("Enemy"))
            Debug.Log("This is an enemy!");
    }
}

5.3 Debug Visualization

// Physics debug visualization
using UnityEngine;

public class PhysicsDebugger : MonoBehaviour
{
    [SerializeField] private float rayDistance = 10f;
    [SerializeField] private float sphereRadius = 0.5f;

    private void Update()
    {
        // Debug.DrawRay -- visible in Scene view during Play mode
        Debug.DrawRay(transform.position, transform.forward * rayDistance, Color.green);

        if (Physics.Raycast(transform.position, transform.forward, out RaycastHit hit, rayDistance))
        {
            Debug.DrawLine(transform.position, hit.point, Color.green);
            Debug.DrawRay(hit.point, hit.normal, Color.red);
        }
    }

    // OnDrawGizmos -- always visible (even outside Play mode)
    private void OnDrawGizmos()
    {
        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(transform.position, sphereRadius);
    }

    // OnDrawGizmosSelected -- only when this object is selected
    private void OnDrawGizmosSelected()
    {
        Gizmos.color = Color.cyan;
        Gizmos.DrawWireSphere(transform.position, rayDistance);
    }
}

6. Advanced Physics

6.1 Joints: Hinge, Spring, Fixed

Joint TypeAnalogyGame Examples
HingeJointDoor hinge, elbowDoors, pinball flippers, drawbridges
SpringJointBungee cordGrappling hooks, bouncy bridges
FixedJointWelded metalAttaching items, breakable structures
ConfigurableJointAny mechanismVehicle suspension, robotic arms
CharacterJointHuman jointsRagdoll limbs
// Practical joint examples
using UnityEngine;

public class JointExamples : MonoBehaviour
{
    // Swinging door with auto-close spring
    public static void CreateSwingingDoor(GameObject door)
    {
        var hinge = door.AddComponent<HingeJoint>();
        hinge.axis = Vector3.up;
        hinge.anchor = new Vector3(-0.5f, 0, 0);

        hinge.useLimits = true;
        var limits = hinge.limits;
        limits.min = -90f;
        limits.max = 90f;
        hinge.limits = limits;

        hinge.useSpring = true;
        var spring = hinge.spring;
        spring.spring = 50f;
        spring.damper = 10f;
        spring.targetPosition = 0;
        hinge.spring = spring;
    }

    // Grappling hook connection
    public static SpringJoint CreateGrapple(GameObject player, Vector3 anchor, float length)
    {
        var spring = player.AddComponent<SpringJoint>();
        spring.autoConfigureConnectedAnchor = false;
        spring.connectedAnchor = anchor;
        spring.maxDistance = length;
        spring.minDistance = 0.5f;
        spring.spring = 80f;
        spring.damper = 7f;
        return spring;
    }

    // Breakable connection (OnJointBreak fires when force exceeded)
    public static void CreateBreakable(GameObject a, Rigidbody b, float breakForce)
    {
        var joint = a.AddComponent<FixedJoint>();
        joint.connectedBody = b;
        joint.breakForce = breakForce;
        joint.breakTorque = breakForce * 0.5f;
    }
}

6.2 Ragdoll Systems

A ragdoll is a physics-driven character: multiple Rigidbodies connected by CharacterJoints. On death, you switch from animation to ragdoll physics for unique, unrepeatable death animations.

// Ragdoll controller -- toggle between animated and physics states
using UnityEngine;

public class RagdollController : MonoBehaviour
{
    [SerializeField] private Animator animator;

    private Rigidbody[] ragdollBodies;
    private Collider[] ragdollColliders;
    private Rigidbody mainRb;
    private Collider mainCol;

    private void Awake()
    {
        ragdollBodies = GetComponentsInChildren<Rigidbody>();
        ragdollColliders = GetComponentsInChildren<Collider>();
        mainRb = GetComponent<Rigidbody>();
        mainCol = GetComponent<Collider>();
        SetRagdollActive(false);
    }

    public void ActivateRagdoll(Vector3 force, Vector3 hitPoint)
    {
        animator.enabled = false;
        mainRb.isKinematic = true;
        mainCol.enabled = false;
        SetRagdollActive(true);

        // Apply impact force to nearest bone
        Rigidbody closest = null;
        float minDist = float.MaxValue;
        foreach (var rb in ragdollBodies)
        {
            if (rb == mainRb) continue;
            float d = Vector3.Distance(rb.position, hitPoint);
            if (d < minDist) { minDist = d; closest = rb; }
        }
        closest?.AddForce(force, ForceMode.Impulse);
    }

    private void SetRagdollActive(bool active)
    {
        foreach (var rb in ragdollBodies)
            if (rb != mainRb) rb.isKinematic = !active;
        foreach (var col in ragdollColliders)
            if (col != mainCol) col.enabled = active;
    }
}
Performance Note

Ragdoll Optimization Tips

  • Simplify bones: 10-12 ragdoll bones instead of full skeleton
  • Pool ragdolls: Pre-instantiate and reuse rather than creating new ones
  • Auto-sleep: PhysX automatically sleeps settled ragdolls
  • Time limit: Destroy/disable ragdolls after a few seconds
Performance Object Pooling

7. Case Studies

Case Study

Celeste -- The Art of Fake Physics

Celeste is the gold standard for platformer physics, yet it violates nearly every law of real-world physics:

  • Variable jump height: Releasing jump early applies extra downward gravity for short hops
  • Asymmetric gravity: Falling uses ~2.5x normal gravity for snappy descent
  • Coyote time (6 frames): Jump briefly after leaving a ledge -- forgives imprecise timing
  • Jump buffering (4 frames): Pre-landing jump input is remembered and executed
  • Corner correction: Head-clipping on ceilings is auto-nudged horizontally
  • Custom physics: Direct velocity manipulation, not Rigidbody or CharacterController

Takeaway: The best game physics prioritize player agency and forgiveness over realism.

Platformer Game Feel Custom Physics
Case Study

Angry Birds -- Physics as Core Gameplay

Angry Birds is the quintessential physics puzzle game, where the entire gameplay loop is built on 2D physics (Box2D):

  • Projectile physics: Parabolic trajectories with gravity. The slingshot uses spring-like impulse force
  • Material simulation: Wood, glass, stone have different mass, friction, and break thresholds -- directly analogous to Physic Materials and joint break forces
  • Chain reactions: The cascading destruction is pure Rigidbody interaction, unique every time
  • Special abilities: Yellow bird = VelocityChange boost, black bird = AddExplosionForce, blue bird = splits into three Rigidbodies

Takeaway: Physics can be the entire game. When interactions are deep and readable, players develop intuition and derive satisfaction from mastery.

Physics Puzzle Box2D Destruction
Cheat Sheet

Key Global Physics Settings (Edit > Project Settings > Physics)

SettingDefaultRecommendation
Gravity(0, -9.81, 0)-20 to -30 for snappier platformers
Fixed Timestep0.02 (50 Hz)0.01 for precision; 0.04 for mobile
Solver Iterations610-12 for stacked objects/complex joints
Layer Collision MatrixAll collideDisable unnecessary pairs
Auto Sync TransformstrueDisable for performance
Project Settings Configuration

Exercises & Self-Assessment

Exercise 1

Rigidbody Playground

Build a physics sandbox to internalize Rigidbody properties:

  1. Create a ramp and flat ground. Add spheres with different mass values (0.1, 1, 10, 100) and observe they all fall at the same rate
  2. Test different drag values (0, 1, 5, 20) and observe how movement is dampened
  3. Apply a bounciness = 1.0 Physic Material to a sphere and watch it bounce forever
  4. Freeze X rotation on a cube, push it down the ramp -- it slides without tumbling
  5. Set one sphere to isKinematic and observe it is immovable but still pushes dynamic bodies
Exercise 2

Trigger Zone Obstacle Course

  1. Create a speed boost zone (OnTriggerEnter sets velocity.z = 20)
  2. Create a gravity flip zone (toggles Physics.gravity)
  3. Create a checkpoint system using trigger volumes that save the player's position
  4. Create a kill zone that respawns the player at the last checkpoint
Exercise 3

Raycast Shooting Gallery

  1. Create 10 targets at varying distances. Implement a hitscan weapon using Physics.Raycast
  2. Visualize shots with Debug.DrawRay (green = hit, red = miss)
  3. Add LayerMask filtering: ignore Player, hit Target and Wall layers
  4. Implement headshot detection with a small "Head" collider (3x damage)
  5. Add an explosion weapon using Physics.OverlapSphere
Exercise 4

Reflective Questions

  1. Explain the difference between ForceMode.Impulse and ForceMode.VelocityChange. When would you use each?
  2. A fast bullet passes through a thin wall. What caused this, and how do you fix it?
  3. Two trigger volumes overlap but neither calls OnTriggerEnter. What is the most likely cause?
  4. Why does Celeste use custom physics instead of Unity's built-in Rigidbody?
  5. You have 500 enemies with MeshColliders matching detailed models. How do you fix the performance?

Physics Configuration Document Generator

Generate a professional physics system design document for your Unity project. Download as Word, Excel, PDF, or PowerPoint.

Draft auto-saved

All data stays in your browser. Nothing is sent to or stored on any server.

Conclusion & Next Steps

You now have a comprehensive understanding of Unity's physics systems. Key takeaways:

  • Two physics worlds: PhysX for 3D, Box2D for 2D. Never mix components.
  • Rigidbody is the gateway: Use isKinematic for scripted movement that still triggers collisions.
  • Collider choice matters: Prefer primitive colliders over MeshColliders. Triggers detect; collisions respond.
  • ForceMode determines feel: VelocityChange for consistent jumps, Impulse for mass-dependent impacts.
  • Custom gravity is essential: Asymmetric gravity + coyote time + jump buffering = great platformer feel.
  • Raycasting is everywhere: Ground checks, weapons, interaction, explosions -- the swiss army knife.
  • Joints and ragdolls add depth: Hinge joints for doors, spring joints for grapples, ragdolls for dynamic deaths.

Next in the Series

In Part 5: UI Systems, we'll master Unity's UI frameworks -- Canvas rendering modes, uGUI components, the new UI Toolkit, responsive design patterns, and building professional menus, HUDs, and in-game interfaces that scale across all screen sizes.

Gaming