Destroying Objects: OnDestroy() and Colliders Failing OnCollisionExit/OnTriggerExit

I am working on a top down action ARPG. Here’s the latest video.

I’m struggling to find a good way to destroy objects. I notice that OnDestroy() is called
1: When program is closed
2: When scene is exited (if it’s not a persistent object that is carried to next scene)
3: If it is manually destroyed using Destroy() or NetworkServer.Destroy()

So within the OnDestroy method I do not know how to handle it. For example if a monster is destroyed and I want it to play audio clip, or spawns little monsters, or adds to the kill counter… I only want this to occur when it’s destroyed manually… not on program/scene closure. Is there any way within the OnDestroy() method to determine what forced the OnDestroy()?

Also. Regarding colliders. If there is an aura, for example, then OnTriggerEnter() is called when an object enters the aura. However, if the object is destroyed while colliding then OnTriggerExit() is NOT called. I maintain a list of objects within the aura using Enter/Exit to Add/Remove… but the problem is that the list will creep in size as it and maintains null destroyed objects. What is the best way to handle this? What I currently do.
-When an object is killed I use a custom bool to flag it for destruction. I set the collider to a layer that does not interact with any other colliders.
-During every FixedUpdate I check for my destruction flag. If the flag is true then OnCollisionExit will be called because the collider layer was changed to break all collisions. Then I call Destroy().
-During the end of the next Update() the object will destroyed. OnDestroy() is called.

So it’s a total pain to destroy an object with lots of ways it can go wrong. Has anybody figured out a good way to destroy objects to avoid these issues? Thanks in advance.

So I wrote a script that uses Collision/Trigger Enter/Exit to maintain a list of current collided objects. It calls Enter/Exit events when a collider enters/exits. But it ALSO calls OnExit when the opposite ColliderWatcher is disabled/destroyed.

It seems to work OK. You have to be a little careful managing the hierarchy and also not to disable/eanble the collider directly, but instead disable the ColliderWatcher.

public class ColliderWatcher : MonoBehaviour
{
  private List<Collider> listColliderOther = new List<Collider>(); //list of current objects in contact
  private Collider colliderSelf;
  public delegate void DelegateCollider(Collider colliderOther); //signature of function
  private DelegateCollider OnEnterEventSender; //called by OnCollisionEnter OR OnTriggerEnter
  private DelegateCollider OnExitEventSender; //called by OnCollisionExit OR OnTriggerExit OR when either collider is disabled/destroyed
  private void Awake()
  {
  if(GameController.isClientRemote)
  {
  Debug.Log("ERROR: This script is only for server.  I think?");
  }
  colliderSelf = GetComponent<Collider>(); //attempt to find collider directly in this heirarchy
  if (colliderSelf != null) //ErrorCheck
  {
  if(!colliderSelf.isTrigger) //triggers always are called on their layer, even if rigidbody is in parent heirarchy.  however if !isTrigger then it CANNOT have a rigidbody in parent otherwise ColliderWatcher will not be called because it will be sent to the rigidbodies layer
  {
  if (colliderSelf.attachedRigidbody != null)
  {
  if (colliderSelf.attachedRigidbody.transform != transform) //ErrorCheck
  {
  Debug.Log("ERROR: The rigidbody, ColliderWatcher, and collider must all be attached to the same transform.  They cannot be nested"); //This errorCheck will not work if rigidbody is added after
  }
  }
  }
  
  }
  else
  {
  Debug.Log("ERROR: No collider");
  }
  }
  public void OnCollisionEnter(Collision collisionOtherIn)
  {
  Debug.Log(gameObject.name + ".OnCollisionEnter");
  AddColliderToList(collisionOtherIn.collider);
  }
  public void OnCollisionExit(Collision collisionOtherIn)
  {
  Debug.Log(gameObject.name + ".OnCollisionExit");
  RemoveColliderFromList(collisionOtherIn.collider);
  }
  public void OnTriggerEnter(Collider colliderIn)
  {
  Debug.Log(gameObject.name + ".OnTriggerEnter");
  AddColliderToList(colliderIn);
  }
  public void OnTriggerExit(Collider colliderIn)
  {
  Debug.Log(gameObject.name + ".OnTriggerExit");
  RemoveColliderFromList(colliderIn);
  }
  private void AddColliderToList(Collider colliderOtherIn)
  {
  if (!listColliderOther.Contains(colliderOtherIn)) //ErrorCheck
  {
  listColliderOther.Add(colliderOtherIn);
  if(!colliderOtherIn.GetComponent<ColliderWatcher>())
  {
  Debug.Log("ERROR: ColliderOther does not contain ColliderWatcher: " + colliderOtherIn.gameObject.name + " " + colliderOtherIn.transform.root.gameObject.name);
  }
  if (OnEnterEventSender != null)
  {
  OnEnterEventSender.Invoke(colliderOtherIn);
  }
  }
  else
  {
  Debug.Log("ERROR: Already contains collider: Should never happen");
  }
  }
  private void RemoveColliderFromList(Collider colliderOtherIn)
  {
  if (listColliderOther.Contains(colliderOtherIn)) //ErrorCheck
  {
  listColliderOther.Remove(colliderOtherIn);
  Debug.Log(gameObject.name + ".RemoveCollider: newCount=" + listColliderOther.Count);
  if (OnExitEventSender != null)
  {
  OnExitEventSender.Invoke(colliderOtherIn);
  }
  }
  else
  {
  Debug.Log("ERROR: List does not contain collider.  It cannot be removed.  Should never happen.");
  }
  }
  private void OnEnable()
  {
  //Debug.Log(gameObject.name + ".OnEnable()");
  colliderSelf.enabled = true;
  }
  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)
  {
  colliderSelf.enabled = false;
  for (int i = listColliderOther.Count - 1; i >= 0; i--) //reverse iterate because enum will be modified
  {
  ColliderWatcher colliderWatcher = FindColliderWatcher(listColliderOther[i]);
  colliderWatcher.RemoveColliderFromList(colliderSelf); //remove this collliderSelf from the OTHER watcher
  RemoveColliderFromList(listColliderOther[i]); //remove colliderOther from this' list
  }
  }
  public void AddListenerEnter(DelegateCollider delegateColliderIn)
  {
  if (delegateColliderIn != null)
  {
  OnEnterEventSender += delegateColliderIn;
  }
  else
  {
  Debug.Log("ERROR: Null");
  }
  }
  public void AddListenerExit(DelegateCollider delegateColliderIn)
  {
  if (delegateColliderIn != null)
  {
  OnExitEventSender += delegateColliderIn;
  }
  else
  {
  Debug.Log("ERROR: Null");
  }
  }

  private static ColliderWatcher FindColliderWatcher(Collider colliderIn)
  {
  if (colliderIn != null)
  {
  ColliderWatcher returnMe = colliderIn.GetComponent<ColliderWatcher>();
  if (returnMe == null)
  {
  Debug.Log("ERROR: No attached ColliderWatcher");
  }
  return returnMe;
  }
  else
  {
  Debug.Log("ERROR");
  return null;
  }
  }
}