Easy Learn C#

Collisions & Triggers in Unity

Introduction to Unity Collision System

Collisions and triggers are fundamental components of game physics in Unity. They allow objects to interact with each other physically, detect overlaps, and respond to various interaction scenarios. Understanding how collisions work is essential for creating realistic physics-based interactions, implementing game mechanics, and optimizing performance.

In this guide, you'll learn:

  • How Unity's collision system works
  • Different types of colliders and their uses
  • The difference between colliders and triggers
  • How to detect and respond to collisions in code
  • Best practices for efficient collision detection
  • Common collision-related problems and solutions

Collider Components

Colliders define the physical shape of an object for collision detection. Unity provides several types of colliders for different needs.

Types of Colliders:

  • Box Collider: Simple rectangular shape, good for boxes, crates, buildings
  • Sphere Collider: Spherical shape, good for balls, projectiles
  • Capsule Collider: Pill-shaped, good for character controllers
  • Mesh Collider: Follows the exact shape of a mesh, more expensive but precise
  • Wheel Collider: Specialized for vehicle wheels with suspension and friction
  • Terrain Collider: For Unity terrain objects
  • 2D Variants: Box, Circle, Polygon, Edge, and Composite colliders for 2D games

Collider selection considerations:

  • Performance: Primitive colliders (Box, Sphere, Capsule) are more efficient
  • Accuracy: Mesh colliders are more precise but computationally expensive
  • Shape: Choose a collider that reasonably approximates the visual shape
  • Complexity: For complex objects, consider using multiple primitive colliders

Collider Properties

Key properties of colliders:

  • Is Trigger: When enabled, the collider detects overlaps but doesn't produce physics reactions
  • Material: Physics Material that defines friction and bounciness
  • Center/Size: Position and dimensions of the collider relative to the GameObject
  • Convex (Mesh Collider): Enables collision detection with other Mesh Colliders (with some limitations)

Rigidbody Components

Rigidbodies work together with colliders to enable full physics simulation and collision response.

Rigidbody Properties:

  • Mass: How heavy the object is (affects physics interactions)
  • Drag/Angular Drag: How quickly the object slows down when moving/rotating
  • Use Gravity: Whether the object is affected by gravity
  • Is Kinematic: When enabled, the object isn't affected by physics (useful for moving platforms)
  • Interpolate: Smooths position between physics updates
  • Collision Detection: Controls how collision detection works at high speeds
  • Constraints: Freeze position or rotation on specific axes

Rigidbody interactions:

  • Dynamic: Has Rigidbody, not Kinematic - Fully simulated by physics
  • Kinematic: Has Rigidbody, is Kinematic - Moved by script, affects other objects
  • Static: No Rigidbody - Doesn't move, collides with Dynamic objects

Collision Detection in Code

Unity provides several methods to detect and respond to collisions in your scripts.

Collision Event Methods:


using UnityEngine;

public class CollisionExample : MonoBehaviour
{
    // COLLISION EVENTS (physical collisions with non-trigger colliders)
    
    // Called when this collider/rigidbody begins touching another collider/rigidbody
    void OnCollisionEnter(Collision collision)
    {
        Debug.Log("Collision began with: " + collision.gameObject.name);
        
        // Get information about the collision
        
        // The GameObject we collided with
        GameObject otherObject = collision.gameObject;
        
        // The contact points (where the objects are touching)
        ContactPoint contact = collision.contacts[0];
        
        // The collision normal (direction of the impact)
        Vector3 normal = contact.normal;
        
        // The point where the collision occurred
        Vector3 point = contact.point;
        
        // The relative velocity of the collision
        Vector3 relativeVelocity = collision.relativeVelocity;
        
        // The impulse force of the collision
        float impulse = collision.impulse.magnitude;
        
        // Example: Check the tag of the object we collided with
        if (otherObject.CompareTag("Enemy"))
        {
            TakeDamage(10);
            // Handle collision with enemy
        }
        else if (otherObject.CompareTag("Ground"))
        {
            // Handle landing on ground
            PlayLandingSound();
        }
        
        // Example: Apply damage based on collision force
        float damageAmount = impulse / 10f;
        if (damageAmount > 5f)
        {
            TakeDamage(Mathf.RoundToInt(damageAmount));
        }
        
        // Visual feedback at collision point
        CreateImpactEffect(point, normal);
    }
    
