Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Distinguish between OnDestroy Component and OnDestroy GameObject

Discussion in 'Scripting' started by Wolfderic, Feb 17, 2020.

  1. Wolfderic

    Wolfderic

    Joined:
    May 9, 2019
    Posts:
    7
    I'd like to differentiate between when a component has been destroyed because the component itself has been destroyed, (but the game object still alive) or because the game object where the component was attached has been destroyed.

    Is there a way to distinguish between this two situations?

    Because OnDestroy could be caused by two different triggers. Component destruction and Game Object destruction (which generates Component cascade destruction)
     
  2. TheGameNewBie

    TheGameNewBie

    Joined:
    Jul 27, 2017
    Posts:
    92
    You can check if the Component is null or not
    gameObject.GetComponent<MyComponent>() == null


    same with GameObject
    gameObject == null
     
  3. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    If I understand the OP correctly, they wonder if in a script there is a way to know during OnDestroy() whether only the component is being destroyed or the entire object (in which case Ondestroy is invoked on all attached components).

    I don't know if the the latter case the object is already null.

    That being said, I find it difficult to think of a use case where this would be an issue. If a component is destroyed, it should only destroy the data it is directly responsible for (i.e. "owns") and that is not managed by GC. Those would be few and very far in-between by themselves. I can't think of a case where a component's OnDestroy should behave differently when only the component is destroyed from when all other compoenents are destroyed.
     
  4. TheGameNewBie

    TheGameNewBie

    Joined:
    Jul 27, 2017
    Posts:
    92
    I apologize for the misunderstanding :p
    After some testing, I came up with this,
    Code (CSharp):
    1. void OnDestroy () {
    2.         if(this.gameObject.activeSelf) {
    3.             Debug.Log("Only Component Destroyed");
    4.         } else  {
    5.             Debug.Log("Whole Object Destroyed");
    6.         }
    7.     }
    (Hope this helps)
     
  5. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    Does that really work? If we regard the following sequence, what would the component 'testScript' that uses above OnDestroy report:

    Code (CSharp):
    1. public GameObject objectToDestroy;
    2.  
    3. public void testDestroy(){
    4.     testScript = objectToDestroy.GetComponent<testScript>();
    5.     objectToDestroy.setActive(false);
    6.     Destroy(testScript);
    7. }
    8.  
    I think that even though I'm destroying the script only, the script reports that the entire object is destroyed.
     
  6. jamespaterson

    jamespaterson

    Joined:
    Jun 19, 2018
    Posts:
    391
    Could you perhaps interrogate the call stack to determine the origin of the OnDestroy()?
     
  7. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,745
    It's not reliable. Call stack might not be the same in the production build.
     
  8. TheGameNewBie

    TheGameNewBie

    Joined:
    Jul 27, 2017
    Posts:
    92
    Here's what I did,
    Code (CSharp):
    1. public class TestScript1 : MonoBehaviour {
    2.              public GameObject obj;
    3.              public int n;
    4.              public bool dest;
    5.  
    6.              void Update () {
    7.                        if(dest) {
    8.                                dest = false;
    9.                                if(n == 1) {
    10.                                       Destroy(Obj.GetComponent<TestScript2>());
    11.                            } else if(n == 2) {
    12.                                  Destroy(obj);
    13.                           }
    14.                       }
    15.               }
    TestScript2 would be the script I wrote previously.

    It worked for me.
     
  9. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    I don't understand how it could work, and unfortunately I don't understand what you are doing in TesScript1.

    That being said: for your OnDestroy() method to work, the following must be true:
    • An object that is being destroyed is always inactive (activeSelf is false). So an active object is set to inactive when destroyed (possible, but I did not verify)
    • If you destroy a component on object that is inactive, that object will return true to activeSelf during the Invokation of destroy, and then return to being inactive (I highly doubt that this is true).
    Can you verify that an object that is inactive, while desroying on of ist component, briefly turns active?
     
  10. TheGameNewBie

    TheGameNewBie

    Joined:
    Jul 27, 2017
    Posts:
    92
    It's just a simple test I setup. When n=1, it destroys only the component. When n=2, Destroys whole GameObject.

    Yes, That's true.

    Yep. The script doesn't work if the object being Destroyed is already Inactive.

    So, Yeah. The script works If the object being destroyed is Active at the time of Destroying. But doesn't work if the object being destroyed is Inactive at the time of Destroying.

    Don't know where this fits into the OP's criteria.
     
  11. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    It doesn't. They want a script that reliably distinguishes whether the object was destroyed or just the component. Hence this thread's title.
     
  12. Wolfderic

    Wolfderic

    Joined:
    May 9, 2019
    Posts:
    7
    Just for clarification.
    I'm trying to do this logic from the script itself.
    Why?
    Because in your case if you do :
    Code (CSharp):
    1.  
    2.  
    3. private void OnDestroy()
    4. {
    5.     if(gameObject == null)
    6.     {
    7.         //Do Stuff here
    8.     }
    9. }
    10.  
    is the same than:

    Code (CSharp):
    1.  
    2. private void OnDestroy()
    3. {
    4.     if(this.gameObject == null)
    5.     {
    6.         //Do Stuff here
    7.     }
    8. }
    9.  

    I mean, the system does this under the hood. So "this" is null. You won't be able to reach the gameObject field reference because "you" are null.

    You can't use another script because if you are in a destruction process (Imagine the "Observer" script looks for a null gameObject reference in the Update function of the "Observable" component) you can't grantee that the "inspected" object is gonna be destroyed after the "inspector" one. (You can force it if you set the script execution order in the editor, but I want to avoid that)

    And you can't also use the gameObject.activeItself flag, because..What happens if the gameObject was deactivated when the component was destroyed?

    You can perfectly have a context when a gameObject is disabled and then you destroy a component. You will incorrectly asume then that the gameObject was also destroyed.
     
    Last edited: Feb 18, 2020
  13. Wolfderic

    Wolfderic

    Joined:
    May 9, 2019
    Posts:
    7

    My question was more focused in guessing if the Component was destroyed because of GameObject destruction rather than the Component destruction. But from the same script.

    The main problem here is that two totally different situations are firing the same Unity Callback. Which is really bad at the end. Because we are describing different actions with the same "event". And this two context are in fact totally different.

    There are some situations where you really may need to distinguish between:

    OnComponentDestroy
    OnGameObjectDestroy

    basically because there are totally different situations and OnDestroy just cares about the component one. I mean, I lke internally assume that every time a component is destroy is caused by the linked gameObject destruction. And that's a wrong assumption.
     
  14. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,745
    That's 95% accurate assumption made because Adding/Removing components in runtime in Unity is bad practice because of performance reasons. Still if you need this clarification, you can add empty component to that game object and fire event from that component OnDestroy. Since it is never destroyed by any means except destroying game object, you might call that event OnGameObjectDestroy.

    I suppose your problem is something else because it is weird when object cares of it's container being destroyed.
     
  15. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    You say that you need to distinguish between the two, but I haven't yet encountered a situation where this is true -- provided, of course, each component correctly manages their own OnDestroy(). A component should NOT behave differently when the owning object is destroyed as well. Objects themselves aren't scripts, just a transform component and some container methods to sort and group other objects. From a script's perspective there is only one event: OnDestroy.

    Can you give a concrete example where this creates a problem?
     
  16. Wolfderic

    Wolfderic

    Joined:
    May 9, 2019
    Posts:
    7
    Interesting approach. I thought about that some time ago as well but I think I lost the track when I got too focused on the issue itself. This is what I wrote in that moment:

    Code (CSharp):
    1.  
    2.  
    3. public class OnDestroyGameObject : MonoBehaviour
    4. {
    5.     private readonly Subject<GameObject> m_OnDestroySubject = new Subject<GameObject>();
    6.  
    7.     private void OnDestroy()
    8.     {
    9.         using (m_OnDestroySubject)
    10.         {
    11.             m_OnDestroySubject.OnNext(gameObject);
    12.             m_OnDestroySubject.OnCompleted();
    13.         }
    14.     }
    15.  
    16.     public IObservable<GameObject> OnDestroyAsObservable()
    17.     {
    18.         return m_OnDestroySubject;
    19.     }
    20. }
    21.  
    22.  
    Code (CSharp):
    1.  
    2.  
    3. public class OnDestroyGameObjectListener : MonoBehaviour
    4. {
    5.     [SerializeField] private OnDestroyGameObject m_OnDestroyGameObject;
    6.  
    7.     private void Start()
    8.     {
    9.         m_OnDestroyGameObject.OnDestroyAsObservable().Subscribe(OnDestroyListener);
    10.     }
    11.  
    12.     private void OnDestroyListener(GameObject gameObject)
    13.     {
    14.         //Do stuff with this destroyed Game Object because the GameObject is not null
    15.     }
    16.  
    17.     // [ContextMenu("Destroy Test")]
    18.     // public void DestroyTest()
    19.     // {
    20.     //     Destroy(m_OnDestroyGameObject.gameObject);
    21.     // }
    22.     //
    23.     // [ContextMenu("Destroy Test Immediate")]
    24.     // public void DestroyTestImmediate()
    25.     // {
    26.     //     Destroy(m_OnDestroyGameObject.gameObject);
    27.     // }
    28. }
    29.  
    30.  
    31.  
    And then in this case we should set the listener execution order to the very last one in the execution queue.
    But this is exactly what I was wondering whether there is a way or not to avoid that.

    The listener in this case is a Monobehaviour. The Ideal scenario, I think is to not use a Monobehaviour, and just use a POCO class so you may not need to worry about execution queues
     
    Last edited: Feb 18, 2020
  17. Wolfderic

    Wolfderic

    Joined:
    May 9, 2019
    Posts:
    7

    Sorry I forgot to answer your question.

    My initial idea was create a custom ServiceLocator object which manages all the references (monobehaviour or not).
    So when a game object is created I add all the attached components, all their interafaces and base types (up to UnityEngine.Object) to the locator service.

    When a component is destroyed I have to remove that instance from the Locator tables and if the gameObject where this component is attached to is also destroyed. I remove also the gameObject instance from the locator "table".

    The problem I had is that I needed to know when a gameObject was destroyed in order to remove that gameObject from the locator's instances references map. But I think with the previous approach I can manage that. (If we always assert that the OnDestroyAsObservable component is only destroyed during the gameObject destruction, something that accurately pointed out Palex-nx)

    And I'd like to add a non reactive (I was using my own reactive programming library) approach (In order to simplify the example)

    Code (CSharp):
    1. public class OnDestroyGameObject : MonoBehaviour
    2. {
    3.     public event Action<GameObject> OnDestroyObject = (_) => { };
    4.     private void OnDestroy()
    5.     {
    6.         OnDestroyObject.Invoke(gameObject);
    7.     }
    8. }
    9.  
    10.  
    11. public class OnDestroyGameObjectListener : MonoBehaviour
    12. {
    13.     [SerializeField] private OnDestroyGameObject m_OnDestroyGameObject;
    14.     private void OnEnable()
    15.     {
    16.         if (m_OnDestroyGameObject != null)
    17.         {
    18.             m_OnDestroyGameObject.OnDestroyObject += OnDestroyListener;
    19.         }
    20.     }
    21.  
    22.     private void OnDisable()
    23.     {
    24.         if (m_OnDestroyGameObject != null)
    25.         {
    26.             m_OnDestroyGameObject.OnDestroyObject -= OnDestroyListener;  
    27.         }
    28.     }
    29.  
    30.     private void OnDestroyListener(GameObject otherGameObject)
    31.     {
    32.         //You can do stuff with the "destroyed" Game Object because the GameObject still not null
    33.     }
    34. }
     
    Last edited: Feb 19, 2020