Unity可以通过两种方式加载场景:SceneManager.LoadSceneAsync()和SceneManager.LoadScene()。
SceneManager.LoadScene()的运行机制
当我们在当前帧意图调用SceneManager.LoadScene()去加载一个新场景的时候。实际上,它并不会立即执行,而是要等到下一帧才会开始加载场景,并且在该帧彻底完成场景的加载(并且激活)。也就是说这种加载场景的方式是延迟一帧的,并且是帧阻塞的。
由于场景的加载将会在一帧立即完成,所以调用该方法,将导致所有先前的AsyncOperation强制被完成,即使AsyncOperation.allowSceneActivation被设置为false也无济于事。所以为了避免这种情况,应该使用SceneManager.LoadSceneAsync()代替该方法。这是因为存在一个AsyncOperation的队列(先进先出),只有前面的AsyncOperation完成(progress达到100%->isDone为true),才会弹出下一个AsyncOperation继续进行处理。
注:上述的AsyncOperation被完成,就是将其IsDone设置为true。由于使用SceneManager.LoadScene()的情况下,新场景将会在一帧内被立即加载与激活,原则上是会Destroy原场景的一切的。这种Destroy对于异步任务而言,就是将其强制提前完成(将isDone设置为),注销原场景所有的异步任务,不在执行。
SceneManager.LoadSceneAsync()的运行机制
调用SceneManager.LoadSceneAsync()方法,将会生成一个对应的AsyncOperation对象,用于实时的跟踪异步任务的状态。同时,这个AsyncOperation对象会被加入一个名为AsyncOperations的队列中,unity会按照先进先出的原则,依次弹出一个AsyncOperation对象,然后在背景线程中处理该对象所代表的异步任务。这意味着,只有位于LoadSceneAsync AsyncOperation对象之前的所有其它AsyncOperation对象都被弹出以后,才会弹出LoadSceneAsync AsyncOperation对象(即执行异步场景加载任务)。注意,所有的异步任务都不是在Main Thread中执行的,而是在一个单独的BackGround Thread中执行的,所以才可以做到不阻塞主线程。
此外,由于AsyncOperations继承自YieldInstruction,并且LoadSceneAsync会返回对它的引用。所以它还可以被用作协程的控制条件,用于在协程中判断场景是否异步加载完成。示例代码如下:
IEnumertor coroutinea()
{
yield return SceneManager.LoadSceneAsync(SceneIndex);
//当运行到这里,场景已经加载完毕,可以进行对新场景的操作。
}
实际上,上述代码等价于:
IEnumertor coroutinea()
{
AsyncOperation asop = SceneManager.LoadSceneAsync(SceneIndex);
yield return asop;
//当运行到这里,场景已经加载完毕,可以进行对新场景的操作。
}
需要注意的是,对yield return SceneManager.LoadSceneAsync()的条件检测发生在Awake和Start之间,这就意味着这在新场景成功加载后的那一帧,新场景会首先调用所有该场景所有组件的Awake,然后再执行yield return SceneManager.LoadSceneAsync()后面的代码,接着在调用所有组件的start方法。具体可以参照脚本生命周期流程图,大部分对yield return *** 的检测发生在Update和LateUpdate之间。
我们也可以通过Application.backgroundLoadingPriority来指定异步任务的性能占用,参考docs.unity3d.com/ScriptRefer… 。
我们应该将异步方法(Resources.LoadAsync, AssetBundle.LoadAssetAsync, AssetBundle.LoadAllAssetAsync, SceneManager.LoadSceneAsync)仅仅视为向Unity引擎本身发出的指令(C#只是一种控制用的脚本语言),其本身并没有实现任何处理异步任务的核心逻辑,而只是指令Unity去完成,并且返回一个AsyncOpreation的C#对象去实时的跟踪异步任务的处理进度,并且可以通过这个对象干预异步任务。所以C#作为一种用户脚本语言,架起了用户与Unity引擎内部进行沟通的桥梁。
异步场景加载的流程如下:
Question:
There are two phases in SceneManager.LoadSceneAsync().
Firstly there's the preload phase. Second the activation phase.
What is exactly loading in each phase?
Answe:
Pre-loading Scene:
A scene is loaded in the background. During this time, the resources such as textures, audios and 3D models referenced in that scene are loaded.
Activating Scene:
When the loaded scene is activated, the current scene is unloaded and the loaded scene will be active. When it becomes active, it will start executing the scripts referenced in that scene.
SceneManager.LoadSceneAsync will load the scene in the background. When the scene is loaded, it will be automatically activated. When activation is done, Unity will enable that loaded scene and the loaded scene will become the current scene.
Controlling Scene Activation:
Sometimes, you want to load the next scene when the current game is about to finish but you don't want it to activate it until the current game play is done. You are pre-loading the scene. This can be done by setting the AsyncOperation.allowSceneActivation property returned by the LoadSceneAsync function to false. By setting it to false, the scene will load but will not activate or run until you set it to true. Let's say you have finished playing the current scene, you can then activate the next scene which actually reduces the amount of time your player has to wait for the scene to finish loading. Loading and activating the next scene when the game is over will take more time than simply activating the scene.
相关参考文档如下: 1,gamedevbeginner.com/how-to-load… 2,stackoverflow.com/questions/5… 3,stackoverflow.com/questions/6…