Reputation: 2117
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
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
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