Unity Physics Systems
Introduction to Unity Physics
Physics simulation is a vital component of almost every game, allowing for realistic object interactions, movement, and dynamics. Unity provides robust physics systems for both 3D and 2D games, helping developers create convincing physical behaviors without having to implement complex physics calculations manually.
In this guide, you'll learn:
- How Unity's physics engines work (3D and 2D)
- Working with Rigidbodies and forces
- Implementing realistic object movement
- Using joints and constraints
- Raycasting and physics queries
- Optimizing physics performance
- Advanced physics techniques
Unity's Physics Systems
Unity includes two separate physics engines:
Physics Systems Overview:
- 3D Physics (NVIDIA PhysX): For 3D games with Rigidbody, Collider components
- 2D Physics (Box2D): For 2D games with Rigidbody2D, Collider2D components
Key components of both systems:
- Rigidbodies: Components that allow GameObjects to be affected by physics
- Colliders: Define the physical shape used for collision detection
- Physics Materials: Define surface properties like friction and bounciness
- Joints/Constraints: Connect Rigidbodies and limit their movement
- Forces: External influences applied to Rigidbodies (gravity, wind, etc.)
- Raycasts: Line-based collision detection for various gameplay needs
Physics Settings
Configure global physics settings in Edit → Project Settings → Physics/Physics 2D:
- Gravity: Global gravity strength and direction
- Collision/Layer Matrix: Define which layers can collide with each other
- Solver Iterations: Affects physics simulation accuracy and performance
- Sleep Threshold: When objects become inactive to save performance
- Default Contact Offset: Skin width for collision detection
- Bounce Threshold: Minimum impact velocity for bouncing
Working with Rigidbodies
Rigidbodies are the core component that make GameObjects interact with the physics system.
Controlling 3D Rigidbodies:
using UnityEngine;
public class RigidbodyController : MonoBehaviour
{
public float moveForce = 10f;
public float jumpForce = 300f;
public float maxSpeed = 5f;
private Rigidbody rb;
private bool isGrounded;
void Start()
{
// Get the Rigidbody component
rb = GetComponent();
// Optional: Override the default gravity
// Physics.gravity = new Vector3(0, -20f, 0);
}
void Update()
{
// Check for jump input
if (Input.GetButtonDown("Jump") && isGrounded)
{
Jump();
}
}
// Use FixedUpdate for physics-related code
void FixedUpdate()
{
// APPLYING FORCES
// Get input axes
float moveHorizontal = Input.GetAxis("Horizontal");
float moveVertical = Input.GetAxis("Vertical");
// Create movement vector
Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
// METHOD 1: AddForce - Most physically accurate
rb.AddForce(movement * moveForce);
// This gradually accelerates the object, simulating inertia
// Limit maximum speed (optional)
if (rb.velocity.magnitude > maxSpeed)
{
rb.velocity = rb.velocity.normalized * maxSpeed;
}
// METHOD 2: Velocity - Direct control, less physically accurate
// rb.velocity = new Vector3(moveHorizontal * maxSpeed, rb.velocity.y, moveVertical * maxSpeed);
// This directly sets velocity, ignoring inertia but providing responsive control
// METHOD 3: MovePosition - For precise movement with physics interaction
// rb.MovePosition(transform.position + movement * maxSpeed * Time.fixedDeltaTime);
// This moves the object while still interacting with the physics system
// FORCE MODES EXAMPLES
// Apply an impulse force (immediate force)
if (Input.GetKey(KeyCode.LeftShift))
{
rb.AddForce(movement * 10f, ForceMode.Impulse);
// ForceMode.Impulse applies the force instantly (good for jumps, impacts)
}
// Apply a continuous force ignoring mass
if (Input.GetKey(KeyCode.R))
{
rb.AddForce(Vector3.up * 20f, ForceMode.Acceleration);
// ForceMode.Acceleration applies force ignoring mass (like gravity)
}
// Apply force at a specific point (creates torque/rotation)
if (Input.GetKey(KeyCode.E))
{
Vector3 forcePoint = transform.position + new Vector3(0, -0.5f, 0.5f);
rb.AddForceAtPosition(Vector3.right * 10f, forcePoint, ForceMode.Force);
// This creates both movement and rotation
}
// Add torque (rotational force)
if (Input.GetKey(KeyCode.Q))
{
rb.AddTorque(Vector3.up * 10f);
// This makes the object spin around its Y axis
}
// GROUND CHECK
isGrounded = Physics.Raycast(transform.position, Vector3.down, 1.1f);
}
void Jump()
{
// Apply upward force
rb.AddForce(Vector3.up * jumpForce);
// Alternative: Set vertical velocity directly
// rb.velocity = new Vector3(rb.velocity.x, jumpSpeed, rb.velocity.z);
}
// Rigidbody's collision detection
void OnCollisionEnter(Collision collision)
{
Debug.Log("Collided with: " + collision.gameObject.name);
// Check collision velocity
float impactForce = collision.impulse.magnitude;
if (impactForce > 10f)
{
Debug.Log("Hard impact: " + impactForce);
}
}
}
Key Rigidbody concepts:
- AddForce/AddTorque: Apply forces to move/rotate the Rigidbody
- ForceMode: Different ways to apply force (Force, Impulse, Acceleration, VelocityChange)
- Velocity: Directly control the speed and direction
- MovePosition/MoveRotation: Kinematic movement with physics interactions
- FixedUpdate: Used for physics code because it syncs with the physics engine
Working with 2D Rigidbodies:
using UnityEngine;
public class Rigidbody2DController : MonoBehaviour
{
public float moveSpeed = 5f;
public float jumpForce = 10f;
private Rigidbody2D rb;
private bool isGrounded;
private bool isFacingRight = true;
void Start()
{
rb = GetComponent();
}
void Update()
{
// Handle jumping
if (Input.GetButtonDown("Jump") && isGrounded)
{
Jump();
}
}
void FixedUpdate()
{
// Get horizontal input
float moveHorizontal = Input.GetAxis("Horizontal");
// MOVEMENT METHODS
// METHOD 1: AddForce - physics-based movement
// rb.AddForce(new Vector2(moveHorizontal * moveSpeed, 0));
// METHOD 2: Velocity - direct control (most common for 2D platformers)
rb.velocity = new Vector2(moveHorizontal * moveSpeed, rb.velocity.y);
// Allows for responsive movement while keeping vertical velocity (gravity/jumping)
// Flip sprite to face movement direction
if (moveHorizontal > 0 && !isFacingRight)
{
Flip();
}
else if (moveHorizontal < 0 && isFacingRight)
{
Flip();
}
// Ground check with 2D physics
isGrounded = Physics2D.OverlapCircle(
transform.position + new Vector3(0, -0.5f, 0), // Position to check (feet)
0.2f, // Radius of check
LayerMask.GetMask("Ground") // Only check for ground layer
);
}
void Jump()
{
// Set vertical velocity directly (most responsive for platformers)
rb.velocity = new Vector2(rb.velocity.x, jumpForce);
// Alternative: Add impulse force
// rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
}
void Flip()
{
// Flip the character to face the other direction
isFacingRight = !isFacingRight;
// METHOD 1: Flip using transform scale
Vector3 scale = transform.localScale;
scale.x *= -1;
transform.localScale = scale;
// METHOD 2: Flip using rotation
// transform.Rotate(0f, 180f, 0f);
}
// 2D collision detection
void OnCollisionEnter2D(Collision2D collision)
{
Debug.Log("2D collision with: " + collision.gameObject.name);
// Get collision normal (direction)
Vector2 normal = collision.contacts[0].normal;
// Check if we hit something from below (ceiling)
if (normal.y < -0.5f)
{
// Hit head on ceiling, stop upward velocity
rb.velocity = new Vector2(rb.velocity.x, 0);
}
}
}
2D vs 3D physics differences:
- 2D space: Uses Vector2 and operates on the XY plane
- ForceMode2D: 2D-specific force modes (Force and Impulse only)
- 2D Colliders: BoxCollider2D, CircleCollider2D, etc.
- Physics2D.*: All physics queries use the Physics2D class
Joints and Constraints
Joints connect Rigidbodies together, allowing for complex physical interactions like hinges, springs, and ropes.
Working with Joints:
using UnityEngine;
public class JointExamples : MonoBehaviour
{
// References to the objects we'll connect
public GameObject objectA;
public GameObject objectB;
void Start()
{
// Make sure both objects have Rigidbody components
EnsureRigidbody(objectA);
EnsureRigidbody(objectB);
// Create various joint types
// 1. HINGE JOINT - acts like a door hinge
CreateHingeJoint();
// 2. SPRING JOINT - elastic connection
// CreateSpringJoint();
// 3. FIXED JOINT - rigid connection
// CreateFixedJoint();
// 4. CHARACTER JOINT - for ragdoll physics
// CreateCharacterJoint();
// 5. CONFIGURABLE JOINT - most flexible
// CreateConfigurableJoint();
}
void EnsureRigidbody(GameObject obj)
{
// Add Rigidbody if it doesn't exist
if (obj.GetComponent() == null)
{
obj.AddComponent();
}
}
void CreateHingeJoint()
{
// Add a HingeJoint to objectA
HingeJoint hinge = objectA.AddComponent();
// Connect to objectB
hinge.connectedBody = objectB.GetComponent();
// Set the anchor point (local to objectA)
hinge.anchor = new Vector3(0, 0.5f, 0);
// Set the axis of rotation (local to objectA)
hinge.axis = new Vector3(0, 0, 1); // Rotate around Z axis
// Add limits to the hinge motion
hinge.useLimits = true;
JointLimits limits = new JointLimits();
limits.min = -45f; // 45 degrees one way
limits.max = 45f; // 45 degrees the other way
limits.bounciness = 0.5f; // Bounce when hitting limits
hinge.limits = limits;
// Add a motor to the hinge
hinge.useMotor = true;
JointMotor motor = new JointMotor();
motor.targetVelocity = 80f; // Degrees per second
motor.force = 10f; // Maximum force to apply
hinge.motor = motor;
}
void CreateSpringJoint()
{
// Add a SpringJoint to objectA
SpringJoint spring = objectA.AddComponent();
// Connect to objectB
spring.connectedBody = objectB.GetComponent();
// Configure spring properties
spring.spring = 10f; // Spring force
spring.damper = 0.2f; // Damping to reduce oscillation
spring.minDistance = 0.5f; // Minimum distance before spring applies force
spring.maxDistance = 2f; // Maximum distance before spring applies force
spring.tolerance = 0.025f; // Position tolerance
// Auto-calculate positions
spring.autoConfigureConnectedAnchor = true;
}
void CreateFixedJoint()
{
// Add a FixedJoint to objectA
FixedJoint fixedJoint = objectA.AddComponent();
// Connect to objectB
fixedJoint.connectedBody = objectB.GetComponent();
// Configure break force (will break if force exceeds this value)
fixedJoint.breakForce = 1000f;
fixedJoint.breakTorque = 1000f;
}
void CreateCharacterJoint()
{
// Add a CharacterJoint to objectA (like a human limb)
CharacterJoint charJoint = objectA.AddComponent();
// Connect to objectB
charJoint.connectedBody = objectB.GetComponent();
// Configure twist limits (rotation around the axis)
SoftJointLimit twistLimit = new SoftJointLimit();
twistLimit.limit = 20f; // Degrees
charJoint.lowTwistLimit = twistLimit;
// Configure swing limits (cone of movement)
SoftJointLimit swingLimit = new SoftJointLimit();
swingLimit.limit = 30f; // Degrees
charJoint.swing1Limit = swingLimit;
}
void CreateConfigurableJoint()
{
// Add a ConfigurableJoint to objectA (most flexible joint)
ConfigurableJoint configJoint = objectA.AddComponent();
// Connect to objectB
configJoint.connectedBody = objectB.GetComponent();
// Setting up a joint that acts like a door hinge
// Lock all motion except rotation around Z
configJoint.xMotion = ConfigurableJointMotion.Locked;
configJoint.yMotion = ConfigurableJointMotion.Locked;
configJoint.zMotion = ConfigurableJointMotion.Locked;
configJoint.angularXMotion = ConfigurableJointMotion.Locked;
configJoint.angularYMotion = ConfigurableJointMotion.Locked;
configJoint.angularZMotion = ConfigurableJointMotion.Limited;
// Set up rotation limits
SoftJointLimit limit = new SoftJointLimit();
limit.limit = 45f; // 45 degrees
configJoint.lowAngularZLimit = limit;
limit.limit = 45f; // 45 degrees in the other direction
configJoint.highAngularZLimit = limit;
}
// Dynamic joint creation at runtime
public void CreateJointBetween(GameObject a, GameObject b)
{
// Ensure both have Rigidbody components
Rigidbody rbA = a.GetComponent();
if (rbA == null) rbA = a.AddComponent();
Rigidbody rbB = b.GetComponent();
if (rbB == null) rbB = b.AddComponent();
// Create a spring joint
SpringJoint joint = a.AddComponent();
joint.connectedBody = rbB;
// Configure the joint
joint.spring = 100f;
joint.damper = 5f;
// Calculate actual world distance between objects
float distance = Vector3.Distance(a.transform.position, b.transform.position);
joint.minDistance = distance * 0.8f;
joint.maxDistance = distance * 1.2f;
}
// Break a joint
public void BreakJoint(GameObject obj)
{
Joint joint = obj.GetComponent();
if (joint != null)
{
Destroy(joint);
Debug.Log("Joint broken!");
}
}
}
Common joint types:
- Hinge Joint: Rotation around a single axis (doors, wheels)
- Spring Joint: Elastic connection with damping (springs, bungee cords)
- Fixed Joint: Rigid connection that transfers forces (welded objects)
- Character Joint: Specialized for character limbs with constraints (ragdolls)
- Configurable Joint: Most flexible, can mimic any other joint type
- Distance Joint: Maintains a specific distance between objects
2D Joints
2D physics has its own set of joints with similar functionality:
- HingeJoint2D: 2D version of hinge joint
- SpringJoint2D: 2D version of spring joint
- DistanceJoint2D: Maintains distance between objects
- SliderJoint2D: Constrains movement to a line
- FixedJoint2D: Rigidly connects objects
- RelativeJoint2D: Maintains relative position and rotation
- TargetJoint2D: Pulls object toward a target position
- WheelJoint2D: Simulates a wheel with suspension
Advanced Physics Techniques
Beyond basic physics, Unity provides tools for more complex physics interactions and optimizations.
Custom Gravity:
using UnityEngine;
public class CustomGravity : MonoBehaviour
{
public Transform gravitySource;
public float gravityStrength = 9.81f;
private Rigidbody rb;
void Start()
{
rb = GetComponent();
// Disable Unity's standard gravity
rb.useGravity = false;
}
void FixedUpdate()
{
if (gravitySource != null)
{
// Calculate direction to gravity source
Vector3 gravityDirection = (gravitySource.position - transform.position).normalized;
// Apply custom gravity
rb.AddForce(gravityDirection * gravityStrength, ForceMode.Acceleration);
// Optional: Orient object to gravity source
Quaternion targetRotation = Quaternion.FromToRotation(transform.up, -gravityDirection) * transform.rotation;
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, 0.1f);
}
}
}
Soft Body Physics Simulation:
using UnityEngine;
using System.Collections.Generic;
public class SoftBody : MonoBehaviour
{
public GameObject nodePrefab; // Prefab for mass points
public float nodeMass = 1f; // Mass of each node
public float springStiffness = 100f; // Stiffness of springs
public float springDamping = 5f; // Damping of springs
public int resolution = 5; // Number of nodes per side
public float size = 2f; // Size of the soft body
private List nodes = new List();
private List springs = new List();
void Start()
{
CreateSoftBody();
}
void CreateSoftBody()
{
// Create nodes in a grid
for (int x = 0; x < resolution; x++)
{
for (int y = 0; y < resolution; y++)
{
// Calculate position
float xPos = (x / (float)(resolution - 1) - 0.5f) * size;
float yPos = (y / (float)(resolution - 1) - 0.5f) * size;
// Create node
GameObject node = Instantiate(nodePrefab, transform.position + new Vector3(xPos, yPos, 0), Quaternion.identity);
node.transform.parent = transform;
// Configure node
Rigidbody nodeRb = node.GetComponent();
if (nodeRb == null) nodeRb = node.AddComponent();
nodeRb.mass = nodeMass;
// Fix the corners in place (optional)
if ((x == 0 && y == 0) || (x == resolution-1 && y == 0))
{
nodeRb.isKinematic = true;
}
// Add to node list
nodes.Add(node);
}
}
// Create springs between nodes
for (int x = 0; x < resolution; x++)
{
for (int y = 0; y < resolution; y++)
{
int index = x * resolution + y;
GameObject currentNode = nodes[index];
// Connect to right neighbor
if (x < resolution - 1)
{
int rightIndex = (x + 1) * resolution + y;
CreateSpring(currentNode, nodes[rightIndex]);
}
// Connect to bottom neighbor
if (y < resolution - 1)
{
int bottomIndex = x * resolution + (y + 1);
CreateSpring(currentNode, nodes[bottomIndex]);
}
// Connect to diagonal (optional, creates more stability)
if (x < resolution - 1 && y < resolution - 1)
{
int diagonalIndex = (x + 1) * resolution + (y + 1);
CreateSpring(currentNode, nodes[diagonalIndex]);
}
}
}
}
void CreateSpring(GameObject nodeA, GameObject nodeB)
{
SpringJoint spring = nodeA.AddComponent();
spring.connectedBody = nodeB.GetComponent();
// Configure spring
spring.spring = springStiffness;
spring.damper = springDamping;
spring.autoConfigureConnectedAnchor = false;
spring.anchor = Vector3.zero;
spring.connectedAnchor = Vector3.zero;
// Calculate actual distance
float distance = Vector3.Distance(nodeA.transform.position, nodeB.transform.position);
spring.minDistance = 0;
spring.maxDistance = distance * 1.5f;
// Add to spring list
springs.Add(spring);
}
// Apply an explosion force to the soft body
public void ApplyExplosion(Vector3 position, float force, float radius)
{
foreach (GameObject node in nodes)
{
Rigidbody nodeRb = node.GetComponent();
if (nodeRb != null && !nodeRb.isKinematic)
{
nodeRb.AddExplosionForce(force, position, radius);
}
}
}
}
Physics Optimization
Physics simulation can be expensive. Here are some tips to optimize physics performance:
- Use primitive colliders instead of mesh colliders whenever possible
- Use compound colliders (multiple simple colliders) instead of complex mesh colliders
- Enable "Is Trigger" for colliders that don't need physical responses
- Adjust Time.fixedDeltaTime for fewer physics updates in less demanding games
- Use layer-based collision to skip unnecessary collision checks
- Apply sleep thresholds to make inactive objects stop calculating physics
- Use kinematic Rigidbodies for objects that don't need full physics simulation
- Disable auto-syncing transforms for objects where you manually set position
- Use continuous collision detection only for fast-moving objects
- Don't simulate physics for distant objects
Physics Optimization Code Example:
using UnityEngine;
public class PhysicsOptimizer : MonoBehaviour
{
public float activationDistance = 20f;
public Transform player;
private Rigidbody rb;
private Collider[] colliders;
void Start()
{
rb = GetComponent();
colliders = GetComponentsInChildren();
// Optimize continuous collision detection
rb.collisionDetectionMode = CollisionDetectionMode.Discrete;
// Adjust solver iterations for less accuracy but better performance
rb.solverIterations = 2; // Default is 6
rb.solverVelocityIterations = 1; // Default is 1
// Increase sleep threshold for earlier deactivation
rb.sleepThreshold = 0.1f; // Default is 0.005
// Disable auto sync transforms (if you're not reading the transform directly)
Physics.autoSyncTransforms = false;
}
void Update()
{
if (player != null)
{
// Calculate distance to player
float distance = Vector3.Distance(transform.position, player.position);
// Activate/deactivate physics based on distance
if (distance > activationDistance)
{
DisablePhysics();
}
else
{
EnablePhysics();
// Only use continuous collision detection for close objects
if (distance < 5f)
{
rb.collisionDetectionMode = CollisionDetectionMode.Continuous;
}
else
{
rb.collisionDetectionMode = CollisionDetectionMode.Discrete;
}
}
}
}
void EnablePhysics()
{
if (!rb.isKinematic)
return;
rb.isKinematic = false;
foreach (Collider col in colliders)
{
col.enabled = true;
}
}
void DisablePhysics()
{
if (rb.isKinematic)
return;
rb.isKinematic = true;
foreach (Collider col in colliders)
{
col.enabled = false;
}
}
// Call at the end of frame when you're done changing transforms
void LateUpdate()
{
Physics.SyncTransforms();
}
}
Best Practices for Unity Physics
- Use FixedUpdate for physics code, not Update
- Avoid changing transform directly on physics objects (use Rigidbody movement methods)
- Use appropriate collider types for your objects (primitive colliders for performance)
- Set up proper collision layers to avoid unnecessary collision checks
- Don't scale colliders non-uniformly as it can cause physics inconsistencies
- Use continuous collision detection for fast-moving objects to prevent tunneling
- Balance performance and accuracy by adjusting physics settings
- Use kinematic Rigidbodies for objects controlled by animation or direct code
- Keep physics objects properly scaled (not too small or too large)
- Test physics behavior across different frame rates to ensure consistency