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.
1
Unity Basics & Interface
Editor overview, assets, prefabs, architecture
2
C# Scripting Fundamentals
MonoBehaviour, coroutines, input systems, patterns
3
GameObjects & Components
Transforms, renderers, custom components
4
Physics & Collisions
Rigidbody, colliders, raycasting, forces
You Are Here
5
UI Systems
Canvas, uGUI, UI Toolkit, responsive design
6
Animation & State Machines
Animator, blend trees, IK, Timeline
7
Audio & Visual Effects
AudioSource, particles, VFX Graph, post-processing
8
Building & Publishing
Build pipeline, optimization, platforms, monetization
9
Rendering Pipelines
URP, HDRP, Shader Graph, lighting systems
10
Data-Oriented Tech Stack
ECS, Jobs System, Burst Compiler
11
AI & Gameplay Systems
NavMesh, FSMs, behavior trees, procedural gen
12
Multiplayer & Networking
Netcode, RPCs, latency, prediction
13
Tools & Editor Scripting
Custom editors, debug tools, CI/CD
14
Architecture & Clean Code
Service locators, DI, ScriptableObject architecture
15
Performance Optimization
CPU/GPU profiling, memory, object pooling
16
Production & Industry Practices
Git, Agile, asset pipelines, debugging at scale
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.
| Feature | 3D Physics (PhysX) | 2D Physics (Box2D) |
| Rigidbody | Rigidbody | Rigidbody2D |
| Colliders | Box, Sphere, Capsule, MeshCollider | Box2D, Circle2D, Capsule2D, Polygon2D |
| Gravity | Default: (0, -9.81, 0) | Default: (0, -9.81) |
| Joints | Hinge, Spring, Fixed, Configurable | Hinge2D, Spring2D, Fixed2D, Distance2D |
| Use Cases | 3D games, VR, simulations | 2D 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.
| Property | Default | Purpose |
| Mass | 1 kg | Affects how forces act on the body. Does NOT affect gravity fall speed. |
| Drag | 0 | Air resistance. Higher values slow linear movement. |
| Angular Drag | 0.05 | Resistance to rotation. Without it, spinning objects never stop. |
| Use Gravity | true | Whether global gravity affects this body. |
| Is Kinematic | false | When true, not driven by physics -- you control it via MovePosition. Still generates collision events. Perfect for moving platforms. |
| Interpolation | None | Smooths visual movement between physics steps. Use Interpolate for players. |
| Collision Detection | Discrete | Discrete is cheap but can miss fast objects. Continuous prevents tunneling. |
| Constraints | None | Freeze 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.
| Aspect | Rigidbody | CharacterController |
| Movement | Force-based (AddForce, velocity) | Direct (Move, SimpleMove) |
| Gravity | Automatic via physics engine | Must be implemented manually |
| Slopes/Steps | Slides on slopes, stuck on steps | Built-in slope limit and step offset |
| Best For | Physics-heavy games, vehicles | FPS 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
| Collider | Shape | Performance | Best Use Case |
| BoxCollider | Rectangular box | Very fast | Crates, walls, platforms |
| SphereCollider | Perfect sphere | Fastest | Balls, projectiles, proximity |
| CapsuleCollider | Cylinder + rounded ends | Fast | Characters, humanoids |
| MeshCollider | Exact mesh geometry | Expensive | Terrain, complex static geometry |
| WheelCollider | Specialized wheel | Moderate | Vehicle 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
| Aspect | Collision (isTrigger = false) | Trigger (isTrigger = true) |
| Physical response? | Yes -- objects bounce, push, block | No -- objects pass through |
| Callbacks | OnCollisionEnter/Stay/Exit | OnTriggerEnter/Stay/Exit |
| Analogy | A solid wall | A motion sensor beam |
| Common uses | Floors, walls, projectile hits | Pickups, 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.
| Property | Range | Example |
| Dynamic Friction | 0 - 1 | Ice = 0.05, Rubber = 0.8 |
| Static Friction | 0 - 1 | Ice = 0.1, Rubber = 0.9 |
| Bounciness | 0 - 1 | Clay = 0, Super Ball = 1.0 |
| Friction Combine | Avg/Min/Max/Multiply | Average is usually best |
| Bounce Combine | Avg/Min/Max/Multiply | Max 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.
| ForceMode | Mass? | Time? | Use Case |
| Force | Yes | Continuous | Engines, thrusters, wind -- continuous push |
| Impulse | Yes | Instant | Jumps, explosions, bullet impacts -- one-time kick |
| Acceleration | No | Continuous | Gravity modifiers, uniform acceleration |
| VelocityChange | No | Instant | Instant 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
| Mode | How It Works | Cost | Use For |
| Discrete | Checks position at each step only | Cheapest | Slow objects, most enemies |
| Continuous | Sweeps against static colliders | Moderate | Player character |
| Continuous Dynamic | Sweeps against static + dynamic | Expensive | Fast projectiles (bullets) |
| Continuous Speculative | Predicts contacts speculatively | Moderate | Good 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
| Method | Shape | Use Case |
| Physics.Raycast | Thin line | Bullets, line-of-sight, ground detection |
| Physics.SphereCast | Swept sphere | Melee attacks, aim assist, ground checks |
| Physics.BoxCast | Swept box | Edge detection, wide scans |
| Physics.OverlapSphere | Stationary sphere | Explosions, proximity, area checks |
| Physics.RaycastAll | Line (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 Type | Analogy | Game Examples |
| HingeJoint | Door hinge, elbow | Doors, pinball flippers, drawbridges |
| SpringJoint | Bungee cord | Grappling hooks, bouncy bridges |
| FixedJoint | Welded metal | Attaching items, breakable structures |
| ConfigurableJoint | Any mechanism | Vehicle suspension, robotic arms |
| CharacterJoint | Human joints | Ragdoll 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)
| Setting | Default | Recommendation |
| Gravity | (0, -9.81, 0) | -20 to -30 for snappier platformers |
| Fixed Timestep | 0.02 (50 Hz) | 0.01 for precision; 0.04 for mobile |
| Solver Iterations | 6 | 10-12 for stacked objects/complex joints |
| Layer Collision Matrix | All collide | Disable unnecessary pairs |
| Auto Sync Transforms | true | Disable for performance |
Project Settings
Configuration
Exercises & Self-Assessment
Exercise 1
Rigidbody Playground
Build a physics sandbox to internalize Rigidbody properties:
- 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
- Test different drag values (0, 1, 5, 20) and observe how movement is dampened
- Apply a bounciness = 1.0 Physic Material to a sphere and watch it bounce forever
- Freeze X rotation on a cube, push it down the ramp -- it slides without tumbling
- Set one sphere to isKinematic and observe it is immovable but still pushes dynamic bodies
Exercise 2
Trigger Zone Obstacle Course
- Create a speed boost zone (OnTriggerEnter sets velocity.z = 20)
- Create a gravity flip zone (toggles Physics.gravity)
- Create a checkpoint system using trigger volumes that save the player's position
- Create a kill zone that respawns the player at the last checkpoint
Exercise 3
Raycast Shooting Gallery
- Create 10 targets at varying distances. Implement a hitscan weapon using
Physics.Raycast
- Visualize shots with
Debug.DrawRay (green = hit, red = miss)
- Add LayerMask filtering: ignore Player, hit Target and Wall layers
- Implement headshot detection with a small "Head" collider (3x damage)
- Add an explosion weapon using
Physics.OverlapSphere
Exercise 4
Reflective Questions
- Explain the difference between
ForceMode.Impulse and ForceMode.VelocityChange. When would you use each?
- A fast bullet passes through a thin wall. What caused this, and how do you fix it?
- Two trigger volumes overlap but neither calls
OnTriggerEnter. What is the most likely cause?
- Why does Celeste use custom physics instead of Unity's built-in Rigidbody?
- You have 500 enemies with MeshColliders matching detailed models. How do you fix the performance?
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.
Continue the Series
Part 3: GameObjects & Components
Deep dive into transforms, renderers, custom components, scene organization, and advanced composition patterns.
Read Article
Part 5: UI Systems
Master Canvas, uGUI, UI Toolkit, responsive design, and building professional interfaces for all platforms.
Read Article
Part 6: Animation & State Machines
Animator controllers, blend trees, inverse kinematics, Timeline, and state machine design patterns.
Read Article