    // Called each frame while the collision is active
    void OnCollisionStay(Collision collision)
    {
        // This runs every frame that the objects remain in contact
        
        // Example: Slowly push back against the object
        Rigidbody rb = GetComponent();
        if (rb != null)
        {
            Vector3 pushDirection = transform.position - collision.transform.position;
            pushDirection.Normalize();
            rb.AddForce(pushDirection * 0.5f);
        }
    }
    
    // Called when this collider/rigidbody stops touching another collider/rigidbody
    void OnCollisionExit(Collision collision)
    {
        Debug.Log("Collision ended with: " + collision.gameObject.name);
        
        // Example: Play a separation sound
        PlaySeparationSound();
    }
    
    // TRIGGER EVENTS (overlaps with trigger colliders)
    
    // Called when this collider enters a trigger collider
    void OnTriggerEnter(Collider other)
    {
        Debug.Log("Trigger entered: " + other.gameObject.name);
        
        // Example: Player enters a trigger zone
        if (other.CompareTag("Player"))
        {
            // Activate a checkpoint
            ActivateCheckpoint();
        }
        else if (other.CompareTag("Pickup"))
        {
            // Collect an item
            CollectItem(other.gameObject);
        }
        else if (other.CompareTag("TrapZone"))
        {
            // Enter a dangerous area
            EnterDangerZone();
        }
    }
    
    // Called each frame while this collider is in a trigger
    void OnTriggerStay(Collider other)
    {
        // Example: Player is in a healing zone
        if (other.CompareTag("HealZone"))
        {
            // Heal the player gradually
            Heal(Time.deltaTime * 5f);
        }
        else if (other.CompareTag("DamageZone"))
        {
            // Damage the player gradually
            TakeDamage(Time.deltaTime * 10f);
        }
    }
    
    // Called when this collider exits a trigger collider
    void OnTriggerExit(Collider other)
    {
        Debug.Log("Trigger exited: " + other.gameObject.name);
        
        // Example: Player exits a trigger zone
        if (other.CompareTag("SafeZone"))
        {
            // No longer in the safe zone
            ExitSafeZone();
        }
    }
    
    // 2D COLLISION EVENTS (for 2D physics)
    
    void OnCollisionEnter2D(Collision2D collision)
    {
        // Similar to OnCollisionEnter but for 2D physics
        Debug.Log("2D collision started with: " + collision.gameObject.name);
    }
    
    void OnCollisionStay2D(Collision2D collision)
    {
        // Called each frame for active 2D collisions
    }
    
    void OnCollisionExit2D(Collision2D collision)
    {
        // Called when a 2D collision ends
    }
    
    void OnTriggerEnter2D(Collider2D other)
    {
        // Similar to OnTriggerEnter but for 2D triggers
    }
    
    void OnTriggerStay2D(Collider2D other)
    {
        // Called each frame for active 2D triggers
    }
    
    void OnTriggerExit2D(Collider2D other)
    {
        // Called when exiting a 2D trigger
    }
    
    // Example helper methods (would be implemented in real code)
    void TakeDamage(float amount)
    {
        Debug.Log("Taking damage: " + amount);
    }
    
    void PlayLandingSound()
    {
        Debug.Log("Playing landing sound");
    }
    
    void CreateImpactEffect(Vector3 position, Vector3 normal)
    {
        Debug.Log("Creating impact effect at " + position);
    }
    
    void PlaySeparationSound()
    {
        Debug.Log("Playing separation sound");
    }
    
    void ActivateCheckpoint()
    {
        Debug.Log("Checkpoint activated");
    }
    
    void CollectItem(GameObject item)
    {
        Debug.Log("Collecting item: " + item.name);
        Destroy(item);
    }
    
    void EnterDangerZone()
    {
        Debug.Log("Entered danger zone");
    }
    
    void Heal(float amount)
    {
        Debug.Log("Healing for: " + amount);
    }
    
    void ExitSafeZone()
    {
        Debug.Log("Exited safe zone");
    }
}
                            

Key collision methods:

  • OnCollisionEnter/Exit/Stay: For physical collisions where objects react with physics
  • OnTriggerEnter/Exit/Stay: For overlaps with trigger colliders that don't cause physical reactions
  • 2D variants: Same methods but for 2D physics system

Important collision data:

  • contact points: Where objects are touching
  • normal: Direction of the impact
  • relative velocity: Speed and direction of objects relative to each other
  • impulse: Force of the impact

Manual Collision Detection

Besides collision events, Unity provides methods to manually check for collisions and overlaps.

Raycasting and Overlap Testing:


using UnityEngine;

public class ManualCollisionDetection : MonoBehaviour
{
    public float raycastDistance = 10f;
    public LayerMask raycastLayers;
    
