I’ve got this running. It seems as the scene activation is part of the async operation, so when I reach 90% (A bit of a ‘magic number’ here…), I say we are close enough and allow scene activation to start.
And don’t stop unity while the AsyncOperation has not completed. Unity will hang.
I’m not sure how ‘safe’ the 90% is and it would be nice to get some confirmation from someone at Unity, that this is a valid wait of doing tings. The progress does not update smoothly, but rather jumps through some numbers now and then. But it always seems to end at 90% when ‘ready to be activated’.
{
AsyncOperation status = Application.LoadLevelAsync(level);
status.allowSceneActivation = false;
// Wait until done and collect progress as we go.
while( !status.isDone )
{
loadProgress = status.progress;
if( loadProgress >= 0.9f )
{
// Almost done.
break;
}
yield return null;
}
// Allow new scene to start.
status.allowSceneActivation = true;
yield return status;
}
Have you tried checking the value of Application.isLoadingLevel to see if it’s done?
Edit: I did a quick test and this doesn’t help. Application.isLoadingLevel stays true until after activation. If it helps I also find it stops at 0.9 progress.
The check for progress < 0.9f seems to work for me as well. I’m using this at the moment:
while (!status.isDone status.progress < 0.9f)
yield return null;
However, make sure you actually use 0.9f, not 0.9. Because of the conversion from float to double the progress never reaches double 0.9 which is a slight bit more than the float 0.9f.
Even though you are delaying scene activation, it doesn’t prevent the previous level from being unloaded. I suspect the gameObject that is running the coroutine is being destroyed before level progress reaches %100. Use DontDestroyOnLoad on your go to have it persist across scene boundaries, and then destroy itself after setting allowSceneActivation = true.
EDIT: If you already are using DontDestroyOnLoad, then perhaps the problem is a Unity bug in the version of Unity you’re using…? I’m on 4.1.2 and the following works for me on PC:
public class SceneMgr : MonoBehaviour
{
private static SceneMgr s_instance = null;
private AsyncOperation m_asop = null;
private bool m_quitAfterCurrentLoad = false;
public static SceneMgr Get()
{
return s_instance;
}
public SceneMgr()
{
s_instance = this;
}
public void RestartCurrentLevel()
{
LoadLevelImmediate(Application.loadedLevel);
}
// Pass either the level name as a string or the level index as an int to "level" param
public void LoadLevel(object level, bool manualActivation = false)
{
if (Application.isLoadingLevel)
{
Debug.LogError("Call attempted to LoadLevel while a level is already in the process of loading; ignoring the load request...");
}
else
{
m_asop = _LoadLevelAsyncProxy(level);
if (null != m_asop)
{
m_asop.allowSceneActivation = !manualActivation;
Application.backgroundLoadingPriority = ThreadPriority.Low;
}
}
}
public void LoadLevelImmediate(object level)
{
if (Application.isLoadingLevel)
{
Debug.LogError("Call attempted to LoadLevel while a level is already in the process of loading; ignoring the load request...");
}
else
{
_LoadLevelImmediateProxy(level);
}
}
public void ActivateLoadedLevel()
{
// This will immediately activate the currently loaded level if it hasn't been activated.
// If the level hasn't finished loading, it will be activated immediately after finishing.
if (null != m_asop)
{
m_asop.allowSceneActivation = true;
}
else
{
Debug.LogWarning("SceneMgr::ActivateLoadedLevel was called, but there is no inactive scene to activate!");
}
}
private static AsyncOperation _LoadLevelAsyncProxy(object level)
{
if (level.GetType() == typeof(int))
{
return Application.LoadLevelAsync((int)level);
}
else if (level.GetType() == typeof(string))
{
return Application.LoadLevelAsync((string)level);
}
else
{
Debug.LogError("SceneMgr.LoadLevel was called with the wrong parameter type " + level.GetType() + "; must be int or string.");
}
return null;
}
private static void _LoadLevelImmediateProxy(object level)
{
if (level.GetType() == typeof(int))
{
Application.LoadLevel((int)level);
}
else if (level.GetType() == typeof(string))
{
Application.LoadLevel((string)level);
}
else
{
Debug.LogError("SceneMgr.LoadLevelImmediate was called with the wrong parameter type " + level.GetType() + "; must be int or string.");
}
}
//////////////////////////////////////////////////////////////////////////
/// UNITY CALLBACKS
void Awake()
{
// Persistent object
DontDestroyOnLoad(gameObject);
}
void OnEnable()
{
}
IEnumerator OnLevelWasLoaded(int level)
{
while (null != m_asop !m_asop.allowSceneActivation)
{
// Wait until the just-loaded scene is allowed to start
yield return null;
}
m_asop = null;
if (m_quitAfterCurrentLoad)
{
Application.Quit();
}
}
void OnApplicationQuit()
{
if (null != m_asop)
{
m_quitAfterCurrentLoad = true;
Application.CancelQuit();
Debug.Log("OnApplicationQuit : CancelQuit! Attempting to quit while a scene is loading; quitting after scene load finishes. Called from: " + name);
}
else
{
Debug.Log("OnApplicationQuit : Shutting down. Called from: " + name);
s_instance = null;
}
}
}
The script uses the existence of the AsyncOperation object stored in m_asop to determine if a level load request is in progress. The “manualActivation” parameter to LoadLevel lets the caller start loading a scene early, but not unload the current level or switch to the new level until ActivateLoadedLevel is called. The purpose of the OnApplicationQuit handler is to prevent crashing if trying to quit during an asynchronous scene load; note however it doesn’t help on iPhone, the WebPlayer, or the Editor because Application.CancelQuit is ignored.
(The _LoadLevelAsyncProxy and _LoadLevelImmediateProxy functions are just a hack to get around the fact that Unity has no interface for converting a scene index to a scene name or vice versa. The hack enables LoadLevel to be called with either a scene index or a scene name… I’m sure there must be a better way of doing that though.)
In case this helps anyone I came across this problem too and found out that the reason we get to 0.9 is that when you make allowSceneActivation = false it will stop downloading it at 90% and wait for it to be true to continue. So because of that it never gets to a 100% and it never changes the isDone to true. But if you leave allowSceneActivation = true then everything works just fine you even get the 100% and the isDone changes to true.
Yep that’s because otherwise the level would simply start as soon as it was finished downloading regardless of the state of the allow flag.
Setting allow to false effectively stalls the completion of the loading process until you are ready. The only issue is that the unity team haven’t documented that this is how it works!
and it was working fine, but I wanted to try having the next scene ‘mostly’ loaded in the background so the final part would load quickly when required, so I tried adding this to an Awake function in the first scene.
However when this new code runs, secondScene becomes active straight away, even though allowSceneActivation is set to false. I must be misunderstanding something. Can anyone tell me how to have the ‘next’ scene ready to activate when the user clicks a button or something?
You always have the problem that not one employee at Unity, has ever, even opened the product. So you get these utterly bizarre problems, that you would notice instantly the first time you say “tried to make a game”
Unity has never changed, and will never change. So you just have to deal with it.
I’m fairly certain that everything to do with the AsyncOperation (both the allowSceneActivation and the Awake behaviour) is both fully intended and documented. Awake should always fire the moment the Object is created, and allowSceneActivation shouldn’t be the only exception to this rule. whether a scene starts or not doesn’t change the fact that it loaded and those objects were instantiated. and objects that are instantiated call Awake
As for the progress pausing at 90% when AllowSceneActivation is false. that’s supposed to happen. and its clearly specified in the documentation.It has always been that way and there’s no reason they should change that. its there to help you synchronize the scene loading with other expensive operations.
This allows:
your other scripts (which activate in Awake) time to complete expensive calculations.
your loading screen time to perform animated transitions (for example, time for fading) towards the starting level
some other expensive AsyncOperations (eg ResourceRequest or AssetBundleRequest) time to complete before you want the level to start
all the audio (music, ambience, SFX) time to fully load before you even start the scene so that you don’t hear desynced audio.
when those other expensive assets are ready then set allowSceneActivation back to true. its not a private variable you can totally change it while the AsyncOperation is running and the progress will finally reach 100%. thats the entire idea behind the property.