Easy Learn C#

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:

  1. Use primitive colliders instead of mesh colliders whenever possible
  2. Use compound colliders (multiple simple colliders) instead of complex mesh colliders
  3. Enable "Is Trigger" for colliders that don't need physical responses
  4. Adjust Time.fixedDeltaTime for fewer physics updates in less demanding games
  5. Use layer-based collision to skip unnecessary collision checks
  6. Apply sleep thresholds to make inactive objects stop calculating physics
  7. Use kinematic Rigidbodies for objects that don't need full physics simulation
  8. Disable auto-syncing transforms for objects where you manually set position
  9. Use continuous collision detection only for fast-moving objects
  10. 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

  1. Use FixedUpdate for physics code, not Update
  2. Avoid changing transform directly on physics objects (use Rigidbody movement methods)
  3. Use appropriate collider types for your objects (primitive colliders for performance)
  4. Set up proper collision layers to avoid unnecessary collision checks
  5. Don't scale colliders non-uniformly as it can cause physics inconsistencies
  6. Use continuous collision detection for fast-moving objects to prevent tunneling
  7. Balance performance and accuracy by adjusting physics settings
  8. Use kinematic Rigidbodies for objects controlled by animation or direct code
  9. Keep physics objects properly scaled (not too small or too large)
  10. Test physics behavior across different frame rates to ensure consistency