- Home /
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!
Answer by CiberX15 · Feb 12, 2014 at 11:14 PM
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.
I have the same problem, i created a list and .... but sometimes no collision detected but my list has value!!!
This can happen sometimes depending on the triggers setting. I am fairly certain you can prevent this by adding a rigidbody to the trigger (set it to kinematic if you don't want it to move), then set the rigidbodys collision detection to dynamic. This is worse for performance but guarantees detection even if an entity would normally be moving so fast as to miss the collider between frames.
This doesn't work. The OnTriggerExit won't be called when objects are deactivated or destroyed. The way around this is to make a list of the objects you are touching, loop through it in an Update, and when they are null or deactivated, remove them.
$$anonymous$$aybe in this case it will be better to use Physics.OverlapBox() method to get the list of objects. You just have to setup box same size as trigger
overlapshere or box would work for him but these calls are very heavy on preformance specially when used every frame or often if that's what he needs.
Using this method, is there a way to see and compare the tags of the objects added to the list?
Sure. If you only wanted to add objects with a given tag you could check for that on the OnTriggerEnter() function.
if(!TriggerList.Contains(other) && other.gameObject.tag == [whatever tag you are looking for]) { //add the object to the list TriggerList.Add(Other); }
If you wanted to only have one object of any given tag you could do that too, though you would have to for loop through the existing list to check if any of the currently stored objects have the tag or not, which might hurt performance depending on how many items end up in the list at any given time.
Answer by chris-nolet · Feb 28, 2018 at 09:42 PM
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.
Interesting... So I can launch a separate job that will update my list async and this will be more efficient?
Hi @NoxCaos. You can call this function as-needed, synchronously. I believe it's fast enough to run in Update()
. (This is in contrast to the original Physics.OverlapSphere()
, which should not be called every frame, because it allocates memory on the heap.)
Okay, I see now. Thank you a lot for that solution
Answer by Ultroman · Feb 29, 2020 at 03:43 PM
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 :)
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[i];
// 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 ;) ).
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();
}
}
Your answer
Follow this Question
Related Questions
Can't click gameobject when over another trigger? 1 Answer
Triggering platform animation on colliding with Button 0 Answers
Use trigger with player but have a collider with everything else? 3 Answers
How do you go from one scene, to another, then a third and back again ? 0 Answers
Stop object colliders (non-mesh) bouncing on collision 1 Answer