    void Update()
    {
        // RAYCASTING EXAMPLES
        
        // Simple raycast
        RaycastHit hit;
        if (Physics.Raycast(transform.position, transform.forward, out hit, raycastDistance))
        {
            // Something was hit
            Debug.Log("Hit object: " + hit.collider.gameObject.name);
            Debug.Log("Hit point: " + hit.point);
            Debug.Log("Hit normal: " + hit.normal);
            Debug.Log("Distance: " + hit.distance);
            
            // You can get the material at the hit point
            if (hit.collider.sharedMaterial != null)
            {
                Debug.Log("Hit material: " + hit.collider.sharedMaterial.name);
            }
        }
        
        // Raycast with layer mask (only detect certain layers)
        if (Physics.Raycast(transform.position, transform.forward, out hit, raycastDistance, raycastLayers))
        {
            // Only objects on the specified layers will be detected
            Debug.Log("Layer-filtered raycast hit: " + hit.collider.gameObject.name);
        }
        
        // Raycast all - get all hits along the ray
        RaycastHit[] hits = Physics.RaycastAll(transform.position, transform.forward, raycastDistance, raycastLayers);
        foreach (RaycastHit hitInfo in hits)
        {
            // Process each hit
            Debug.Log("RaycastAll hit: " + hitInfo.collider.gameObject.name);
        }
        
        // PHYSICS OVERLAP TESTS
        
        // Check for colliders in a sphere
        Collider[] sphereResults = Physics.OverlapSphere(transform.position, 5f, raycastLayers);
        Debug.Log("Found " + sphereResults.Length + " colliders in sphere");
        
        // Check for colliders in a box
        Vector3 boxSize = new Vector3(2f, 2f, 2f);
        Collider[] boxResults = Physics.OverlapBox(transform.position, boxSize / 2f, transform.rotation, raycastLayers);
        Debug.Log("Found " + boxResults.Length + " colliders in box");
        
        // Check for colliders in a capsule
        Vector3 capsulePoint1 = transform.position + Vector3.up;
        Vector3 capsulePoint2 = transform.position - Vector3.up;
        float capsuleRadius = 1f;
        Collider[] capsuleResults = Physics.OverlapCapsule(capsulePoint1, capsulePoint2, capsuleRadius, raycastLayers);
        Debug.Log("Found " + capsuleResults.Length + " colliders in capsule");
        
        // LINECAST, SPHERECAST, BOXCAST
        
        // Linecast (similar to raycast but with start and end points)
        Vector3 lineEnd = transform.position + transform.forward * raycastDistance;
        if (Physics.Linecast(transform.position, lineEnd, out hit))
        {
            Debug.Log("Linecast hit: " + hit.collider.gameObject.name);
        }
        
        // SphereCast (like a raycast but with thickness)
        float sphereRadius = 0.5f;
        if (Physics.SphereCast(transform.position, sphereRadius, transform.forward, out hit, raycastDistance))
        {
            Debug.Log("SphereCast hit: " + hit.collider.gameObject.name);
        }
        
        // BoxCast (like a raycast but with a box)
        Vector3 boxHalfExtents = new Vector3(0.5f, 0.5f, 0.5f);
        if (Physics.BoxCast(transform.position, boxHalfExtents, transform.forward, out hit, transform.rotation, raycastDistance))
        {
            Debug.Log("BoxCast hit: " + hit.collider.gameObject.name);
        }
        
        // 2D VARIANTS
        
        // 2D Raycast example
        RaycastHit2D hit2D = Physics2D.Raycast(transform.position, transform.right, raycastDistance, raycastLayers);
        if (hit2D.collider != null)
        {
            Debug.Log("2D Raycast hit: " + hit2D.collider.gameObject.name);
        }
        
        // 2D Circle overlap example
        Collider2D[] circleResults = Physics2D.OverlapCircleAll(transform.position, 5f, raycastLayers);
        Debug.Log("Found " + circleResults.Length + " 2D colliders in circle");
    }
    
    // Visualize raycasts and overlap tests in the Scene view
    void OnDrawGizmos()
    {
        // Draw the main raycast
        Gizmos.color = Color.red;
        Gizmos.DrawLine(transform.position, transform.position + transform.forward * raycastDistance);
        
        // Draw a sphere for the OverlapSphere
        Gizmos.color = Color.green;
        Gizmos.DrawWireSphere(transform.position, 5f);
        
        // Draw a box for the OverlapBox
        Gizmos.color = Color.blue;
        Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one);
        Gizmos.DrawWireCube(Vector3.zero, new Vector3(2f, 2f, 2f));
        Gizmos.matrix = Matrix4x4.identity;
    }
}
                            

