Version: Unity 6.6 (6000.6)
Language : English
OnCollision events
Create and configure a trigger collider

OnTrigger events

Trigger colliders don’t cause collisions. Instead, they detect other colliders that pass through them, and call functions that you can use to initiate events.

Example uses for triggers include:

  • When the player reaches a specific area at the end of a corridor, activate a cinematic cutscene.
  • When the player character walks within a space in front of a sliding door, trigger an animation to open the door.
  • When projectiles pass through a trigger collider in the far distance, disable or destroy the projectile.

Working with trigger colliders primarily involves the following API functions:

  • Collider.OnTriggerEnter: Unity calls this function on a trigger collider when it first makes contact with another collider.
  • Collider.OnTriggerStay: Unity calls this function on a trigger collider once per frame if it detects another Collider inside the trigger collider.
  • Collider.OnTriggerExit: Unity calls this function on a trigger collider when it ceases contact with another collider.

The following example prints a message to the Console when Unity calls each function.

using UnityEngine;
using System.Collections;

public class DoorObject : MonoBehaviour
{
    // “other” refers to the collider on the GameObject inside this trigger
    void OnTriggerEnter (Collider other)
    {
        Debug.Log ("A collider has entered the DoorObject trigger");
    }

    void OnTriggerStay (Collider other)
    {
        Debug.Log ("A collider is inside the DoorObject trigger");
    }
    
    void OnTriggerExit (Collider other)
    {
        Debug.Log ("A collider has exited the DoorObject trigger");
    }
}

For examples of practical applications for OnTrigger events, refer to Example scripts for collider events.

Detect colliders that are destroyed or disabled inside a trigger

If you disable or destroy a collider while it overlaps a trigger collider, Unity doesn’t call OnTriggerExit for that pair of colliders. The same applies if you deactivate or destroy the trigger collider itself.

Physics backends report only that two shapes start or stop overlapping, so Unity calls OnTriggerExit when the two colliders move apart. Deactivation and destruction don’t produce an exit event for two different reasons:

  • If you disable a collider, or the GameObject it belongs to, the event would report a collider that’s no longer in the simulation. Deactivating a GameObject also stops its scripts from receiving messages at all, unlike a disabled MonoBehaviour, which still receives trigger events.
  • If you destroy a collider, there’s nothing left to report to. The destroyed object can’t receive the event, and the collider that OnTriggerExit supplies as its argument no longer exists.

Unity does not call OnTriggerExit to store and maintain a record of every overlapping pair of colliders in the scene. Maintaining that record would cost CPU time and memory in every project and even in projects would not require that record.

If your project needs an exit event whenever a collider stops overlapping a trigger, record the overlaps yourself and check them each physics update. If you choose which colliders and interactions to record, this approach usually costs much less than maintaining a record of every pair in the scene.

Record each collider’s EntityId, along with any data you need about it, while the collider is still alive. After Unity destroys a collider, the reference to it compares equal to null, so you can’t read any values from it and you can’t rely on it to report its own EntityId. The EntityId that you record on entry is what identifies the collider that left.

Unity can reuse the EntityId of a destroyed object for a different object, so an entry under an EntityId that you already recorded doesn’t always mean the same collider entered twice. Compare the collider reference you recorded with the one Unity supplies, and treat a different reference as evidence that the collider you recorded left. For more information, refer to InstanceID to EntityId migration.

Your record also has to account for the trigger collider itself leaving the simulation, because Unity doesn’t call OnTriggerExit in that case either. Disabling the trigger’s Collider component doesn’t stop the script that holds the record, so check the trigger’s own state each physics update. Deactivating the trigger’s GameObject stops FixedUpdate from running at all, so clear the record in MonoBehaviour.OnDisable instead. Disabling only the MonoBehaviour isn’t the same case: it stops FixedUpdate, but the script keeps receiving trigger messages, and the overlaps it recorded are still valid.

The following example records the colliders that enter a trigger collider, and treats any recorded collider that’s no longer active, and any record held when the trigger stops being simulated, as having left the trigger.

using System.Collections.Generic;
using UnityEngine;

public class TriggerOccupancy : MonoBehaviour
{
    readonly Dictionary<EntityId, (Collider Collider, string Name)> m_Occupants =
        new Dictionary<EntityId, (Collider, string)>();

    readonly List<EntityId> m_Departed = new List<EntityId>();

    Collider m_Trigger;

    void Awake()
    {
        m_Trigger = GetComponent<Collider>();
    }

    void OnTriggerEnter(Collider other)
    {
        var id = other.GetEntityId();

        // Unity can reuse the EntityId of a destroyed object, so a record under this ID might belong to
        // a different collider that left without an exit event. Report that one before overwriting it.
        if (m_Occupants.TryGetValue(id, out var recorded) && recorded.Collider != other)
            HandleExit(id);

        // Record the identity and data you need while the collider is still alive.
        m_Occupants[id] = (other, other.name);
    }

    void OnTriggerExit(Collider other)
    {
        HandleExit(other.GetEntityId());
    }

    void OnDisable()
    {
        // Deactivating this GameObject stops FixedUpdate, so nothing below runs again while the trigger
        // is out of the simulation. Reactivating it makes the remaining overlaps enter again.
        // Disabling only this MonoBehaviour keeps the records valid, so leave them alone.
        if (!gameObject.activeInHierarchy)
            ReportAllAsDeparted();
    }

    void FixedUpdate()
    {
        // Disabling or destroying the trigger's own Collider takes it out of the simulation without
        // stopping this script, and ends every overlap silently. Check the trigger before its occupants.
        if (m_Trigger == null || !m_Trigger.enabled)
        {
            ReportAllAsDeparted();
            return;
        }

        // A collider that Unity destroys or deactivates inside the trigger doesn't send
        // OnTriggerExit, so check the recorded colliders for any that are no longer active.
        m_Departed.Clear();

        foreach (var occupant in m_Occupants)
        {
            var collider = occupant.Value.Collider;

            if (collider == null || !collider.enabled || !collider.gameObject.activeInHierarchy)
                m_Departed.Add(occupant.Key);
        }

        foreach (var id in m_Departed)
            HandleExit(id);
    }

    void ReportAllAsDeparted()
    {
        m_Departed.Clear();
        m_Departed.AddRange(m_Occupants.Keys);

        foreach (var id in m_Departed)
            HandleExit(id);
    }

    void HandleExit(EntityId id)
    {
        if (!m_Occupants.TryGetValue(id, out var occupant))
            return;

        m_Occupants.Remove(id);

        // Use the recorded name: the collider might already be destroyed.
        Debug.Log(occupant.Name + " left the trigger");
    }
}

Collision events have the same limitation. For more information, refer to OnCollision events.

Additional resources

OnCollision events
Create and configure a trigger collider