Search code examples
c#unity-game-engineunity-ui

MissingReferenceException: The object of type 'UnityEngine.GameObject' has been destroyed but you are still trying to access it from EventSystem Unity


The Built-in eventsystem for unity UI keeps trying to access a button after I have used Destroy() on its gameobject. it stops and doesn't start again when I pause but will continue every frame otherwise. the object is just the right click-> UI -> button - text mesh pro. The following is the function the button executes and the function to make the button (in that order)

    public void makeProgram(int i, Button button)
    {
        GameObject obj = Instantiate(ProgramObject, programHolder);
        programs.Add(obj.GetComponent<Program>());
        obj.GetComponent<Program>().gameManager = this;
        obj.GetComponent<RectTransform>().localPosition = button.transform.localPosition;
        Destroy (button.gameObject);
    }
    public void makeButton(int i)
    {
        Button button = Instantiate(ProgramBuyButton, programHolder)
        .GetComponent<Button>();
        button.name = i+"";
        button.GetComponent<RectTransform>().localPosition = Tools.makeProgramPosition(i - 1);
        button.onClick.AddListener(() => makeProgram(i, button));
    }

The error references InputSystemUIInputModule:476, which is the second line in the following code.

            // now issue the enter call up to but not including the common root
            Transform oldPointerEnter = eventData.pointerEnter?.transform;
            eventData.pointerEnter = currentPointerTarget;

The problem with this is that it stops the user from interacting with any other UI objects. The error is:

Your script should either check if it is null or you should not destroy the object.
UnityEngine.Object+MarshalledUnityObject.TryThrowEditorNullExceptionObject (UnityEngine.Object unityObj, System.String parameterName) (at <adbae017f0374fce9921b97a33a4e8ca>:0)
UnityEngine.Bindings.ThrowHelper.ThrowNullReferenceException (System.Object obj) (at <adbae017f0374fce9921b97a33a4e8ca>:0)
UnityEngine.GameObject.get_transform () (at <adbae017f0374fce9921b97a33a4e8ca>:0)
UnityEngine.InputSystem.UI.InputSystemUIInputModule.ProcessPointerMovement (UnityEngine.InputSystem.UI.ExtendedPointerEventData eventData, UnityEngine.GameObject currentPointerTarget) (at ./Library/PackageCache/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs:476)
UnityEngine.InputSystem.UI.InputSystemUIInputModule.ProcessPointerMovement (UnityEngine.InputSystem.UI.PointerModel& pointer, UnityEngine.InputSystem.UI.ExtendedPointerEventData eventData) (at ./Library/PackageCache/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs:402)
UnityEngine.InputSystem.UI.InputSystemUIInputModule.ProcessPointer (UnityEngine.InputSystem.UI.PointerModel& state) (at ./Library/PackageCache/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs:352)
UnityEngine.InputSystem.UI.InputSystemUIInputModule.Process () (at ./Library/PackageCache/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs:2257)
UnityEngine.EventSystems.EventSystem.Update () (at ./Library/PackageCache/com.unity.ugui/Runtime/UGUI/EventSystem/EventSystem.cs:530)

I have tried to find a way to "safely" destroy the button but i cant find any that relate to the eventsystem, I feel like moving it away from the cursor and using a coroutine to destroy it after a little would (edit: I tested it and it does, but still would like a nicer solution) work but also wouldnt be the most elegant and I wish to find a better way to deal with this. Thank you for reading and I hope that there is a better resolution for this.


Solution

  • Sounds like they tricked themselves when implementing the InputSystemUIInputModule!

    eventData.pointerEnter is of type GameObject which is a UnityEngine.Object. You can read the details here (Why does C# null-conditional operator not work with Unity serializable variables?) but in short:

    You can't/shouldn't use the ?. (nor ?? and !.) operator on anything derived from UnityEngine.Object because Unity has their own custom == null check override which operates on the native c++ engine basis. However, the ?. (and related) operator works on a pure System.Object level and bypasses their custom check.

    => This check

    eventData.pointerEnter?.transform
    

    is invalid! The c# level System.Object is not null at this point - which is actually also the reason why you are getting a custom MissingReferenceException including the information that you destroyed the object, rather than just the normal NullReferenceException.

    At this point the object is only destroyed on the c++ engine level and therefore their custom operator returns true for == null, but when exactly the c# level System.Object for this instance is actually GC collected you can't tell, usually at least not during the current frame!


    Long story short, you either customize that line in the source code (if possible)

    Transform oldPointerEnter = eventData.pointerEnter != null ? eventData.pointerEnter.transform : null;
    

    which now works on the UnityEngine.Object level and properly uses their custom == operator.


    Alternatively a simple workaround without requiring touching the source code or a Coroutine might be to use the delay parameter of Destroy and do e.g.

    button.gameObject.SetActive(false); 
    Destroy(button.gameObject, 1);
    

    This will only deactivate the button and destroy it after 1 second.

    One second is probably overkill but just to be sure