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
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Reference to a GameObject is unexpectedly cut off while waiting for an event fired

Discussion in 'Scripting' started by bboydaisuke, Nov 19, 2017.

  1. bboydaisuke

    bboydaisuke

    Joined:
    Jun 14, 2014
    Posts:
    67
    Hello,
    I'm struggling with the following weird and nasty behavior seems like a reference is cut off even though the object is still there. Please help me to point out what is wrong, fix it, and write a safe code.

    PROBLEM
    I have a reference to a GameObject because I want to control it on an event fired. However, the reference is released while waiting on the event.
    This problem occurs if the scene is loaded twice or more. Not happening on the first load.

    STEPS TO REPRO
    1. Clone or download https://github.com/dsuz/mre-proof-code and open with Unity
    2. Open scene "1" and run.
    3. Wait for "OK. Event is handled successfully. Click the button below." message and click button.
    4. Unity will load another scene and load that scene again.
    EXPECTED RESULT
    Event is handled and the message is displayed as first time loaded.

    ACTUAL RESULT
    The message won't be shown with the following error in console:
    This means it has lost the reference to the GameObject.

    ENVIRONMENT
    • macOS Sierra 10.12.6
    • Unity 2017.2.0f3
    CODE
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class SceneManager : MonoBehaviour
    6. {
    7.     [SerializeField] private EventKicker m_eventKicker;
    8.     [SerializeField] private GameObject m_go;
    9.  
    10.     void Start ()
    11.     {
    12.         Debug.Log("Deactivate m_go.");
    13.         m_go.SetActive(false);
    14.         Debug.Log("Add Event Handler.");
    15.         EventKicker.Kick += EventHandler;
    16.     }
    17.  
    18.     void EventHandler()
    19.     {
    20.         m_go.SetActive(true);  // the MRE thrown here. The object set in the inspector is accessed on Start() without any problem. However, it is gone while waiting for the event.
    21.     }
    22. }
    23.  
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class EventKicker : MonoBehaviour
    6. {
    7.     public delegate void MyAction();
    8.     public static event MyAction Kick;
    9.     bool m_flg;
    10.  
    11.     void Update()
    12.     {
    13.         if (!m_flg)
    14.         {
    15.             m_flg = true;
    16.             Debug.Log("Kick it.");
    17.             Kick();
    18.         }
    19.     }
    20. }
    21.  
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class SceneManager : MonoBehaviour
    6. {
    7.     [SerializeField] private EventKicker m_eventKicker;
    8.     [SerializeField] private GameObject m_go;
    9.  
    10.     void Start ()
    11.     {
    12.         Debug.Log("Deactivate m_go.");
    13.         m_go.SetActive(false);
    14.         Debug.Log("Add Event Handler.");
    15.         EventKicker.Kick += EventHandler;
    16.     }
    17.  
    18.     void EventHandler()
    19.     {
    20.         m_go.SetActive(true);
    21.     }
    22. }
    23.  
     
    Last edited: Dec 2, 2017
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,199
    The MissingReferenceException is a special Unity exception, which means that the target has been destroyed. So it hasn't "lost" the reference - that's not possible.

    So what's happening is:
    - you add the function of this SceneManager to the Kick event
    - you load the other scene. Both the SceneManager, the m_go and the m_eventKicker objects are destroyed. The registered event is static, though, so it keeps a reference to the destroyed object's EventHandler
    - you load the first scene again, and call the event. Everything registered from the last time is destroyed, so the invocation fails.


    The nitty gritty here is that C# can't really destroy objects at all - it's a garbage collected language. Unity destroys things all the time on the engine side. In the C# scripting environment, those destroyed objects gets put into an "is destroyed" state, and interacting with them causes exceptions.

    The easiest fix is to make you EventKicker's delegate not be static, or clear out the static delegate on scene load by setting it to null.
     
  3. bboydaisuke

    bboydaisuke

    Joined:
    Jun 14, 2014
    Posts:
    67
    Thanks for the response. I missed it.

    The easiest fix I took (specifically for the project... I have to consider more for the real project) is as follows:

    Code (CSharp):
    1.  .   void EventHandler()
    2.      {
    3.          m_go.SetActive(true);
    4.      }
    5.  
    6.      // add
    7.      private void OnDestroy()
    8.      {
    9.          EventKicker.Kick -= EventHandler;
    10.      }
    11. }