如何等到场景完全加载?

How to wait until a scene is fully loaded?

我需要等到场景完全加载才能将 gameObjectDontDestroyOnLoad 场景移动到它。如果我做得太早(就在调用 SceneManager.LoadScene() 之后),那么 gameObject 就会消失。基于this post我实现了一个场景加载class来解决这个问题:

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);
    }
}

我正在从中调用两个事件(LoadSceneUnloadScene)。但是 LoadLevel(newSceneName) 不起作用 - 它根本不加载场景。我在这里做错了什么?

编辑:

现在我正在传递调用 OnLoadScene 方法的脚本的 MonoBehavior 引用,如下所示:

    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);
    }

现在场景加载了,但是当我检查当前加载的场景时,它 returns 以前的场景名称。

编辑 2:

更准确地说,我用 Debug.Log(SceneManager.GetSceneByName(newSceneName).isLoaded); 替换了 Debug.Log(SceneManager.GetActiveScene().name); 并且 returns False.

您必须 运行 协程使用 StartCoroutine

您要么需要传入将执行协程的 MonoBehaviour 的引用,要么只是让您的 class 成为一个永远不会被销毁的单例

实际上你会在事件尚未加载但你刚刚开始加载它时过早地调用它,所以宁愿这样做,例如

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));
    }

另一种方法是使用异步方法。

要注意的是,为了等待 A​​syncOperation,您需要自定义等待程序 class。有几个库使之成为可能,我最喜欢的是 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);
    }
}