Wojtek Wencel
Wojtek Wencel

Reputation: 2117

How to wait until a scene is fully loaded?

I need to wait until a scene is fully loaded in order to move a gameObject to it from DontDestroyOnLoad scene. If I do it too soon (just after calling SceneManager.LoadScene()) then the gameObject disappears. Based on this post I implemented a scene loading class to solve this issue:

public static class CustomSceneManager
{
    public delegate void SceneChange(string sceneName);

    public static event SceneChange LoadScene;
    public static event SceneChange UnloadScene;

    private static IEnumerator LoadLevel (string sceneName){
        var asyncLoadLevel = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
        while (!asyncLoadLevel.isDone){
            Debug.Log("Loading the Scene"); 
            yield return null;
        }
    }
    
    public static void OnLoadScene(string newSceneName)
    {
        OnUnloadScene(newSceneName);
        LoadLevel(newSceneName);
        LoadScene?.Invoke(newSceneName);
    }
    private static void OnUnloadScene(string newSceneName)
    {
        UnloadScene?.Invoke(newSceneName);
    }
}

I'm calling two events from it (LoadScene and UnloadScene). However the LoadLevel(newSceneName) doesn't work - it simply doesn't load a scene. What am I doing wrong here?

EDIT:

Now I'm passing the MonoBehavior reference of the script calling OnLoadScene methid like this:

    public static void OnLoadScene(MonoBehaviour loader, string newSceneName)
    {
        UnloadScene?.Invoke(newSceneName);
        loader.StartCoroutine(LoadLevel(newSceneName));
        Debug.Log(SceneManager.GetActiveScene().name); // this line returns previous scene
        LoadScene?.Invoke(newSceneName);
    }

Now the scene loads, but when I check what scene is currently loaded, it returns the previous scene name.

EDIT 2:

To be more precise I replaced Debug.Log(SceneManager.GetActiveScene().name); with Debug.Log(SceneManager.GetSceneByName(newSceneName).isLoaded); and it returns False.

Upvotes: 4

Views: 12818

Answers (2)

Iggy
Iggy

Reputation: 4888

Another way you can do this is with an async method.

The catch is that in order to await an AsyncOperation you need a custom awaiter class. There are a few libraries that make it possible, and my favorite one is UniTask.

using Cysharp.Threading.Tasks;
using UnityEngine.SceneManagement;

public static class CustomSceneManager
{
    public delegate void SceneChange(string sceneName);

    public static event SceneChange LoadScene;
    public static event SceneChange UnloadScene;

    private static async UniTask LoadLevelAsync(string sceneName)
    {
        await SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
    }

    public static async UniTask OnLoadSceneAsync(string newSceneName)
    {
        OnUnloadScene(newSceneName);
        await LoadLevelAsync(newSceneName);
        LoadScene?.Invoke(newSceneName);
    }

    private static void OnUnloadScene(string newSceneName)
    {
        UnloadScene?.Invoke(newSceneName);
    }
}

Upvotes: 1

derHugo
derHugo

Reputation: 90724

You have to run coroutienes using StartCoroutine.

You either would need to pass in a reference of a MonoBehaviour that will execute the coroutine or simply make your class a Singleton that is never destroyed

Than actually you will invoke your event too early when it is not yet loaded but you just started to load it so rather do e.g.

public class CustomSceneManager : MonoBehaviour
{
    public delegate void SceneChange(string sceneName);

    public static event SceneChange LoadScene;
    public static event SceneChange UnloadScene;

    private CustomNetworkManager singleton;

    private void Awake ()
    {
        if(singleton && singleton != this)
        {
            Destroy(gameObject);
        }

        singleton = this;
        DontDestroyOnLoad (gameObject);
    }
    
    private static IEnumerator LoadLevel (string sceneName){
        var asyncLoadLevel = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
        while (!asyncLoadLevel.isDone){
            Debug.Log("Loading the Scene"); 
            yield return null;
        }

        LoadScene?.Invoke(newSceneName);
    }
    
    public static void OnLoadScene(string newSceneName)
    {
        if(! singleton)
        {
            singleton = new GameObject("CustomNetworkManager").AddComponent<CustomNetworkManager>();
        }

        OnUnloadScene(newSceneName);
        singleton.StartCoroutine(LoadLevel(newSceneName));
    }

Upvotes: 3

Related Questions