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:
- Create a Physics Material: Right-click in Project window → Create → Physics Material (or Physic Material 2D)
- 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
- 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:
- Define Layers: Edit → Project Settings → Tags and Layers → Layers
- Assign Layers: Select GameObjects and set their Layer in the Inspector
- 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
-
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.
-
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.
-
Unstable stacking of objects
Solution: Increase solver iteration count in Physics settings, and ensure objects have appropriate mass ratios.
-
Jittering when objects are in contact
Solution: Enable interpolation on Rigidbodies, adjust mass values, or use Continuous Collision Detection.
-
Colliders not matching visual appearance
Solution: Use compound colliders (multiple primitive colliders) to better match complex shapes.
-
Performance issues with many collisions
Solution: Use simpler collider shapes, implement object pooling, and use collision layers to filter unnecessary collisions.
-
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
- Use primitive colliders whenever possible (box, sphere, capsule) for better performance
- Set up proper collision layers to avoid unnecessary collision checks
- Make colliders slightly smaller than visual meshes to prevent visual overlapping
- Use compound colliders (multiple primitive colliders) for complex shapes instead of mesh colliders
- Avoid moving colliders by changing transform directly; use Rigidbody movement methods instead
- Don't scale colliders non-uniformly as it can cause physics inconsistencies
- Cache references to components in collision callbacks rather than using GetComponent repeatedly
- Use triggers for detection zones and regular colliders for physical obstacles
- Keep physics consistent by using FixedUpdate for physics operations
- Test collision behavior across different frame rates to ensure consistent results