Manual detection methods:

  • Raycast: Line from a point in a direction
  • SphereCast/BoxCast: Like Raycast but with volume
  • OverlapSphere/Box/Capsule: Find all colliders in a volume
  • Linecast: Line between two specific points
  • 2D variants: Similar functions for 2D physics

Uses for manual detection:

  • Line of sight: Check if objects can "see" each other
  • Range detection: Find objects within a certain distance
  • Targeting: Find potential targets for weapons or abilities
  • Ground detection: Check if character is on ground
  • Environmental interaction: Detect interactive objects

Physics Materials

Physics Materials define how surfaces interact in terms of friction and bounciness (restitution).

Creating and Using Physics Materials:

  1. Create a Physics Material: Right-click in Project window → Create → Physics Material (or Physic Material 2D)
  2. Configure Properties:
    • Dynamic Friction: Friction when objects are moving
    • Static Friction: Friction when objects are stationary
    • Bounciness: How bouncy the surface is (0 = no bounce, 1 = perfect bounce)
    • Friction Combine: How to combine friction with other materials (Average, Minimum, Maximum, Multiply)
    • Bounce Combine: How to combine bounciness with other materials
  3. Apply to Collider: Drag the Physics Material to the Material property of a Collider component

Common Physics Materials examples:

  • Ice: Low friction (0.05), low bounciness
  • Rubber: Medium friction (0.4), high bounciness (0.8)
  • Metal: Medium friction (0.3), medium bounciness (0.4)
  • Mud: High friction (0.8), low bounciness (0.1)

Collision Layers and Filtering

Unity uses layers and collision matrices to control which objects can collide with each other.

Setting Up Collision Layers:

  1. Define Layers: Edit → Project Settings → Tags and Layers → Layers
  2. Assign Layers: Select GameObjects and set their Layer in the Inspector
  3. Configure Collision Matrix: Edit → Project Settings → Physics → Layer Collision Matrix

Common layer setups:

  • Player: The player character
  • Enemy: Enemy characters
  • Ground: Terrain and floors
  • Pickup: Collectible items
  • Obstacle: Walls and barriers
  • Projectile: Bullets, arrows, etc.
  • Trigger: Objects that only act as triggers
  • Ignore Raycast: Objects that shouldn't be detected by raycasts

Example collision rules:

  • Player can collide with Ground, Obstacle, Enemy, Pickup
  • Projectiles can collide with Player, Enemy, Obstacle, but not other Projectiles
  • Pickup can only collide with Player
  • Triggers don't collide with anything (use trigger callbacks instead)

Common Collision Issues and Solutions

  1. Objects passing through each other at high speeds

    Solution: Use Continuous Collision Detection on fast-moving Rigidbodies, or use larger colliders for small, fast objects.

  2. Character getting stuck on edges

    Solution: Use a capsule collider for characters and ensure the bottom is rounded. Add a Physics Material with low friction.

  3. Unstable stacking of objects

    Solution: Increase solver iteration count in Physics settings, and ensure objects have appropriate mass ratios.

  4. Jittering when objects are in contact

    Solution: Enable interpolation on Rigidbodies, adjust mass values, or use Continuous Collision Detection.

  5. Colliders not matching visual appearance

    Solution: Use compound colliders (multiple primitive colliders) to better match complex shapes.

  6. Performance issues with many collisions

    Solution: Use simpler collider shapes, implement object pooling, and use collision layers to filter unnecessary collisions.

  7. Trigger events not firing

    Solution: Ensure at least one object has a non-kinematic Rigidbody, and check layer collision settings.

Best Practices for Collision and Triggers

  1. Use primitive colliders whenever possible (box, sphere, capsule) for better performance
  2. Set up proper collision layers to avoid unnecessary collision checks
  3. Make colliders slightly smaller than visual meshes to prevent visual overlapping
  4. Use compound colliders (multiple primitive colliders) for complex shapes instead of mesh colliders
  5. Avoid moving colliders by changing transform directly; use Rigidbody movement methods instead
  6. Don't scale colliders non-uniformly as it can cause physics inconsistencies
  7. Cache references to components in collision callbacks rather than using GetComponent repeatedly
  8. Use triggers for detection zones and regular colliders for physical obstacles
  9. Keep physics consistent by using FixedUpdate for physics operations
  10. Test collision behavior across different frame rates to ensure consistent results