0
\$\begingroup\$

In my game, I have

  1. A start up scene that has a 1 script. This script loads
  2. The inventory scene
  3. The first level

I don't want to show the inventory scene, I just want to pre-load it. Therefore I have put all the objects in the inventory scene into an empty game object named "InventoryRoot". I have called DontDestroyOnLoad on it. I want to disable this gameobject after the scene has loaded. This way, the inventory scene is not shown, but it is "there". When I want to show the inventory scene, I will call InventoryRoot.setActive(true);

However, I don't know how to set it non-active from the outside.

My current code is this:

AsyncOperation asyncLoad = SceneManager.LoadSceneAsync("InventoryScene");

// Wait until the asynchronous scene fully loads
while (!asyncLoad.isDone)
{
    yield return null;
}
      
//now I would like to do something like this (pseudo-code):
asyncLoad.getScene().getRootObject().setActive(false);

Edit:

Later, when I want to show the inventory, I would simply call

_InventoryScene.getRootObject().setActive(true);

I would also like to pass variables to the _InventoryScene and not use playerprefs or similar.

That is why I wanted to learn how to access the rootobject of another scene. I thought I could then in the same way also access scripts in the other scene and pass data to it, for example like this:

_InventoryScene.showInventory(MyPlayer.CurrentInventory);
_InventoryScene.getRootObject().setActive(true);
\$\endgroup\$
4
  • \$\begingroup\$ It sounds like what you really want to ask is "How can I disable the root game object of a scene loaded asynchronously?" or maybe more fundamentally "How do I load scene content but not show it until later?. You're guessing that this would be done by reaching "through" the AsyncOperation to get the scene-in-waiting, but this guess might be wrong. Asking about your guess can get you dead ends if your guessed-at approach turns out to be impossible (or unhelpful). Asking about your problem gets you solutions, even if they're not the ones you imagined at first. \$\endgroup\$
    – DMGregory
    Jul 29 at 23:28
  • \$\begingroup\$ @DMGregory Thank you, I have re-phrased my question and added more info about my plans. \$\endgroup\$
    – tmighty
    Jul 30 at 0:20
  • \$\begingroup\$ Have you considered allowing scene activation on your inventory scene, but just setting the root object(s) to inactive in the scene file at edit time, or in a Start/Awake script? \$\endgroup\$
    – DMGregory
    Jul 30 at 1:14
  • \$\begingroup\$ Yes, I have considered that, but I also wanted to learn how to access gameobjects in other scenes. Being able to do that would then allow me to both access the root object and calling scripts in this scene from outside. \$\endgroup\$
    – tmighty
    Jul 30 at 1:26
0
\$\begingroup\$

Unfortunately this does not work. Setting .allowSceneActivation = false puts the entire app into a holding state: Scenes do not stop showing "(is loading)"

An alternative approach using a singleton would look like this:

Create a GameManager script of type Singleton. It's code is stated on the bottom of this posting.

Instead of using global variables and access them directly (like PlayerHealther = 50), you call it like this:

GameManager.Instance.PlayerHealth = 50;

Keeping everything in a single class makes using it fun and keeps everything nicely organized.

In your GameManager, at startup, you load the scenes that you require to be permanently available, additively.

You put all of your scene object in an empty gameobject called "...Root". For example, the inventory screen scene's root gameobject would be called "InventorySceneRoot".

You would load it like this:

private GameObject _InventoryRoot = null;

public void StartUp()
{
     pGetOrLoadInventoryWithoutActivating(); 
}

private void pGetOrLoadInventoryWithoutActivating()
{
    if (_InventoryRoot != null)
    {
        return;//already loaded 
    }

    _InventoryRoot = GameObject.Find("InventoryRoot");

    if (_InventoryRoot != null)
    {
        return;//already loaded 
    }

    StartCoroutine(pLoadInventoryWithoutActivating());
}

IEnumerator pLoadInventoryWithoutActivating()
{
    AsyncOperation nAsyncLoad = SceneManager.LoadSceneAsync("InventoryScene");

    // Wait until the asynchronous scene fully loads
    while (!nAsyncLoad.isDone)
    {
        yield return null;
    }

    _InventoryRoot = GameObject.Find("InventoryRoot");

    if (_InventoryRoot == null)
    {
        Debug.Break();
    }
    DontDestroyOnLoad(_InventoryRoot);//make it so that the InventoryRoot is not destroyed when we load other scenes. But setting the root object to DontDestroyOnLoad, we protect the entire scene of being unloaded

    _InventoryRoot.SetActive(false);//make the inventory scene disactive at first

    pCheckStarted();
}

