Search Unity

OnTriggerExit/OnCollisionExit and destroyed GameObjects

Discussion in 'Physics' started by Michael-Thomas, Jan 22, 2016.

  1. Michael-Thomas

    Michael-Thomas

    Joined:
    Jul 8, 2014
    Posts:
    20
    I have many instances in my game where I want to do something whenever certain other objects are touching a different type of object. So I can write something like the following:

    Code (CSharp):
    1. class FooBehaviour : MonoBehaviour {
    2.  
    3.     int insideCount = 0;
    4.  
    5.     public void OnTriggerEnter (Collider col) {
    6.         if (CareAbout(col)) {
    7.             insideCount++;
    8.         }
    9.     }
    10.     public void OnTriggerExit (Collider col) {
    11.         if (CareAbout(col)) {
    12.             insideCount--;
    13.         }
    14.     }
    15.  
    16.     public void Update () {
    17.         if (insideCount > 0) {
    18.             DoSomething();
    19.         }
    20.     }
    21. }
    ... which works fine. Until one of the objects that happens to be touching my FooBehaviour object is destroyed. In this case, OnTriggerExit is never called and we now have a dangling extra insideCount with no object around that will ever cause it to be decremented.

    This seems like it cannot be that uncommon a case (and I've seen several other posts of this nature elsewhere with various hacky/fragile/unpleasant workarounds) and yet there doesn't seem to be a good way to do this kind of thing from Unity.

    So, what I'm looking for is either a) Is there some actually good way to do what I want to do here but which is robust against the objects that are intersecting me being destroyed or b) Some official response from Unity as to why OnTriggerExit is not called in this case. If it's just a matter of not wanting to break backward compatibility in some way, can we get an OnTriggerDestroyed() call or something?

    As it currently stands, I'm having to make my game code much, much less efficient and much less elegant in order to work around this unfortunate case.
     
    mishasniper, llMarty and Zullar like this.
  2. Michael-Thomas

    Michael-Thomas

    Joined:
    Jul 8, 2014
    Posts:
    20
    Just to add to my post above, add "when the collider is disabled" as a case when OnTriggerExit is (annoyingly) not called.
     
    PrimeNexusDev likes this.
  3. Hyblademin

    Hyblademin

    Joined:
    Oct 14, 2013
    Posts:
    725
    I guess it would make things a bit easier if OnTriggerExit() was called when a trigger disappears, but personally I wouldn't consider it hacky or ugly to add a bit more logic that causes the destroyed object to check whether it was overlapping anything that is counting overlaps and call a function to decrement insideCount if that's the case.

    Maybe you could to create a list of all the instance ID's of the objects overlapping it and check the length, and just use OnTriggerStay() to update the list while rejecting duplicates; I have no idea how fast that would be, though.
     
  4. hoesterey

    hoesterey

    Joined:
    Mar 19, 2010
    Posts:
    659
    This makes me crazy as well. I've registered and unregistered events onenter/exit so that I can call "notify death event" on death. Though unity isn't 100% accurate with Triggers in my experience and events are not free.

    I've also heard a lot of people move the object just before they destroy it.
     
  5. Michael-Thomas

    Michael-Thomas

    Joined:
    Jul 8, 2014
    Posts:
    20
    @hoesterey - I've seen that suggestion to move the object and then wait a frame before destroying it. That approach strikes me as unacceptably fragile and error-prone, not to mention unpleasantly inefficient for the physics system to deal with - Physics doesn't tend to react well when you warp an object away like that.
     
    raymondy1, FOXAcemond and Ultroman like this.
  6. hoesterey

    hoesterey

    Joined:
    Mar 19, 2010
    Posts:
    659
    Agreed. I find this extremely hard to deal with as well. :)
     
  7. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
  8. hoesterey

    hoesterey

    Joined:
    Mar 19, 2010
    Posts:
    659
    Best workaround I've found is to register an event OnEnter and have the object remove itself, unregister the event, and trigger the appropriate OnExit scripts when it is destroyed.
     
    Zullar likes this.
  9. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    I'm trying to do something like this. I have a ColliderWatcher script that maintains a list of current collisions. When the object is disabled/destroyed it clears the list by accessing any current collisions and removing them on the *other* objects in addition to self. This calls an "OnExit" method even if you destroy/disable something. Works OK except...

    With a complex object hierarchy w/ rigidbody and multiple colliders the ColliderWatcher can't maintain a proper list because EVERY trigger from the parent layer, or any child layers, all trickle down to the parent layer (because there's a rigidbody). So I have no way of distinguishing what collider was triggered. Ugh.

    Any ideas?
     
  10. hoesterey

    hoesterey

    Joined:
    Mar 19, 2010
    Posts:
    659
    I'm not sure of your use case.

    Psudo Code:
    On each parent transform with a set of colliders add a single script that is your "Collider Watcher"

    Psudo Code:
    Dictionary<Collider, GameObject>
    event TriggerClear;
    OnEnter()
    {
    if(Dictionary.ContainsKey(Collider))
    {
    Dictionary.Add(Collider, Collider.Gameobject)
    Collider.Gamobject.OnDestroyEvent+= OnDestroy;
    }

    }

    void OnExit()
    {
    RemoveCollider(Collider);
    }

    void RemoveCollider(Collider)
    {
    if(Dictionary.ContainsKey(Collider))
    {
    Dictionary.Remove(Collider);
    Collider.Gameobject.OnDestroyEvent -= OnDestroy;
    }
    if(Dictionary.Count == 0)
    {
    TriggerClear.Invoke();
    }
    }

    void OnDestroy(Collider)
    {
    RemoveCollider(Collider);
    }
     
    Zullar likes this.
  11. SoftwareGeezers

    SoftwareGeezers

    Joined:
    Jun 22, 2013
    Posts:
    902
    This strikes me as overkill. All I need is a count of objects inside a trigger, just to test if any is present. What should be achieved with just a count is now turning into a complex, large overhead job of recording every object in a trigger and testing it every frame. That's pretty stupid and inefficient.

    Surely in the case of an object being destroy, it should first notify every appropriate system that it's gone, and then be removed. Everyone coding is expecting this behaviour and is getting confused when OnExit isn't being called, which shows that's the natural way to do it. ;)

    An alternative would be to perform a manual call for trigger overlaps, but there's no means to do that outside of the most basic primitives. A manual 'TestCollisions' would suffice, similar to the Cast methods.

    Edit: I see there is a Collder2D.Cast() method!
    https://docs.unity3d.com/ScriptReference/Collider2D.Cast.html

    So could call this every update to count how many triggers are present.
     
  12. hoesterey

    hoesterey

    Joined:
    Mar 19, 2010
    Posts:
    659
    I wouldn't do physics operations on every update, you will get a nasty perf hit. Sadly right now to the best of my knowledge adding a object to a List OnEnter and removing it OnExit OR though an event OnDeath is a lot of work (and shouldn't be) but you don't have to test it every frame if you use events so you shouldn't have a huge perf hit.
     
  13. SoftwareGeezers

    SoftwareGeezers

    Joined:
    Jun 22, 2013
    Posts:
    902
    Sage advice but dependent entirely on the game. For what I'm doing, a test every frame is fine. Worst case I can move it to every few frames.
     
  14. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    I've created a ColliderWatcher script that I attach to all colliders. It records Enter/Exit and maintains a list of current collisions. However, it also handles OnDisable/OnDestroy and calls DisableExit for each current trigger/collision so there are never "dangling" collisions where Exit is failed to be called when an object is destroyed or scene is exited.

    It ended up being complicated to script (due to the way Unity's collision engine handles complex hierarchies and everything trickles down to the rigidbody... conflating all triggers/collisions). It's also tricky to handle how to disable colliders *during* a collision physics update (basically all collisions must be sorted and Enter's called first, then Exit's... never Exit before Enter)

    You can subscribe by ColliderWatcher.AddListener(TriggerCollisionData) which contains
    -Enum Trigger or Collision
    -Enum Enter, Exit, or ExitDisable (Enter and Exit are like normal, but ExitDisable is new and different and called when one of the 2 contacting colliders is disabled.)
    -Collision information (if it's a collision not a trigger)
    -Self Collider
    -Other Collider

    This solves issues #1, #3, #5 (It could also solve #4 with some sorting work)
    https://forum.unity3d.com/threads/collider-issues.450982/
     
  15. AndyP-123

    AndyP-123

    Joined:
    Jun 14, 2013
    Posts:
    2
    I'm not sure whether it's best to create further events to handle objects being destroyed or disabled, but that sounds like a relatively clean way to code it, although I think that having multiple subscribers to events can create garbage, and you then have to manage the event subscriptions to avoid memory leaks :/ Moving objects on destroy/disable sounds really horrible, but I guess if it works for some people, then more power to them :D

    I have a sensor class in my game that keeps a list of objects that have entered the trigger, removes them when they exit, and then has an update loop that removes the objects if their reference has become null. This only works for objects that have been destroyed. I could add a check for gameObject.activeInHierarchy to handle the disabled case, but since I'm not disabling colliders on objects that are detected by the sensor, I didn't add that yet. I don't like this solution very much, but it works for what I'm doing, and I actually need the list of objects anyway, since the sensor needs to know what is in range, and I also keep square distances so I can get the closest or sort the list etc.

    The reason I came here today is that I just made another class that needs to keep track of colliding objects to detect if an object is on the ground (which you can also do by constantly raycasting), and encountered a bug when disabling an object, so I came looking for a clean solution. Still not sure what the best answer is, but I guess I should do some tests and see what works best.

    It's a little sad that we have to write a bunch of hacky-feeling code to deal with an issue that could probably just be cleanly handled by OnTriggerExit and OnCollisionExit. Even very simple update methods and checks are not free, and neither is having lots of things subscribing to events, and complexity always adds a much greater chance of introducing new bugs.
     
    Zullar likes this.
  16. telecaster

    telecaster

    Joined:
    Jan 6, 2013
    Posts:
    29
    why not have a Boolean flag on every object that is set if it is in a collision with the FooBehaviour, by the FooBehaviour script. This sets the flag on the collider object when it is in a collision.
    The destroy command should check if the destroyed object has a flag and reduce the insidecount if the flag is set.

    I hope that makes sense. to me it is clean, has very little overhead and is frame rate independent.
     
  17. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    Unity's physics/collision system has lots of issues
    -OnCollision/TriggerExit is not called when the other object in a current collision is destroyed.
    -OnCollision/TriggerExit is not called when the self object in a current collision is destroyed.
    -OnCollision/TriggerExit is not called when the other object in a current collision is disabled.
    -OnCollision/TriggerExit is not called when the self object in a current collision is disabled.
    -OnCollision/TriggerExit is not called when the other object in a current collision's layer is changed (If I remember right)
    -OnCollision/TriggerExit is not called when the self object in a current collision layer is changed (If I remember right)
    -The order of OnCollision/Trigger Enter/Exit is not consistent. If an object is changed to/from a Collider to a Trigger you can get 2 Enter's or 2 Exit's in a row.
    -Unity's collision system does not allow you to check a list of current collisions (and maintaining a list is unreliable due to bugs above)
    -Unity's collision system does not allow you to instantly spot check what is in a new mesh collider. For example if a dragon breathes fire you wanted to create a cone mesh and take a snapshot to see what is within the cone there is no way to do instantly do this. You must instantiate the cone, and then wait for the next fixed update.
    -Unity's collision system does not handle hierarchy well. If you have several colliders on each object it's difficult to tell what collided/triggered with what. Colliders & Triggers also do not behave consistently in hierarchies.
    -Rigidbody-interpolate affects velocity (https://fogbugz.unity3d.com/default.asp?987466_rtr9jodnm5n8afbp)

    Here's the code I used to fix. It's really ugly. I have to add a ColliderWatcher to each collider. But it fixes most bugs above. Maybe somebody can clean it up a bit.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic; //allow List and Dictionary
    5.  
    6. //This script is always placed in the same level of the hierarchy as the collider
    7. public sealed class ColliderWatcher : MonoBehaviour
    8. {
    9.     public Collider colliderSelf { get; private set; }
    10.     private Rigidbody attachedRigidbody; //store this value for errorChecking at a later time (to make sure a rigidbody isn't added AFTER colliderWatcher is initialized)
    11.  
    12.     public List<ColliderWatcher> listColliderWatcherOther { get; private set; } //list of current triggers/collisions
    13.     //public List<Collider> listColliderOther { get; private set; }
    14.  
    15.     //public delegate void DelegateCollider(Collider colliderOther, Collision collisionCBNIn); //signature of function
    16.     //private DelegateCollider OnEnterEventSender; //called by OnCollisionEnter OR OnTriggerEnter
    17.     //private DelegateCollider OnExitEventSender; //called by OnCollisionExit OR OnTriggerExit OR when either collider is disabled/destroyed
    18.  
    19.     public delegate void DelegateTriggerCollision(TriggerCollisionData triggerCollisionData); //signature of function
    20.     private DelegateTriggerCollision OnTriggerCollisionEventSender; //called by OnTriggerXXX OR OnCollisionXXX OR when disabled (or when something it is currently colliding with is disabled)
    21.  
    22.     //public delegate void DelegateTriggerCollide(EnumEnterExit enumEnterExitIn, Collider colliderOther, Collision collisionCBNIn)
    23.  
    24.     //public Collider[] debugColliderArray; //serialized for debug
    25.     //public string[] debugCurrentCollisions;
    26.  
    27.     private void Awake()
    28.     {
    29.         //listColliderOther = new List<Collider>(); //initialize list
    30.         listColliderWatcherOther = new List<ColliderWatcher>(); //initialize list
    31.         InitializeFindColliderSelf();
    32.     }
    33.  
    34.     private void Start()
    35.     {
    36.         InitializeAddColliderWatcherHelper(); //is it better to do this during Start()?  That way any changing of hierarchy is complete before it adds ColliderWatcherHelper?
    37.     }
    38.  
    39.  
    40.     private void InitializeFindColliderSelf()
    41.     {
    42.         colliderSelf = GetComponent<Collider>(); //every ColliderWatcher must be in the same hierarchy level as it's collider (even though OnCollision/Trigger/Enter/Exit may not be called on this layer)
    43.         if (colliderSelf == null) //ErrorCheck
    44.         {
    45.             BatDebug.Error(this, "768uytyrt3r467", "colliderSelf == null");
    46.         }
    47.     }
    48.  
    49.     private void InitializeAddColliderWatcherHelper()
    50.     {
    51.         if (colliderSelf != null) //ErrorCheck
    52.         {
    53.             attachedRigidbody = colliderSelf.attachedRigidbody; //the rigidbody MUST be attached before ColliderWatcher is initialized.  otherwise ColliderWatcherSender will be in the wrong layer of the hierarchy
    54.             if (attachedRigidbody == null) //this collider does not have an attachedRigidbody
    55.             {
    56.                 colliderSelf.transform.gameObject.AddComponent<ColliderWatcherHelper>(); //add the ColliderWatcherHelper directly to this layer of hierarchy
    57.             }
    58.             else //attachedRigidbody = true
    59.             {
    60.                 ColliderWatcherHelper colliderWatcherHelperTemp = attachedRigidbody.gameObject.GetComponent<ColliderWatcherHelper>();
    61.                 if (colliderWatcherHelperTemp == null)
    62.                 {
    63.                     //Debug.Logz("Adding ColliderWatcherHelper to rigidbody");
    64.                     attachedRigidbody.gameObject.AddComponent<ColliderWatcherHelper>(); //add the ColliderWatcherHelper to the parent layer of hierarchy that contains rigidbody
    65.                 }
    66.                 //else
    67.                 //{
    68.                 //    Debug.Logz("Not ColliderWatcherHelper to rigidbody.  Already has this compoment.  Can occur if there are multiple colliders all feeding into 1 rigidbody");
    69.                 //}
    70.  
    71.             }
    72.         }
    73.         else
    74.         {
    75.             BatDebug.Error(this, "5y6uhtrgr3645y7u6jyin", "colliderSelf == null");
    76.         }
    77.     }
    78.  
    79.     //public void AddColliderOther(Collider colliderOtherIn)
    80.     //{
    81.     //    Debug.Logz("ColliderWatcher2.AddColliderOther");
    82.     //}
    83.  
    84.     //public void RemoveColliderOther(Collider colliderOtherIn)
    85.     //{
    86.     //    Debug.Logz("ColliderWatcher2.RemoveColliderOther");
    87.     //}
    88.  
    89.     /*
    90. public void AddRemoveColliderOther(Collider colliderOtherIn, EnumEnterExit enumEnterExitIn) //called by ColliderWatcherHelper when a pair is found
    91. {
    92.     if(colliderOtherIn != null) //ErrorCheck
    93.     {
    94.         if(enumEnterExitIn == EnumEnterExit.Enter)
    95.         {
    96.             AddColliderToList(colliderOtherIn);
    97.         }
    98.         else if (enumEnterExitIn == EnumEnterExit.Exit)
    99.         {
    100.             RemoveColliderFromList(colliderOtherIn);
    101.         }
    102.         else
    103.         {
    104.             Debug.Logz("ERROR: Invalid enum");
    105.         }
    106.     }
    107.     else
    108.     {
    109.         Debug.Logz("ERRORz");
    110.     }
    111. }
    112. */
    113.  
    114.     private void AddColliderWatcherToList(ColliderWatcher colliderWatcherOtherIn) //Add to list w/ ErrorCheck
    115.     {
    116.         if (!listColliderWatcherOther.Contains(colliderWatcherOtherIn)) //ErrorCheck, make sure list does not already contain colliderWatcherOther
    117.         {
    118.             listColliderWatcherOther.Add(colliderWatcherOtherIn); //add to list
    119.         }
    120.         else
    121.         {
    122.             BatDebug.Error(this, "656uyjnhgrer3243", "Already contains collider: Should never happen");
    123.         }
    124.     }
    125.  
    126.     private void RemoveColliderWatcherFromList(ColliderWatcher colliderWatcherOtherIn) //Remove from list w/ ErrorCheck
    127.     {
    128.         if (listColliderWatcherOther.Contains(colliderWatcherOtherIn)) //ErrorCheck, make sure list does not already contain colliderWatcherOther
    129.         {
    130.             listColliderWatcherOther.Remove(colliderWatcherOtherIn); //remove from list
    131.         }
    132.         else
    133.         {
    134.             BatDebug.Error(this, "457yu6jythrgtr345y", "List does not contain ColliderWatcher: Should never happen");
    135.         }
    136.     }
    137.  
    138.     /*
    139.     private void AddColliderToList(Collider colliderOtherIn)
    140.     {
    141.         //Debug.Logz(gameObject.name + ".AddColliderToList " + colliderOtherIn.gameObject.name);
    142.         if (!listColliderOther.Contains(colliderOtherIn)) //ErrorCheck, make sure list does not already contain colliderOther
    143.         {
    144.             listColliderOther.Add(colliderOtherIn); //add to list
    145.             if (colliderOtherIn.GetComponent<ColliderWatcher>() == null) //ErrorCheck, make sure colliderOther has an associated ColliderWatcher
    146.             {
    147.                 Debug.Logz("ERROR: ColliderOther does not contain ColliderWatcher: " + colliderOtherIn.gameObject.name + " " + colliderOtherIn.transform.root.gameObject.name);
    148.             }
    149.             if (OnEnterEventSender != null) //at least 1 listener
    150.             {
    151.                 Debug.Logz("FIXME NULL PLACEHOLDER"); OnEnterEventSender.Invoke(colliderOtherIn, null);
    152.             }
    153.         }
    154.         else
    155.         {
    156.             Debug.Logz("ERROR: Already contains collider: Should never happen. " + gameObject.NameHierarchy() + " attempting to add " + colliderOtherIn.gameObject.NameHierarchy());
    157.         }
    158.         //DebugRefreshDebugColliderArray();
    159.     }
    160.  
    161.     private void RemoveColliderFromList(Collider colliderOtherIn)
    162.     {
    163.         //Debug.Logz(gameObject.name + ".RemoveColliderFromList " + colliderOtherIn.gameObject.name);
    164.         if (colliderOtherIn != null)
    165.         {
    166.             if (listColliderOther.Contains(colliderOtherIn)) //ErrorCheck, make sure list contains colliderOther before it's removed
    167.             {
    168.                 listColliderOther.Remove(colliderOtherIn); //remove from list
    169.                 if (OnExitEventSender != null) //at least 1 listener
    170.                 {
    171.                     Debug.Logz("FIXME NULL PLACEHOLDER"); OnExitEventSender.Invoke(colliderOtherIn, null);
    172.                 }
    173.             }
    174.             else
    175.             {
    176.                 Debug.Logz("ERROR: List does not contain collider.  It cannot be removed.  Should never happen." + gameObject.NameHierarchy() + "  otherGORoot=" + colliderOtherIn.gameObject.NameHierarchy());
    177.             }
    178.         }
    179.         else
    180.         {
    181.             Debug.Logz("ERROR: colliderOtherIn == null" + gameObject.name + " " + gameObject.transform.root.gameObject.name);
    182.         }
    183.         //DebugRefreshDebugColliderArray();
    184.     }
    185.     */
    186.  
    187.     public void OnTriggerCollisionPairXXX(TriggerCollisionData triggerCollisionDataIn) //the other collision info is sent to this ColliderWatcher, and this ColliderWatcher's collision info is sent to the other ColliderWatcher
    188.     {
    189.         //Debug.Logz("OnTriggerCollisionPairXXX:    Self:" + gameObject.NameHierarchy() + "    Other:" + triggerCollisionDataIn.colliderWatcherOther.gameObject.NameHierarchy() + "    " + Time.time);
    190.         if(triggerCollisionDataIn != null) //ErrorCheck
    191.         {
    192.             if(triggerCollisionDataIn.enumEnterExit == EnumEnterExit.Enter)
    193.             {
    194.                 //listColliderOther.Add(triggerCollisionDataIn.coll); //add to list
    195.                 //listColliderWatcherOther.Add(triggerCollisionDataIn.colliderWatcherOther);
    196.                 AddColliderWatcherToList(triggerCollisionDataIn.colliderWatcherOther);
    197.                 //new TriggerCollisionData()
    198.                
    199.  
    200.             }
    201.             else if (triggerCollisionDataIn.enumEnterExit == EnumEnterExit.Exit)
    202.             {
    203.                 //listColliderWatcherOther.Remove(triggerCollisionDataIn.colliderWatcherOther);
    204.                 RemoveColliderWatcherFromList(triggerCollisionDataIn.colliderWatcherOther);
    205.             }
    206.             else
    207.             {
    208.                 BatDebug.Error(this, "5467yur4t35r42", "Unhandled enum case");
    209.             }
    210.             if (OnTriggerCollisionEventSender != null) //at least 1 listener
    211.             {
    212.                 OnTriggerCollisionEventSender.Invoke(triggerCollisionDataIn);
    213.             }
    214.         }
    215.         else
    216.         {
    217.             BatDebug.Error(this, "vf34FDSadf", "triggerCollisionDataIn == null");
    218.         }
    219.         //DebugRefreshCurrentCollisions();
    220.     }
    221.  
    222.  
    223.  
    224.     private void OnEnable()
    225.     {
    226.         //Debug.Logz("OnEnable(): " + gameObject.NameHierarchy());
    227.         if (colliderSelf != null)
    228.         {
    229.             colliderSelf.enabled = true;
    230.         }
    231.         else
    232.         {
    233.             BatDebug.Error(this, "456tjhgrer243r54t", "colliderSelf == null");
    234.         }
    235.     }
    236.  
    237.     private void OnDestroy() //If something is Destroy(gameObject) during OnCollision in the Physics loop it will be disabled immediately, but disabled at the END of all triggers/collisions.  Attempting to execute stack during OnDestroy is a good spot.
    238.     {
    239.         //Debug.Logz(this.GetType().Name + ".OnDestroy(): " + gameObject.NameHierarchy() + " " + Time.time + " currentCollisions=" + listColliderWatcherOther.Count);
    240.         ColliderWatcherManager.ColliderWatcherDisabled.ExecuteStack("OnDestroy");
    241.     }
    242.  
    243.     private void OnDisable() //this is called before something is destroyed.  it should also be called if a dead monster is made hollow (the collider.enabled should NOT be set directly)  this is used because other scripts will still exist (they won't be null'd yet)
    244.     {
    245.         //Debug.Logz(this.GetType().Name + ".OnDisable(): " + gameObject.NameHierarchy() + " " + Time.time + " currentCollisions=" + listColliderWatcherOther.Count + " " + ScriptExecutionOrder.enumLoop);
    246.  
    247.         if (colliderSelf != null)
    248.         {
    249.             colliderSelf.enabled = false;
    250.         }
    251.         else
    252.         {
    253.             BatDebug.Error(this, "3454thtbgrr233rt", "colliderSelf == null");
    254.         }
    255.  
    256.  
    257.         new ColliderWatcherManager.ColliderWatcherDisabled(this); //adds to stack & executes stack. Even if current collisions = 0 it still adds to stack because it's possible that some collisions may still occur during this loop and add to list
    258.         //ColliderWatcherManager
    259.  
    260.         //Debug.Logz("OnDisable2: " + gameObject.NameHierarchy() + " " + Time.time + " currentCollisions=" + listColliderWatcherOther.Count);
    261.  
    262.         //for (int i = 0; i <= 10000; i++) //~while loop, prevent infinite looping
    263.         //{
    264.         //    if (i == 10000)
    265.         //    {
    266.         //        Debug.Logz("ERROR: Infinite Loop");
    267.         //    }
    268.  
    269.         //    if (listColliderWatcherOther.Count > 0)
    270.         //    {
    271.         //        //listColliderWatcherOther[0].ExecuteAndRemoveFromStack();
    272.         //    }
    273.         //    else //has cleared all collisions
    274.         //    {
    275.         //        break;
    276.         //    }
    277.         //}
    278.  
    279.         /*
    280.         for (int i = listColliderOther.Count - 1; i >= 0; i--) //reverse iterate because collection will be modified
    281.         {
    282.             if (listColliderOther[i] != null)
    283.             {
    284.                 ColliderWatcher colliderWatcherOther = listColliderOther[i].GetComponent<ColliderWatcher>();
    285.                 if (colliderWatcherOther != null)
    286.                 {
    287.                     colliderWatcherOther.RemoveColliderFromList(colliderSelf); //remove this collliderSelf from the OTHER watcher
    288.                     RemoveColliderFromList(listColliderOther[i]); //remove colliderOther from this' list
    289.                 }
    290.                 else
    291.                 {
    292.                     Debug.Logz("ERROR: ColliderOther does not contain ColliderWatcher: " + listColliderOther[i].gameObject.name + " " + listColliderOther[i].transform.root.gameObject.name);
    293.                 }
    294.             }
    295.             else
    296.             {
    297.                 Debug.Logz("ERROR: listColliderOther[" + i + "] == null.   This name = " + gameObject.transform.root.gameObject.name);
    298.             }
    299.  
    300.  
    301.         }
    302.         */
    303.     }
    304.  
    305.     public void OnDisabledFromStack() //called from ColliderWatcherManager when this is removed from stack (occurs after OnDisable)
    306.     {
    307.         //Debug.Logz("OnDisabledFromStack(): " + gameObject.NameHierarchy() + " " + Time.time + " currentCollisions=" + listColliderWatcherOther.Count);
    308.         TriggerCollisionData triggerCollisionDataA = new TriggerCollisionData(EnumTriggerCollision.Disable, EnumEnterExit.Exit, this, null);
    309.         for (int i = listColliderWatcherOther.Count - 1; i >= 0; i--) //reverse iterate because collection will be modified
    310.         {
    311.             if (listColliderWatcherOther[i] != null)
    312.             {
    313.                
    314.                 TriggerCollisionData triggerCollisionDataB = new TriggerCollisionData(EnumTriggerCollision.Disable, EnumEnterExit.Exit, listColliderWatcherOther[i], null);
    315.                 TriggerCollisionData.ApplyPair(triggerCollisionDataA, triggerCollisionDataB);
    316.  
    317.                 //ColliderWatcher colliderWatcherOther = listColliderOther[i].GetComponent<ColliderWatcher>();
    318.                 //if (colliderWatcherOther != null)
    319.                 //{
    320.  
    321.                 //colliderWatcherOther.RemoveColliderFromList(colliderSelf); //remove this collliderSelf from the OTHER watcher
    322.                 //RemoveColliderFromList(listColliderOther[i]); //remove colliderOther from this' list
    323.                 //}
    324.                 //else
    325.                 //{
    326.                 //    Debug.Logz("ERROR: ColliderOther does not contain ColliderWatcher: " + listColliderOther[i].gameObject.name + " " + listColliderOther[i].transform.root.gameObject.name);
    327.                 //}
    328.             }
    329.             else
    330.             {
    331.                 BatDebug.Error(this, "5u6trhyt4r35", "listColliderOther[" + i + "] == null");
    332.             }
    333.  
    334.  
    335.         }
    336.     }
    337.  
    338.     /*
    339.     public void AddListenerEnter(DelegateCollider delegateColliderIn) //subscribe to eventSender
    340.     {
    341.         if (delegateColliderIn != null)
    342.         {
    343.             OnEnterEventSender += delegateColliderIn;
    344.         }
    345.         else
    346.         {
    347.             Debug.Logz("ERROR: Null");
    348.         }
    349.     }
    350.  
    351.     public void AddListenerExit(DelegateCollider delegateColliderIn) //subscribe to eventSender
    352.     {
    353.         if (delegateColliderIn != null)
    354.         {
    355.             OnExitEventSender += delegateColliderIn;
    356.         }
    357.         else
    358.         {
    359.             Debug.Logz("ERROR: Null");
    360.         }
    361.     }
    362.     */
    363.  
    364.     public void AddListener(DelegateTriggerCollision delegateTriggerCollisionIn)
    365.     {
    366.         if (delegateTriggerCollisionIn != null)
    367.         {
    368.             OnTriggerCollisionEventSender += delegateTriggerCollisionIn;
    369.         }
    370.         else
    371.         {
    372.             BatDebug.Error(this, "54675yutjhrtr423", "delegateTriggerCollisionIn == null");
    373.         }
    374.     }
    375.  
    376.     public void RemoveListener(DelegateTriggerCollision delegateTriggerCollisionIn)
    377.     {
    378.         if (delegateTriggerCollisionIn != null)
    379.         {
    380.             OnTriggerCollisionEventSender -= delegateTriggerCollisionIn;
    381.         }
    382.         else
    383.         {
    384.             BatDebug.Error(this, "545ythgbfrew2rt45y", "delegateTriggerCollisionIn == null");
    385.         }
    386.     }
    387.  
    388.     //private void DebugRefreshDebugColliderArray()
    389.     //{
    390.     //    debugColliderArray = listColliderOther.ToArray();
    391.     //}
    392.  
    393.         /*
    394.     private void DebugRefreshCurrentCollisions()
    395.     {
    396.         List<string> listStringTemp = new List<string>();
    397.         for(int i = 0; i < listColliderWatcherOther.Count; i++)
    398.         {
    399.             string otherNameTemp = "null";
    400.             if(listColliderWatcherOther[i] != null)
    401.             {
    402.                 otherNameTemp = listColliderWatcherOther[i].gameObject.NameHierarchy();
    403.             }
    404.             listStringTemp.Add("[" + i + "]" + otherNameTemp);
    405.         }
    406.         debugCurrentCollisions = listStringTemp.ToArray();
    407.     }
    408.     */
    409.  
    410.     public bool isTrigger
    411.     {
    412.         get
    413.         {
    414.             return colliderSelf.isTrigger;
    415.         }
    416.         set
    417.         {
    418.             if(isTrigger != value) //value has change
    419.             {
    420.                 //bool isEnabledPre = enabled;
    421.                 if(listColliderWatcherOther.Count > 0) //currently enabled
    422.                 {
    423.                     BatDebug.Error(this, "465yhgrfe23rt4y", "annot change isTrigger while currently colliding??  Since Trigger always occurs before Collided it will either be added twice in a row or removed twice in a row causing errors");
    424.                 }
    425.             }
    426.             else
    427.             {
    428.                 BatDebug.Error(this, "45ythgrr3454y5", "Set to same value of " + value);
    429.             }
    430.         }
    431.  
    432.     }
    433. }
    434.  
    435.  
    436. using UnityEngine;
    437. using System.Collections;
    438. using System.Collections.Generic; //allow List
    439.  
    440. public class ColliderWatcherHelper : MonoBehaviour
    441. {
    442.     //private static EnumEnterExit enumEnterExit = EnumEnterExit.Enter;
    443.     //private static Collider colliderA = null; //for OnTriggerXXX
    444.     //private static Collider colliderB = null; //for OnTriggerXXX
    445.     //private static Collision collisionA = null; //for OnCollisionXXX (will be null for trigger)
    446.     //private static Collision collisionB = null; //for OnCollisionXXX (will be null for trigger)
    447.  
    448.     //private static float timeFixedTimeA = 1.111f;
    449.     //private static float timeFixedTimeB = 2.222f;
    450.  
    451.     //private static List<ColliderPair> stackColliderPair = new List<ColliderPair>();
    452.     //private static bool currentlyExecutingStack = false;
    453.  
    454.     private void Awake()
    455.     {
    456.         ColliderWatcher[] colliderWatcherArray = GetComponents<ColliderWatcher>();
    457.         if(colliderWatcherArray.Length >= 2) //There can only be 1 collisionHelper
    458.         {
    459.             BatDebug.Error(this, "54657u6yihjrge", "Already contains ColliderWatcherHelper.  There should only be 1");
    460.         }
    461.     }
    462.  
    463.    
    464.  
    465.     public void OnTriggerEnter(Collider colliderOtherIn)
    466.     {
    467.         //Debug.Logz("OnTriggerEnter: " + gameObject.NameHierarchy() + "    other: " + colliderOtherIn.gameObject.NameHierarchy());
    468.         //Debug.Logz(gameObject.name + ".OnTriggerEnter");
    469.         //OnEnter(colliderOtherIn);
    470.  
    471.         //if(ErrorCheckAndFindColliderWatcher(colliderOtherIn))
    472.         //{
    473.         //    ColliderWatcherManager.OnTriggerXXX(EnumEnterExit.Enter, colliderOtherIn);
    474.         //}
    475.  
    476.         //OnTriggerXXX(EnumEnterExit.Exit, colliderOtherIn);
    477.         OnTriggerCollisionXXX(EnumTriggerCollision.Trigger, EnumEnterExit.Enter, colliderOtherIn, null);
    478.     }
    479.  
    480.     public void OnTriggerExit(Collider colliderOtherIn)
    481.     {
    482.         //Debug.Logz("OnTriggerExit: " + gameObject.NameHierarchy() + "    other: " + colliderOtherIn.gameObject.NameHierarchy());
    483.         //Debug.Logz(gameObject.name + ".OnTriggerExit");
    484.         //OnExit(colliderIn);
    485.  
    486.         //OnTriggerXXX(EnumEnterExit.Exit, colliderOtherIn);
    487.         OnTriggerCollisionXXX(EnumTriggerCollision.Trigger, EnumEnterExit.Exit, colliderOtherIn, null);
    488.     }
    489.  
    490.     //private void OnTriggerXXX(EnumEnterExit enumEnterExitIn, Collider colliderOtherIn)
    491.     //{
    492.     //    ColliderWatcher colliderWatcherOtherTemp = ErrorCheckAndFindColliderWatcher(colliderOtherIn); //if this fails it spits out errors
    493.     //    if (colliderWatcherOtherTemp != null) //ErrorCheck
    494.     //    {
    495.     //        ColliderWatcherManager.OnTriggerXXX(EnumEnterExit.Exit, colliderWatcherOtherTemp, colliderOtherIn);
    496.     //    }
    497.     //}
    498.  
    499.     public void OnCollisionEnter(Collision collisionOtherIn)
    500.     {
    501.         //Debug.Logz("OnCollisionEnter: " + gameObject.NameHierarchy() + "    other: " + collisionOtherIn.collider.gameObject.NameHierarchy());
    502.         //Debug.Logz(gameObject.name + ".OnCollisionEnter");
    503.         //OnEnter(collisionOtherIn.collider);
    504.         //ContactPoint[] contactPointArray = collisionOtherIn.contacts;
    505.         //Debug.Logz(gameObject.NameHierarchy() + ".OnCollisionEnter: Contact Points = " + contactPointArray.Length);
    506.         //for(int i = 0; i < contactPointArray.Length; i++)
    507.         //{
    508.         //    Debug.Logz("[" + i + "]   Self: " + contactPointArray[i].thisCollider.gameObject.NameHierarchy() + "     Other: " + contactPointArray[i].otherCollider.gameObject.NameHierarchy());
    509.         //}
    510.  
    511.         //if (!ErrorCheckColliderHasColliderWatcher(collisionOtherIn.collider))
    512.         //{
    513.         //    ColliderWatcherManager.OnCollisionXXX(EnumEnterExit.Enter, colliderIn);
    514.         //}
    515.         OnTriggerCollisionXXX(EnumTriggerCollision.Collision, EnumEnterExit.Enter, collisionOtherIn.collider, collisionOtherIn);
    516.     }
    517.  
    518.     public void OnCollisionExit(Collision collisionOtherIn)
    519.     {
    520.         //Debug.Logz("OnCollisionExit: " + gameObject.NameHierarchy() + "    other: " + collisionOtherIn.collider.gameObject.NameHierarchy());
    521.         //Debug.Logz(gameObject.name + ".OnCollisionExit");
    522.         //OnExit(collisionOtherIn.collider);
    523.         OnTriggerCollisionXXX(EnumTriggerCollision.Collision, EnumEnterExit.Exit, collisionOtherIn.collider, collisionOtherIn);
    524.     }
    525.  
    526.     //private void OnCollisionXXX(EnumEnterExit enumEnterExitIn, Collision collisionOtherIn)
    527.     //{
    528.     //    ColliderWatcher colliderWatcherOtherTemp = ErrorCheckAndFindColliderWatcher(collisionOtherIn.collider); //if this fails it spits out errors
    529.     //    if (colliderWatcherOtherTemp != null) //ErrorCheck
    530.     //    {
    531.     //        ColliderWatcherManager.OnCollisionXXX(EnumEnterExit.Exit, colliderWatcherOtherTemp, collisionOtherIn);
    532.     //    }
    533.     //}
    534.  
    535.     //private void OnEnter(Collider colliderOtherIn) //collision AND trigger
    536.     //{
    537.     //    //Debug.Logz("OnEnter: " + gameObject.NameHierarchy() + "    other: " + colliderOtherIn.gameObject.NameHierarchy());
    538.     //    enumEnterExit = EnumEnterExit.Enter;
    539.     //    OnEnterExit(colliderOtherIn);
    540.  
    541.        
    542.     //}
    543.  
    544.     //private void OnExit(Collider colliderOtherIn) //collision AND trigger
    545.     //{
    546.     //    //Debug.Logz("OnEnter: " + gameObject.NameHierarchy() + "    other: " + colliderOtherIn.gameObject.NameHierarchy());
    547.     //    enumEnterExit = EnumEnterExit.Exit;
    548.     //    OnEnterExit(colliderOtherIn);
    549.     //}
    550.  
    551.     private void OnTriggerCollisionXXX(EnumTriggerCollision enumTriggerCollisionIn, EnumEnterExit enumEnterExitIn, Collider colliderOtherIn, Collision collisionCBNIn) //Collider is used for Trigger, and Collision is used for Collision
    552.     {
    553.         //Debug.Logz("ColliderWatcherHelper.OnTriggerCollisionXXX    Self: " + gameObject.NameHierarchy() + "    Other: " + colliderOtherIn.gameObject.NameHierarchy() + " " + enumTriggerCollisionIn + " " + enumEnterExit);
    554.         //ScriptExecutionOrder.DebugDisplay();
    555.         ColliderWatcher colliderWatcherOtherTemp = ErrorCheckAndFindColliderWatcher(colliderOtherIn); //if this fails it spits out errors
    556.         if (colliderWatcherOtherTemp != null) //ErrorCheck
    557.         {
    558.             //ColliderWatcherManager.OnTriggerXXX(EnumEnterExit.Exit, colliderWatcherOtherTemp, colliderOtherIn);
    559.             //ColliderWatcherManager.OnTriggerCollisionXXX(enumTriggerCollisionIn, enumEnterExitIn, colliderWatcherOtherTemp, collisionCBNIn);
    560.  
    561.             TriggerCollisionData triggerCollisionData = new TriggerCollisionData(enumTriggerCollisionIn, enumEnterExitIn, colliderWatcherOtherTemp, collisionCBNIn); //one half of the collision (takes 2 to make a pair)
    562.             ColliderWatcherManager.OnTriggerCollisionXXX(triggerCollisionData); //send one half of the pair (may be the 1st or 2nd half)
    563.         }
    564.     }
    565.  
    566.     /*
    567.     private void OnEnterExit(Collider colliderOtherIn) //used for both enter/exit for both collision/trigger
    568.     {
    569.         if (colliderOtherIn != null) //ErrorCheck
    570.         {
    571.             if (colliderOtherIn.GetComponent<ColliderWatcher>()) //ErrorCheck just to make sure the other collider contains ColliderWatcher (common error if you forget to add ColliderWatcher to every collider in the game)
    572.             {
    573.                 if (colliderA == null)
    574.                 {
    575.                     colliderA = colliderOtherIn;
    576.                     //timeFixedTimeA = Time.fixedTime;
    577.                 }
    578.                 else
    579.                 {
    580.                     colliderB = colliderOtherIn;
    581.                     //timeFixedTimeB = Time.fixedTime;
    582.                     EnterExitPair();
    583.                 }
    584.             }
    585.             else
    586.             {
    587.                 Debug.Logz("ERROR: " + colliderOtherIn.gameObject.NameHierarchy() + " does not contain ColliderWatcher");
    588.             }
    589.         }
    590.         else
    591.         {
    592.             Debug.Logz("ERROR: colliderOtherIn == null");
    593.         }
    594.     }
    595.     */
    596.  
    597.     /*
    598.     private static void EnterExitPair() //called OnTriggerEnter/OnCollisionEnter
    599.     {
    600.         //Debug.Logz("Pair " + enumEnterExit + "  A:" + colliderA.gameObject.NameHierarchy() + "   B:" + colliderB.gameObject.NameHierarchy());
    601.  
    602.         if (colliderA != null && colliderB != null) //ErrorCheck
    603.         {
    604.             ColliderWatcher colliderWatcherA = colliderA.GetComponent<ColliderWatcher>();
    605.             ColliderWatcher colliderWatcherB = colliderB.GetComponent<ColliderWatcher>();
    606.             if (colliderWatcherA != null && colliderWatcherB != null) //ErrorCheck
    607.             {
    608.                 bool enabledBeforeA = colliderWatcherA.enabled;
    609.                 bool enabledBeforeB = colliderWatcherB.enabled;
    610.                 colliderWatcherA.AddRemoveColliderOther(colliderB, enumEnterExit);
    611.                 colliderWatcherB.AddRemoveColliderOther(colliderA, enumEnterExit);
    612.                 if(colliderWatcherA.enabled != enabledBeforeA) //ErrorCheck, cannot change collider.enabled status during OnCollisionEnter/Exit
    613.                 {
    614.                     Debug.Logz("ERROR: During OnCollisionEnter/Exit the ColliderWatcher must not be disabled/eanbled during the execution of the pair");
    615.                 }
    616.                 if (colliderWatcherB.enabled != enabledBeforeB) //ErrorCheck, cannot change collider.enabled status during OnCollisionEnter/Exit
    617.                 {
    618.                     Debug.Logz("ERROR: During OnCollisionEnter/Exit the ColliderWatcher must not be disabled/eanbled during the execution of the pair");
    619.                 }
    620.  
    621.                 //stackColliderPair.Add(new ColliderPair(colliderA, colliderWatcherA, colliderB, colliderWatcherB, enumEnterExit));
    622.                 //if (currentlyExecutingStack == false)
    623.                 //{
    624.                 //    currentlyExecutingStack = true;
    625.                 //    while(stackColliderPair.Count > 0)
    626.                 //    {
    627.                 //        stackColliderPair[0].colliderWatcherA.AddRemoveColliderOther(stackColliderPair[0].colliderB, stackColliderPair[0].enumEnterExit);
    628.                 //        stackColliderPair[0].colliderWatcherB.AddRemoveColliderOther(stackColliderPair[0].colliderA, stackColliderPair[0].enumEnterExit);
    629.                 //        stackColliderPair.RemoveAt(0);
    630.                 //    }
    631.                 //    currentlyExecutingStack = false;
    632.                 //}
    633.             }
    634.             else
    635.             {
    636.                 Debug.Logz("ERROR: colliderWatcherA A or B is null");
    637.             }
    638.         }
    639.         else
    640.         {
    641.             Debug.Logz("ERROR: collider A or B is null");
    642.         }
    643.  
    644.         //regardless if there are errors or not, clear the static collider pair
    645.         colliderA = null;
    646.         colliderB = null;
    647.     }
    648.     */
    649.  
    650.     //public enum EnumEnterExit { Enter, Exit}
    651.  
    652.     //public static void ErrorCheckUpdate() //called by GameController
    653.     //{
    654.     //    if(colliderA != null || colliderB != null)
    655.     //    {
    656.     //        colliderA = null;
    657.     //        colliderB = null;
    658.     //        Debug.Logz("ERROR: collisions should always occur in pairs.  during update there should never be a colliderA or B that is not null");
    659.     //    }
    660.     //}
    661.  
    662.     /*
    663.     private struct ColliderPair
    664.     {
    665.         public Collider colliderA { get; private set; }
    666.         public ColliderWatcher colliderWatcherA { get; private set; }
    667.         public Collider colliderB { get; private set; }
    668.         public ColliderWatcher colliderWatcherB { get; private set; }
    669.         public EnumEnterExit enumEnterExit { get; private set; }
    670.  
    671.         public ColliderPair(Collider colliderAIn, ColliderWatcher colliderWatcherAIn, Collider colliderBIn, ColliderWatcher colliderWatcherBIn, EnumEnterExit enumEnterExitIn) //constructor
    672.         {
    673.             colliderA = colliderAIn;
    674.             colliderWatcherA = colliderWatcherAIn;
    675.             colliderB = colliderBIn;
    676.             colliderWatcherB = colliderWatcherBIn;
    677.             if(colliderA == null || colliderWatcherA == null || colliderB == null || colliderWatcherB == null) //ErrorCheck
    678.             {
    679.                 Debug.Logz("ERROR: Something null");
    680.             }
    681.             enumEnterExit = enumEnterExitIn;
    682.         }
    683.     }
    684.     */
    685.  
    686.     private ColliderWatcher ErrorCheckAndFindColliderWatcher(Collider colliderIn) //return true if error, false if no error
    687.     {
    688.         if(colliderIn != null)
    689.         {
    690.             ColliderWatcher returnMe = colliderIn.GetComponent<ColliderWatcher>(); //attempt to get
    691.             if (returnMe != null) //contains a colliderWatcher
    692.             {
    693.                 return returnMe;
    694.             }
    695.             else
    696.             {
    697.                 BatDebug.Error(this, "565uyjhtrgtr3", "returnMe == null: no ColliderWatcher");
    698.                 return null;
    699.             }
    700.         }
    701.         else
    702.         {
    703.             BatDebug.Error(this, "35zcxv353543", "colliderIn == null");
    704.             return null;
    705.         }
    706.     }
    707.  
    708.    
    709. }
    710.  
    711.  
    712.  
    713. using UnityEngine;
    714. using System.Collections;
    715. using System;
    716. using System.Collections.Generic; //allow List
    717.  
    718. public class ColliderWatcherManager : MonoBehaviour
    719. {
    720.     private static ColliderWatcherManager singleton;
    721.  
    722.     private static TriggerCollisionData triggerCollisionDataA = null;
    723.     private static TriggerCollisionData triggerCollisionDataB = null;
    724.  
    725.     private void Awake()
    726.     {
    727.         InitializeSingleton();
    728.     }
    729.  
    730.     private void InitializeSingleton()
    731.     {
    732.         if(singleton != null)
    733.         {
    734.             BatDebug.Error(this, "756avs34aweasf", "singleton set twice");
    735.         }
    736.         singleton = this;
    737.     }
    738.  
    739.     //private void Update() //due to scriptExecutionOrder this will be the FIRST thing that will be run.  Essentially the 1st thing ran after Physics performs a stack of collisions.
    740.     //{
    741.     //    ColliderWatcherDisabled.ExecuteStack("ColliderWatcherManager.Update");
    742.     //}
    743.  
    744.     public static void OnTriggerCollisionXXX(TriggerCollisionData triggerCollisionDataIn) //for trigger AND collision
    745.     {
    746.         //Debug.Logz("OnTriggerCollisionXXX: " + triggerCollisionDataIn.enumTriggerCollision + " " + triggerCollisionDataIn.enumEnterExit + " " + triggerCollisionDataIn.colliderWatcherOther.gameObject.NameHierarchy() + " " + triggerCollisionDataIn.collisionCBN);
    747.         if (triggerCollisionDataA == null)
    748.         {
    749.             triggerCollisionDataA = triggerCollisionDataIn;
    750.         }
    751.         else //A is already assign, so assign B
    752.         {
    753.             triggerCollisionDataB = triggerCollisionDataIn;
    754.             //PLACEHOLDER, errorChecks
    755.  
    756.             TriggerCollisionData.ApplyPair(triggerCollisionDataA, triggerCollisionDataB);
    757.  
    758.             triggerCollisionDataA = null; //clear
    759.             triggerCollisionDataB = null; //clear
    760.         }
    761.     }
    762.  
    763.     /*
    764.     public abstract class Stackable// : IComparable<Stackable>
    765.     {
    766.         private static List<Stackable> listStack = new List<Stackable>();
    767.         private static bool isExecutingStack = false;
    768.  
    769.         public Stackable() //constructor
    770.         {
    771.             listStack.Add(this);
    772.             //ExecuteStack();
    773.             Debug.Logz("StackCount: Add = " + listStack.Count);
    774.         }
    775.  
    776.         private void ExecuteAndRemoveFromStack()
    777.         {
    778.             Debug.Logz("ExecuteAndRemoveFromStack: " + this.GetType().Name);
    779.             listStack.Remove(this);
    780.             Debug.Logz("StackCount: Remove = " + listStack.Count);
    781.             ExecuteAndRemoveFromStack1();
    782.  
    783.         }
    784.  
    785.         protected abstract void ExecuteAndRemoveFromStack1();
    786.  
    787.         public static void ExecuteStack()
    788.         {
    789.             if (isExecutingStack == false && listStack.Count > 0)
    790.             {
    791.                 Debug.Logz("ExecuteStack: count = " + listStack.Count);
    792.                 listStack.Sort();
    793.                 isExecutingStack = true;
    794.                 for (int i = 0; i <= 10000; i++) //~while loop, prevent infinite looping
    795.                 {
    796.                     if (i == 10000)
    797.                     {
    798.                         isExecutingStack = false;
    799.                         Debug.Logz("ERROR: Infinite Loop");
    800.                     }
    801.  
    802.                     if (listStack.Count > 0)
    803.                     {
    804.                         listStack[0].ExecuteAndRemoveFromStack();
    805.                     }
    806.                     else
    807.                     {
    808.                         isExecutingStack = false;
    809.                         break;
    810.                     }
    811.                 }
    812.             }
    813.             //else
    814.             //{
    815.             //    Debug.Logz("Currently executing stack, not executing again");
    816.             //}
    817.            
    818.         }
    819.  
    820.         //public int CompareTo(Stackable other)
    821.         //{
    822.         //    return other.sortValue - sortValue;
    823.         //}
    824.  
    825.         //protected abstract int sortValue
    826.         //{
    827.         //    get;
    828.         //}
    829.     }
    830.     */
    831.  
    832.     /*
    833.     public sealed class TriggerCollisionPair : Stackable
    834.     {
    835.         private TriggerCollisionData triggerCollisionDataA;
    836.         private TriggerCollisionData triggerCollisionDataB;
    837.  
    838.         public TriggerCollisionPair(TriggerCollisionData triggerCollisionDataAIn, TriggerCollisionData triggerCollisionDataBIn) //constructor
    839.         {
    840.             if(triggerCollisionDataAIn == null || triggerCollisionDataBIn == null)
    841.             {
    842.                 Debug.Logz("ERRORz");
    843.             }
    844.             triggerCollisionDataA = triggerCollisionDataAIn;
    845.             triggerCollisionDataB = triggerCollisionDataBIn;
    846.             //ExecuteStack(); //execute AFTER everything is assigned (which is why ExecuteStack cannot be part of parent constructor)
    847.         }
    848.  
    849.        
    850.  
    851.         protected sealed override void ExecuteAndRemoveFromStack1()
    852.         {
    853.             if(triggerCollisionDataA == null)
    854.             {
    855.                 Debug.Logz("ERRORz");
    856.             }
    857.             if(triggerCollisionDataA.colliderWatcher == null)
    858.             {
    859.                 Debug.Logz("ERRORz");
    860.             }
    861.             triggerCollisionDataA.colliderWatcher.OnTriggerCollisionPairXXX(triggerCollisionDataB);
    862.             triggerCollisionDataB.colliderWatcher.OnTriggerCollisionPairXXX(triggerCollisionDataA);
    863.             //triggerCollisionDataA.colliderWatcher.AddRemoveColliderOther()
    864.         }
    865.  
    866.         protected sealed override int sortValue //trigger/collisions happen first
    867.         {
    868.             get
    869.             {
    870.                 return 1;
    871.             }
    872.         }
    873.     }
    874.     */
    875.  
    876.     public sealed class ColliderWatcherDisabled// : Stackable
    877.     {
    878.         private static List<ColliderWatcherDisabled> listStack = new List<ColliderWatcherDisabled>(); //list of all disabled colliders (they are added to stack of they occur during the physics loop, and are execute immediately if they occur at a different time)
    879.  
    880.         private ColliderWatcher colliderWatcher;
    881.         private static bool isExecutingStack = false;
    882.  
    883.         public ColliderWatcherDisabled(ColliderWatcher colliderWatcherIn) //constructor
    884.         {
    885.             //Debug.Logz("New ColliderWatcherDisabled: Loop = " + ScriptExecutionOrder.enumLoop);
    886.             colliderWatcher = colliderWatcherIn;
    887.             listStack.Add(this);
    888.  
    889.             if (ScriptExecutionOrder.enumLoop != ScriptExecutionOrder.EnumLoop.Physics) //anything other than the physics trigger/collision loop
    890.             {
    891.                 //Debug.Logz("Execute Stack Immediate");
    892.                 ExecuteStack("ColliderWatcherDisabled Immediate"); //execute stack. if other colliderWatchers are disabled while executing stack then they will also be added to the stack
    893.             }
    894.             else //was disabled during the trigger/collision loop
    895.             {
    896.                 //Debug.Logz("Execute Stack Delayed");
    897.                 //Debug.Logz("Postponing execution of disable stack because it occured within the Physics loop");
    898.             }
    899.         }
    900.  
    901.         private void ExecuteAndRemoveFromStack()
    902.         {
    903.             listStack.Remove(this);
    904.             colliderWatcher.OnDisabledFromStack();
    905.         }
    906.  
    907.         public static void ExecuteStack(string callerNameIn)
    908.         {
    909.             //Debug.Logz("ExecuteStack: currentlyExecuting = " + isExecutingStack + "  stackCount=" + listStack.Count);
    910.             if (isExecutingStack == false && listStack.Count > 0)
    911.             {
    912.                 //Debug.Logz("ExecuteStack: count = " + listStack.Count + " " + Time.time + "  callerNameIn=" + callerNameIn);
    913.                 //listStack.Sort();
    914.                 isExecutingStack = true;
    915.                 for (int i = 0; i <= 10000; i++) //~while loop, prevent infinite looping
    916.                 {
    917.                     if (i == 10000)
    918.                     {
    919.                         isExecutingStack = false;
    920.                         BatDebug.Error(typeof(ColliderWatcherManager), "455yjunghr32r4t5h", "Infinite Loop");
    921.                     }
    922.  
    923.                     if (listStack.Count > 0)
    924.                     {
    925.                         //Debug.Logz("ExecuteAndRemoveFromStack: " + listStack[0].colliderWatcher.gameObject.NameHierarchy());
    926.                         listStack[0].ExecuteAndRemoveFromStack();
    927.                     }
    928.                     else
    929.                     {
    930.                         isExecutingStack = false;
    931.                         break;
    932.                     }
    933.                 }
    934.             }
    935.         }
    936.     }
    937.  
    938.    
    939.  
    940. }
    941.  
    942.  
     
    Neos_, ejoflo, Ultroman and 1 other person like this.
  18. robmt

    robmt

    Joined:
    Dec 13, 2015
    Posts:
    2
    Does anyone know if Unreal has these problems? It's becoming daily more apparent to me that Unity is very poorly structured and lacks very basic things that would prevent performance hits. This is really a no brainer - a destroyed object should trigger all exits as it has exited. Instead we are expected to write code to maintain lists and arrays, perform checks at least every few frames etc for something that should already be in place. Another stupid niggle to add to the list right next to rigidbody movement juddering when moving along side navmesh agents. These problems have been in place for years.
     
  19. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    Also not being able to spotcheck an attack is a big problem for me.

    Like if I have a dragons breath an I want to do an instant snapshot to see what a cone hits... is there a way to do this? Like SphereCast or RayCast but with a custom shaped collider like a cone or arc.
     
  20. Marcos-Schultz

    Marcos-Schultz

    Joined:
    Feb 24, 2014
    Posts:
    381
  21. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    -But if another object it's touching is destroyed it won't register... it'll still think it's in contact with it.
    -Or if you change the objects trigger type it will call collisions in incorrect order (i.e you can get 2 enter's in a row or 2 exits in a row)

    I would really like a clean physics collision system with these properties
    1: It maintains an accessible list of current collisions.
    2: Callbacks for Enter/Exit regardless of the enter/exit reason. It will also provide additional info and tell you why the enter/exit call was made (Enter: NormalPhysics, Exit: NormalPhysics, Exit: SelfDestroy, Exit: OtherDestroy, Exit: LayerChange, Exit: SceneClosure?, etc.).
    3: Allow you to instantiate a mesh & spot check it for collisions instantly (i.e. a cone shaped dragons breath... what did it hit? You can't do this with the current system)



    Now you can bandaid and create #1 and #2 with some work. What I did was
    1: Add a ColliderWatcher to EVERY COLLIDER in the game
    2: The ColliderWatcher maintains a list of collisions. OnEnter/OnExit it adds/subtracts to the list.
    3: Any collision that occurs is added to a big least each FixedUpdate. Then at the end of the FixedUpdate I do a lot of postprocessing to adjust the ColliderWatchers Enters/Exits.
    3: In addition whenever an object is disabled/destroyed it clears its collision list on self, but also accesses the ColliderWatcher's on the other objects it's in contact with and removes it from the list there as well.
    4: Whenever anything is added/removed from the list it provides a callback for the Enter/Exit as well as the reason (i.e. Physics, SelfDestroy, OtherDestroy, etc.)

    This took a lot of work to do... but still doesn't address issue #3.
     
  22. zalogic

    zalogic

    Joined:
    Oct 6, 2010
    Posts:
    273
    Apparently in Unity 2018.3.12f1 this issue is still present. (no "OnTriggerExit" callback received when something enters a trigger and it gets destroyed or disabled).
    Managed to find a simple approach that worked for me.
    I made a small method extension sample for others to test it easier.
    Before I want to destroy/disable/pool a gameobject that I know it will have to trigger an "OnTriggerExit" callback I disable the "detectCollisions" flag on its "Rigidbody" component.
    You then need a small delay to allow the physics engine to trigger the physics callbacks and then you can destroy/disable/pool/hide the "GameObject".

    Hope this helps.

    Code (CSharp):
    1. public static class GameObjectExtensions
    2. {
    3.     public static void DisableRigidbodyAndDestroy(this GameObject gameObj, float delay = 0f, Rigidbody cachedRigidbody = null)
    4.     {
    5.         if (cachedRigidbody == null)
    6.             cachedRigidbody = gameObj.GetComponent<Rigidbody>();
    7.  
    8.         if (cachedRigidbody != null)
    9.         {
    10.             cachedRigidbody.detectCollisions = false;
    11.             cachedRigidbody.WakeUp();
    12.         }
    13.  
    14.         GameObject.Destroy(gameObj, 0.1f + delay);
    15.     }
    16. }
     
    Zullar likes this.
  23. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    The workaround is OK but only works if you destroy objects manually using DisableRigidbodyAndDestroy(...).

    When things are destroyed other ways (i.e. scene unloading or collider change to/from trigger) then it will not be invoked.

    I cannot find a robust solution to this whole mess.
     
    LuLusg, Ultroman and zalogic like this.
  24. IvyMoon

    IvyMoon

    Joined:
    Sep 26, 2015
    Posts:
    17
    Oh I was trying to figure this out today... try this! Using OnTriggerStay is way cleaner than OnTriggerExit:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class CollisionChecker : MonoBehaviour {
    6.     bool inTrigger = false;
    7.     bool enterTrigger = false;
    8.     float state;
    9.  
    10.     void Update () {
    11.         if (enterTrigger)
    12.         {
    13.             //Do what you want to happen every frame
    14.             if (state == 0)
    15.             {
    16.                 //Do what you want to happen once here
    17.                 state = 1;
    18.             }
    19.             if (!inTrigger)
    20.             {
    21.                 //no longer in the trigger (OnTriggerExit)
    22.                 state = 0;
    23.                 enterTrigger = false;
    24.             }
    25.             inTrigger = false;
    26.         }
    27.  
    28.  
    29.     }
    30.  
    31.     private void OnTriggerEnter(Collider other)
    32.     {
    33.         enterTrigger = true;
    34.     }
    35.  
    36.     private void OnTriggerStay(Collider other)
    37.     {
    38.         inTrigger = true;
    39.     }
    40. }
     
    CodeSmile and sibsH like this.
  25. tconkling

    tconkling

    Joined:
    Jul 13, 2012
    Posts:
    20
    I created a "DynamicTrigger" utility class for my project that handles this:

    https://gist.github.com/tconkling/82044a159e62313f041a62da9111f016

    An object that has a trigger collider, and that may be disabled or destroyed while it's colliding with objects that need to receive trigger-exit events, can add the DynamicTrigger component, which tracks all intersecting objects and calls `OnDynamicTriggerEnter` and `OnDynamicTriggerExit` events on those objects when appropriate, including when the object is destroyed, disabled, or re-enabled after being disabled.

    It's nothing fancy; it just does what I'd expect Unity to do out of the box.
     
    Zullar likes this.
  26. Azux

    Azux

    Joined:
    Mar 3, 2015
    Posts:
    19
    My solution is ....
    Code (CSharp):
    1. object.transform.position = new Vector3(-10000, - 10000, -10000);
    2. Destroy(Object);
     
    CodeSmile, ZMVL and llMarty like this.
  27. DarkGate

    DarkGate

    Joined:
    Jan 26, 2016
    Posts:
    33
    Having the same issue as everyone here. Also using the temporary workaround to move the collider before disabling.
     
  28. zalogic

    zalogic

    Joined:
    Oct 6, 2010
    Posts:
    273
    Forgot to return here and add an extra info. The above metioned method of "safe" destroy is not ok for more general purpose situations.
    For example if you have a compound rigidbody object (with multiple child rigidbodies), if any of the child rigidbodies was inside a trigger then it will not raise the "OnTriggerExit" event if you destroy it using the above method.

    The only more generic way I found was by going back to the drawing board and move the parent object at a "safe" out of screen position (for example at: [0f, -1000f, 0f]) and wait for one physics frame using "yield new WaitForFixedUpdate()" so that any physics callbacks get triggered and then destroy it or return it to a on objects pool. (depending on your needs).
     
  29. Tomer-Barkan

    Tomer-Barkan

    Joined:
    Jul 31, 2012
    Posts:
    150
    Wow, I was really surprised to find that this is how it works. It's a shame no one from unity is paying attention or commenting on this thread. This is really annoying.
     
    Ultroman likes this.
  30. Tomer-Barkan

    Tomer-Barkan

    Joined:
    Jul 31, 2012
    Posts:
    150
    I solved it like this. Fairly simple, but still hackey and I'd expect the engine to be able to handle this scenario that is pretty common.

    Code (CSharp):
    1.     public class NotifyMeWhenDisabledScript : MonoBehaviour {
    2.         public event System.Action onDestroy;
    3.         public event System.Action onDisabled;
    4.  
    5.         private void OnDisable() { onDisabled?.Invoke(); }
    6.         private void OnDestroy() { onDestroy?.Invoke(); }
    7.     }
    Then in the script that needs to know as long as the collision is active:
    Code (CSharp):
    1.     public class CollisionHandlerScript: MonoBehaviour {
    2.         protected Dictionary<Collider, NotifyMeWhenDisabledScript> notifiers = new Dictionary<Collider, NotifyMeWhenDisabledScript>();
    3.  
    4.         private void OnTriggerEnter(Collider other) {
    5.             // Do whatever I need to do when collision begins
    6.             // ...
    7.  
    8.             // Notify when disabled/destroyed
    9.             notifiers[other] = other.gameObject.AddComponent<NotifyMeWhenDisabledScript>();
    10.             notifiers[other].onDisabled += () => OnTriggerExit(other);
    11.         }
    12.  
    13.         private void OnTriggerExit(Collider other) {
    14.             // Do whatever I need to do when collision ends
    15.             // ...
    16.  
    17.             // Remove my notifier script from other
    18.             Destroy(notifiers[other]);
    19.         }
    20.     }
    21.  
     
  31. EmpireStudios

    EmpireStudios

    Joined:
    Oct 23, 2018
    Posts:
    24
    Try having to do this using Playmaker.
     
  32. zalogic

    zalogic

    Joined:
    Oct 6, 2010
    Posts:
    273
    Does not always works. Depends on the Fixed Timestep settings you have. You need to wait for a FixedUpdate before attempting to call the destroy.

    The only one size fits all solution I also found is this: move the object out of the scene, wait for one FixedUpdate using a WaitForFixedUpdate coroutine and then actually pool or destroy the object.
     
    Last edited: Jul 7, 2021
    CodeSmile and ksf000 like this.
  33. Jairusx

    Jairusx

    Joined:
    Jun 25, 2020
    Posts:
    62
    Hey. Is there any workaround for parenting objects? I have similar issue. After parent my object in my hand in example, the collisionExit does not update and my pressure plate stays active in my case :/
     
  34. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    I have similar issues with parenting. And the collider may or may not collide depending on if rigidbodies are nested within the hierarchy. The whole collision system is unrobust and inconsistent.

    The hacky bandaid I use is posted here.
    https://forum.unity.com/threads/collider-issues.450982/
    Maybe there's a better way.
     
    Jairusx likes this.
  35. AustinSyu

    AustinSyu

    Joined:
    Dec 5, 2018
    Posts:
    12
    I've done a few experiences on the 2020.2ver to find out the truth that destroy and disable doesn't trigger "exit".
    Can't believe they just let this BUG sleeping there for years, what a shame!

    Similarly, if my memory is correct, you can't addforce on the vertical direction if there's a root motion animator.
    These, deadly BUGs, I think they'll never fix it or even explain why don't.
     
    Last edited: May 24, 2021
  36. GHEBAH

    GHEBAH

    Joined:
    Feb 3, 2019
    Posts:
    20
    I am a complete noob in this business and also faced a problem, but I found the simplest solution, which, as for me, is good, because does not complicate anything and does not require load and computation. JUST TELEPORT THROUGH transform.position = new Vector3 (0, 0, 0); OBJECT FOR TRIGER OR COLIDER AND THEN IMMEDIATELY DELETE
     
  37. zalogic

    zalogic

    Joined:
    Oct 6, 2010
    Posts:
    273
    Careful. It might seem that it works. But it will NOT work the same on different machines or even on same machine if you have a frame-rate drop / change at some point.
    See previous post:
    https://forum.unity.com/threads/ont...nd-destroyed-gameobjects.381145/#post-6179583