Short answer
Currently we can't do that in certain cases.
Long asnwer
AsyncOperation.allowSceneActivation tells us:
When allowSceneActivation
is set to false then progress is stopped at 0.9
. The isDone
is then maintained at false
. When allowSceneActivation
is set to true isDone
can complete. While isDone
is false, the AsyncOperation
queue is stalled. For example, if a LoadSceneAsync.allowSceneActivation
is set to false
, and another AsyncOperation
(e.g. SceneManager.UnloadSceneAsync
) is initialized, the last operation will not be called before the first allowSceneActivation
is set to true
.
Which means that there is an order to load pre-loaded scenes. If we pre-load scenes [Scene] Main Game
and [Scene] Sandbox
in this order, we can't activate [Scene] Sandbox
until we activate [Scene] Main Game
. If we first activate [Scene] Main Game
it will activate without a problem if it was pre-loaded first, and following we can activate [Scene] Sandbox
because the queue is empty.
In conclusion, right now we can only load scenes in specific order.
More: [ISSUE] Activating multiple scene at the same time via AsyncOperation.allowSceneActivation .
Solutions
- Use Addressables and make scenes as prefabs. Load/Unload instances of this prefab in a manner like you would load/unload scenes.
Code
Git: Preload multiple scenes at the same time and activate them on demand in Unity. [Unity, async, simultaneous]
I have tested this with both SceneManager
and Addressables
.
PreloadSceneManager.cs
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;
public class PreloadSceneManager : MonoBehaviour
{
private Dictionary<string, AsyncOperation> _sceneName_sceneLoadAsyncOperation = new Dictionary<string, AsyncOperation>();
public AsyncOperation PreloadSceneAsync(string sceneName, LoadSceneMode loadSceneMode)
{
if (this._sceneName_sceneLoadAsyncOperation.TryGetValue(key: sceneName, value: out AsyncOperation loadSceneAsyncOperation))
return loadSceneAsyncOperation;
loadSceneAsyncOperation = SceneManager.LoadSceneAsync(
sceneName: sceneName,
mode: loadSceneMode
);
loadSceneAsyncOperation.allowSceneActivation = false;
this._sceneName_sceneLoadAsyncOperation.Add(
key: sceneName,
value: loadSceneAsyncOperation
);
return loadSceneAsyncOperation;
}
public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode)
{
if (this._sceneName_sceneLoadAsyncOperation.TryGetValue(key: sceneName, value: out AsyncOperation loadSceneAsyncOperation))
{
loadSceneAsyncOperation.allowSceneActivation = true;
return loadSceneAsyncOperation;
}
loadSceneAsyncOperation = SceneManager.LoadSceneAsync(
sceneName: sceneName,
mode: loadSceneMode
);
return loadSceneAsyncOperation;
}
private Dictionary<string, AsyncOperationHandle<SceneInstance>> _sceneName_sceneLoadAsyncOperationHandle = new Dictionary<string, AsyncOperationHandle<SceneInstance>>();
public AsyncOperationHandle<SceneInstance> AddressablesPreloadSceneAsync(string key, LoadSceneMode loadSceneMode)
{
if (this._sceneName_sceneLoadAsyncOperationHandle.TryGetValue(key: key, value: out AsyncOperationHandle<SceneInstance> loadSceneAsyncOperationHandle))
return loadSceneAsyncOperationHandle;
loadSceneAsyncOperationHandle = Addressables.LoadSceneAsync(
key: key,
loadMode: loadSceneMode,
activateOnLoad: false
);
this._sceneName_sceneLoadAsyncOperationHandle.Add(
key: key,
value: loadSceneAsyncOperationHandle
);
return loadSceneAsyncOperationHandle;
}
public AsyncOperationHandle<SceneInstance> AddressablesLoadSceneAsync(string key, LoadSceneMode loadSceneMode)
{
if (this._sceneName_sceneLoadAsyncOperationHandle.TryGetValue(key: key, value: out AsyncOperationHandle<SceneInstance> loadSceneAsyncOperationHandle))
{
loadSceneAsyncOperationHandle.Result.ActivateAsync();
return loadSceneAsyncOperationHandle;
}
loadSceneAsyncOperationHandle = Addressables.LoadSceneAsync(
key: key,
loadMode: loadSceneMode,
activateOnLoad: true
);
return loadSceneAsyncOperationHandle;
}
private void Awake()
{
Object.DontDestroyOnLoad(target: this.gameObject);
}
}
SceneLoader.cs
using System.Collections;
using UnityEngine;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;
public class SceneLoader : MonoBehaviour
{
private IEnumerator LoadSceneAsyncProcess(string sceneName, AsyncOperation asyncOperation)
{
while (!asyncOperation.isDone)
{
Debug.Log($"[scene]:{sceneName} [percent complete]: {asyncOperation.progress}");
yield return null;
}
}
private IEnumerator LoadSceneAsyncProcess(string sceneName, AsyncOperationHandle<SceneInstance> asyncOperationHandle)
{
while (!asyncOperationHandle.IsDone)
{
Debug.Log($"[scene]:{sceneName} [percent complete]: {asyncOperationHandle.PercentComplete}");
yield return null;
}
}
[SerializeField] private PreloadSceneManager _preloadSceneManager;
public PreloadSceneManager _PreloadSceneManager => this._preloadSceneManager;
[SerializeField] private string _sceneNameOrKey = "[Scene] Preload Scene 1";
public string _SceneNameOrKey => this._sceneNameOrKey;
[SerializeField] private KeyCode _loadSceneKeyCode = KeyCode.Alpha1;
public KeyCode _LoadSceneKeyCode => this._loadSceneKeyCode;
[SerializeField] private LoadSceneMode _loadSceneMode = LoadSceneMode.Additive;
public LoadSceneMode _LoadSceneMode => this._loadSceneMode;
private void Update()
{
//if (Input.GetKeyDown(key: KeyCode.Return))
//{
// // Start scene preloading.
// Debug.Log($"Preload [scene]: {this._sceneName}");
// AsyncOperation asyncOperation = this._preloadSceneManager.PreloadSceneAsync(
// sceneName: this._sceneName,
// loadSceneMode: LoadSceneMode.Additive
// );
// this.StartCoroutine(
// routine: this.LoadSceneAsyncProcess(
// sceneName: this._sceneName,
// asyncOperation: asyncOperation
// )
// );
//}
//if (Input.GetKeyDown(key: this._loadSceneKeyCode))
//{
// Debug.Log($"Load [scene]: {this._sceneName}");
// AsyncOperation asyncOperation = this._preloadSceneManager.LoadSceneAsync(
// sceneName: this._sceneName,
// loadSceneMode: LoadSceneMode.Additive
// );
//}
if (Input.GetKeyDown(key: KeyCode.Return))
{
// Start scene preloading.
Debug.Log($"Preload [scene]: {this._sceneNameOrKey}");
AsyncOperationHandle<SceneInstance> asyncOperationHandle = this._preloadSceneManager.AddressablesPreloadSceneAsync(
key: this._sceneNameOrKey,
loadSceneMode: this._loadSceneMode
);
this.StartCoroutine(
routine: this.LoadSceneAsyncProcess(
sceneName: this._sceneNameOrKey,
asyncOperationHandle: asyncOperationHandle
)
);
}
if (Input.GetKeyDown(key: this._loadSceneKeyCode))
{
Debug.Log($"Load [scene]: {this._sceneNameOrKey}");
this._preloadSceneManager.AddressablesLoadSceneAsync(
key: this._sceneNameOrKey,
loadSceneMode: this._loadSceneMode
);
}
}
private void Awake()
{
Object.DontDestroyOnLoad(target: this.gameObject);
}
}