Then if you later want to show or hide the inventory scene, you call the following function:

public void ShowOrHideInventory(bool uActivate)
{
    _InventoryRoot.SetActive(uActivate);
}

like this:

GameManager.Instance.ShowOrHideInventory(true); //do this to show the inventory

You would do the level loading in the GameMananager as well, for example like this:

public void LoadLevel1()
{
    if (_Level1Root != null)
    {
        return;//already loaded via StartCoroutine(pLoadSceneAsync("Level1Scene"));
    }

    _Level1Root = GameObject.Find("Level1Root");

    if (_Level1Root != null)
    {
        return;//already loaded
    }

    StartCoroutine(pLoadLevel1Async("Level1Scene"));
}

private IEnumerator pLoadLevel1Async(string u)
{
    AsyncOperation nAsyncLoad = SceneManager.LoadSceneAsync(u);

    while (!nAsyncLoad.isDone)
    {
        yield return null;
    }
    _Level1Root = GameObject.Find("Level1Root");

    if (_Level1Root != null)
    {
        Debug.Break();//must be present
    }
}

That's the basic concept. You have to understand it and adapt it to your project because not all games have the same architecture, but for the start, it will work.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using UnityEditor;
using UnityEngine.SceneManagement;

public class GameManager : Singleton<GameManager>
{
    private GameObject _InventoryRoot = null;
    private GameObject _HUDRoot = null;
    private GameObject _Level1Root = null;

    private HUDanimator _HUD = null;

    private bool _bPrerequisitesLoaded = false;

    public bool PrerequitesLoaded()
    {
        return _bPrerequisitesLoaded;//In my game, the HUD is in a scene of its own. However, Level1 uses this HUD via GameManager.Instance.getHUD(). There are situations where the Level1 has been loaded before the HUDScene, and the HUD is not available yet. Instead of letting it throw an error, I check in LateUpdate, Update, OnGUI, etc. of Level1 if all the necessary stuff (inventory and HUD) have already been loaded (hasStarted=
    }

    // Add your game manager members here
    public void Pause(bool paused)
    {
    }
    public void SaySomething(string u)
    {
        if (EditorUtility.DisplayDialog("Ok :-)", u, "Ok"))
        {

        }
    }
    public HUDanimator getHUD()
    {
        if (_HUD == null)
        {
            GameObject nGo = GameObject.Find("TheHUDCanvas");
            if (nGo == null)
            {
                Debug.Break();//the HUDScene is not loaded yet. This may not happen! We need to make sure that HUDScene has already been loaded
            }
            _HUD = nGo.GetComponentInChildren<HUDanimator>();
            if (_HUD == null)
            {
                Debug.Break();
            }
        }
        return _HUD;
    }
    public void ShowOrHideInventory(bool uActivate)
    {
        _InventoryRoot.SetActive(uActivate);
    }
    public void StartupLoadInventoryAndHUD()
    {
        GameObject nGo = GameObject.Find("Level1Root");
        if (nGo != null)
        {
            DontDestroyOnLoad(nGo);//we are calling this script from Level1Scene. So before we load HUD and Inventory, we need to protect this scene
        }

        pGetOrLoadInventoryWithoutActivating();
        pGetOrLoadHUD();
    }
    private void pGetOrLoadHUD()
    {
        if (_HUDRoot != null)
        {
            return;//already loaded 
        }

        _HUDRoot = GameObject.Find("HUDRoot");

        if (_HUDRoot != null)
        {
            return;//already loaded 
        }

        StartCoroutine(pLoadHUDWithoutActivating());
    }
    private void pGetOrLoadInventoryWithoutActivating()
    {
        if (_InventoryRoot != null)
        {
            return;//already loaded 
        }

        _InventoryRoot = GameObject.Find("InventoryRoot");

        if (_InventoryRoot != null)
        {
            return;//already loaded 
        }

        StartCoroutine(pLoadInventoryWithoutActivating());
    }
    private void pCheckStarted()
    {
        if (_HUDRoot != null)
        {
            if (_InventoryRoot != null)
            {
                _bPrerequisitesLoaded = true;
            }
        }
    }
    public void LoadLevel1()
    {
        if (_Level1Root != null)
        {
            return;//already loaded via StartCoroutine(pLoadSceneAsync("Level1Scene"));
        }

        _Level1Root = GameObject.Find("Level1Root");

        if (_Level1Root != null)
        {
            return;//already loaded
        }

        StartCoroutine(pLoadLevel1Async("Level1Scene"));
    }
    private IEnumerator pLoadLevel1Async(string u)
    {
        AsyncOperation nAsyncLoad = SceneManager.LoadSceneAsync(u);

        while (!nAsyncLoad.isDone)
        {
            yield return null;
        }
        _Level1Root = GameObject.Find("Level1Root");

        if (_Level1Root != null)
        {
            Debug.Break();//must be present
        }
    }

