Fundamental issue with SetActive and Canvas elements

Hello,

I have some issue with the SetActive function and elements of a Canvas. In short: Once a GameObject is deactivated via SetActive(false) it can’t be reactivated.

Suppose a simple setting with a new scene. Just add a text element. Then add the following C# script to the Canvas object (not the text object!):

using System;
using UnityEngine;
using System.Timers;

public class Switch : MonoBehaviour
{
    public GameObject textObj;

    private Timer timer;
    // Start is called before the first frame update
    void Start()
    {
        textObj.SetActive(false);
        timer = new Timer(3000);
        timer.AutoReset = false;
        timer.Elapsed += ElapsedHandler;
        timer.Start();
    }

    private void ElapsedHandler(System.Object sender, EventArgs evtArgs)
    {
        Debug.Log("Timer elapsed.");
        textObj.SetActive(true);
        Debug.Log("End reached.");
    }}

The text object is correctly disabled, but when trying to reenable it, nothing happens. Interestingly the end of the ElapsedHandler is never reached. I just get the “Timer elapsed.” output. But no errors, no warning, nothing.

But if you do the activation in the Update routine (even if just called once), it suddenly works. E.g. this is working:

using System;
using UnityEngine;
using System.Diagnostics;
using Debug = UnityEngine.Debug;

public class Switch : MonoBehaviour
{
    public GameObject textObj;
    private bool once = false;

    private Stopwatch watch;
    // Start is called before the first frame update
    void Start()
    {
        textObj.SetActive(false);
        watch = new Stopwatch();
        watch.Start();
    }
    
    void Update()
    {
        if (watch.Elapsed.TotalSeconds > 5 && !once)
        {
            textObj.SetActive(true);
            watch.Stop();
            once = true;
        }
    }
}

And now I wonder why is that? Where does the magic happen? What do I not understand about the fundamental way the SetActive method works?
And what’s my main problem: I would like to deactivate and reactivate objects which are childs of the Canvas element from within other non-Unity methods. (The use of a timer with an event handler was just for demonstration purposes.)

I spent already a couple of hours on this. And this forum is also filled with questions regarding the SetActive method, but none have helped me so far. :confused:

Thanks in advance for any help!

After spending a lot of time into this nasty problem I found some kind of a solution and the cause of the problem.

Thanks to the Unity Tools for Visual Studio I was able to debug such a test script like in my original post. There I inspected the textObj when it should be set to active. And no wonder: It was indeed null (or at least important parts of it). Furthermore I was able to extract some error messages via the debug mode. See for example this screenshot:

alt text
The first and the last line are in german, sorry for that. But the important lines in between are in english. Here is a translation anyway: First line: “The value of this expression is possibly incorrect. It couldn’t be evaluated due to the following reasons:” and the last line: “Refresh in order to execute a reevaluation.”

So the important message is:

[…] can only be called from the main thread

Also in some other elements of the textObj I was able to read some error messages:

  • “Instantiate failed because the clone
    was destroyed during creation. This
    can happen if DestroyImmediate is
    called in MonoBehaviour.Awake.”
  • “The Object you want to instantiate is
    null.”

Well, what I’ve learned from this is the following:
You can’t disable/enable GameObjects of your scene from within other threads. And that’s what basically happened in my actual project. I do there a very little bit of multi threading due to reasons. And one of the threads actually runs some methods of which some want to enable and disable GameObjects.

That’s why en-/disabling objects from within the Update() method (as in my original post) works, because the Update() method of MonoBehaviour is called from within the main thread of the Engine.
Consequently a possible solution would be to do some check e.g. with a Stopwatch or Unity’s Time class as above within the Update() method. But since this is very inefficient, especially if you just need to run the enabling and/or disabling once (thousands and thousands of unnecessary checks), this is a bad solution. So what’s the alternative if you want to do a little bit of multithreading while maintaining the ability to enable and disable GameObjects? The answer is:

Coroutines!

Luckily the MonoBehaviour base class provides Coroutines. These Couroutines belong due to the (to me not so well known) internal structure of the Unity engine obviously somehow to the main thread. And they provide a very efficient way to achieve a multithreaded behaviour as I desire it.
Using coroutines I’m able to fix my problem by recoding the script somehow like this:

using System;
using UnityEngine;
using System.Collections;
using System.Diagnostics;
using Debug = UnityEngine.Debug;

public class Test : MonoBehaviour
{
    public GameObject textObj;
    // Start is called before the first frame update
    void Start()
    {
        textObj.SetActive(false);
        Stopwatch watch = new Stopwatch();
        watch.Start();
        StartCoroutine(TestCoRoutine(watch));
    }

    private IEnumerator TestCoRoutine(Stopwatch wotspatch)
    {
        Debug.Log("Entered TestCoRoutine.");
        while (wotspatch.Elapsed.TotalSeconds < 5)
            yield return null;
        Debug.Log("5 secs elapsed.");
        textObj.SetActive(true);
        StopCoroutine("TestCoRoutine");
    }
}

Et voilà! It is working now! :slight_smile:

Still I criticize that SetActive() can’t be called from within other threads. I would like to see that possibility in the future of the UnityEngine.