Getting a list of Colliders inside a Trigger

Hello,

I have a giant sphere collider around my player object that is set as a trigger. At any point of the game, I need to get a list of colliders inside the sphere and select the one that is closest to the player. To do this, I need to first find a way to get a list of colliders inside the sphere, which i seem to not be able to do… I’ve dont OnTriggerStay, but that only gives me a Collider rather than a list (or an array) of colliders.

So… How do I get that list?

Thanks!

The best way I have found to do this with a trigger is something like this:

//Has to go at the top of your file
#pragma strict
import System.Collections.Generic;

//The list of colliders currently inside the trigger
var TriggerList : List.<Collider> = new List.<Collider>();

//called when something enters the trigger
function OnTriggerEnter(other : Collider)
{
	//if the object is not already in the list
	if(!TriggerList.Contains(other))
	{
		//add the object to the list
		TriggerList.Add(Other);
	}
}

//called when something exits the trigger
function OnTriggerExit(other : Collider)
{
	//if the object is in the list
	if(TriggerList.Contains(other))
	{
		//remove it from the list
		TriggerList.Remove(Other);
	}
}

Mind you I have not tested this, but I have used this method before on other projects.

You can use Physics.OverlapSphereNonAlloc() for this purpose. It’s available from Unity 5.3 onwards. The results array should be allocated once and re-used, so there’s no garbage being generated.

I want to propose a slightly different solution to this. I know it’s an old question, with an answer, but the accepted answer doesn’t handle the problem of triggers becoming disabled or destroyed, in which case they are never removed from the resulting list of colliders. The second answer (OverlapSphereNonAlloc and the like) are hampered by us having to do those calls for every collider under our rigidbody, instead of being able to rely on the OnTrigger*** functions to easily handle multi-collider triggers. I can see that people are still searching for a solution to this, still commenting on this question and others like it to this day, so I wouldn’t call this necro’ing, though others might. Anyway, I only just coded this and haven’t tested it, but it’s fairly simple code.

Here’s my proposal. You might find it a bit much for something like this, but I’ll only be needing one of these in my game, so I think it can handle it :slight_smile:

I use OnTriggerEnter and OnTriggerStay to add and update the state of my currently registered triggers (their value in the _triggerStates Dictionary is set to “true”), and I use FixedUpdate to prune the triggers, removing those that did not fire an OnTriggerEnter or OnTriggerStay last FixedUpdate, as well as those which were destroyed in-between.

using System.Collections.Generic;
using UnityEngine;

public class Test : MonoBehaviour
{
    private Dictionary<Collider, bool> _triggerStates = new Dictionary<Collider, bool>();
    public List<Collider> _currentTriggers { get; private set; } = new List<Collider>();
    private bool _on = true;

    // FixedUpdate is first in Unity's execution order.
    // We use it to check the state of the triggers we are currently colliding with.
    // To avoid iterating over the Dictionary, because then I can't remove from it while iterating,
    // I keep the equivalent of its keys in a list next to it. This might not be the most memory
    // efficient, until you realize that it doubles as a constantly up-to-date list of all
    // the colliders we are currently triggering, which is what we wanted to get this thing
    // to produce in the first place. Also, they'll just be references, some bools and some hashes.
    // We shouldn't be handling too many collisions like this, anyway.
    // You can easily create functions to turn it on and off, if you find it to be too intensive.
    private void FixedUpdate()
    {
        for (var i = 0; i < _currentTriggers.Count; i++)
        {
            var trigger = _currentTriggers*;*

// If the trigger is no more, for whatever reason, remove it.
if (!trigger)
{
_triggerStates.Remove(trigger);
_currentTriggers.Remove(trigger);
continue;
}

// If _currentTriggers has a collider which _triggerStates doesn’t have, something
// weird has happened (it should not be possible), so default to scrapping it.
// It’ll get added again next FixedUpdate if it is still OnTriggerStay’ing.
if (!_triggerStates.ContainsKey(trigger))
{
_currentTriggers.Remove(trigger);
}
else
{
// This is the exciting part. FixedUpdate runs before the OnTrigger***, so we can
// check here what the results are after the last OnTrigger*** calls came in.
// They all set the state to “true” when adding or stay’ing a trigger, so if its
// state is false here, then it is no longer stay’ing, and OnTriggerExit has not
// been called to remove it, so we do it here.
if (!_triggerStates[trigger])
{
_triggerStates.Remove(trigger);
_currentTriggers.Remove(trigger);
}
else
{
// Reset state to “false”, so the coming OnTrigger*** calls can update them again.
_triggerStates[trigger] = false;
}
}
}
}

// Called when a trigger enters
private void OnTriggerEnter(Collider other)
{
//if the object is not already in the list
if (!_triggerStates.ContainsKey(other))
{
//add the object to the list
_triggerStates.Add(other, true);
_currentTriggers.Add(other);
}
}

// Called every FixedUpdate between OnTriggerEnter and OnTriggerExit (or until the trigger is
// disabled, in which case OnTriggerExit is not called, which is why we’re doing all of this :wink: ).
private void OnTriggerStay(Collider other)
{
if (!_triggerStates.ContainsKey(other))
{
//add the object to the list
_triggerStates.Add(other, true);
_currentTriggers.Add(other);
}
else
{
_triggerStates[other] = true;
}
}

// Called when a trigger exits.
private void OnTriggerExit(Collider other)
{
_triggerStates.Remove(other);
_currentTriggers.Remove(other);
}

public void ResetRegisteredTriggers()
{
_currentTriggers.Clear();
_triggerStates.Clear();
}

public void Toggle(bool on, bool resetCurrentTriggers = true)
{
_on = on;
if(resetCurrentTriggers)
ResetRegisteredTriggers();
}
}