    IEnumerator pLoadInventoryWithoutActivating()
    {
        AsyncOperation nAsyncLoad = SceneManager.LoadSceneAsync("InventoryScene");

        while (!nAsyncLoad.isDone)
        {
            yield return null;
        }

        _InventoryRoot = GameObject.Find("InventoryRoot");

        if (_InventoryRoot == null)
        {
            Debug.Break();
        }
        DontDestroyOnLoad(_InventoryRoot);//make it so that the InventoryRoot is not destroyed when we load other scenes. But setting the root object to DontDestroyOnLoad, we protect the entire scene of being unloaded

        _InventoryRoot.SetActive(false);//make the inventory scene disactive at first

        pCheckStarted();
    }

    IEnumerator pLoadHUDWithoutActivating()
    {
        AsyncOperation nAsyncLoad = SceneManager.LoadSceneAsync("HUDScene");

        // Wait until the asynchronous scene fully loads
        while (!nAsyncLoad.isDone)
        {
            yield return null;
        }

        _HUDRoot = GameObject.Find("HUDRoot");

        if (_HUDRoot == null)
        {
            Debug.Break();
        }
        DontDestroyOnLoad(_HUDRoot);

        // _HUDRoot.SetActive(false);//make the HUD scene disactive at first

        pCheckStarted();
    }
}

And this is the singleton class:

using UnityEngine;

/// <summary>
/// Inherit from this base class to create a singleton.
/// e.g. public class MyClassName : Singleton<MyClassName> {}
/// </summary>
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    // Check to see if we're about to be destroyed.
    private static bool _bShuttingDown = false;
    private static object _bLock = new object();
    private static T _Instance;

    /// <summary>
    /// Access singleton instance through this propriety.
    /// </summary>
    public static T Instance
    {
        get
        {
            if (_bShuttingDown)
            {
                Debug.LogWarning("[Singleton] Instance '" + typeof(T) +
                    "' already destroyed. Returning null.");
                return null;
            }

            lock (_bLock)
            {
                if (_Instance == null)
                {
                    // Search for existing instance.
                    _Instance = (T)FindObjectOfType(typeof(T));

                    // Create new instance if one doesn't already exist.
                    if (_Instance == null)
                    {
                        // Need to create a new GameObject to attach the singleton to.
                        var singletonObject = new GameObject();
                        _Instance = singletonObject.AddComponent<T>();
                        singletonObject.name = typeof(T).ToString() + " (Singleton)";

                        // Make instance persistent.
                        DontDestroyOnLoad(singletonObject);
                    }
                }

                return _Instance;
            }
        }
    }

    private void OnApplicationQuit()
    {
        _bShuttingDown = true;
    }

    private void OnDestroy()
    {
        _bShuttingDown = true;
    }
}
\$\endgroup\$
-1
\$\begingroup\$

You could use SceneManager.LoadSceneAsync in conjunction with AsyncOperation.allowSceneActivation = false to load basically everything and then wait to activate until you're ready for it. Details on that here. Since AsyncOperations are executed in a queue that is suspended by this flag, you should enqueue scenes for async loading in the order that you intend to show them. The last thing in the queue is the one you can delay showing using allowSceneActivation = false.

You could also try what you had planned but load behind a splash so players don't see the scene pop in, then disable once it's active using your root object. While the scene has not yet been loaded, I don't know how you can get your root object to turn it on or off.

As with all things performance, before opting for a complicated loading system, you should profile to make sure it's worth your time.

\$\endgroup\$
2
  • \$\begingroup\$ Unfortunately this does not work. Setting .allowSceneActivation = false puts the entire app into a holding state: gamedev.stackexchange.com/questions/194874/… \$\endgroup\$
    – tmighty
    Jul 30 at 1:09
  • \$\begingroup\$ Weird, I should've read that more carefully, that's not how I anticipated that to work. It appears AsyncOperations are executed in a queue, so enter the scene you want to appear first, then put the one that should appear later after. If this question is related the project from your other question though, I'm not sure this approach will serve you well. \$\endgroup\$ Jul 30 at 2:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.