场景切换的时候经常会造成卡顿,导致用户体验并不好。
如何能做到不卡呢,有一种方式是做成prefab,然后通过启用禁用来模拟切场景,但是现在遇到的情况是希望烘焙出的lightmap也能够进行切换。而在unity5中动态的去切lightmap有点麻烦,没有去深入研究,于是采用了下面缓存场景的做法。
假设场景切换顺序:Main->A->B->C->Main
从Main场景切到A,A加载完后希望在A运行的同时能够缓存B场景。这样待会从B切到C时B因为已经缓存过了,加载耗时会比较短,B加载同时又缓存C场景,缓存C的过程不会造成B的卡顿,之后会有解释。
异步切换场景的核心代码如下
LoadAsyncApi(ref _curResult,_curSceneName);
yield return StartCoroutine(LoadInProgress(_curResult));
yield return StartCoroutine(LoadAfterProgress(_curResult, _curSceneName));
private void LoadAsyncApi(ref AsyncOperation result,string sceneName)
{
result = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
}
//0-0.9
private IEnumerator LoadInProgress(AsyncOperation result)
{
result.allowSceneActivation = false;
while (result.progress < 0.9f)
{
yield return null;
}
}
//0.9-1(start awake ,not include coroutine)
private IEnumerator LoadAfterProgress(AsyncOperation result,string sceneName )
{
result.allowSceneActivation = true;
while (!SceneManager.GetSceneByName(sceneName).isLoaded)
{
yield return null;
}
}
调用LoadSceneAsyc异步加载,然后将之后的加载分为两步。
allowSceneActivation很多帖子写的都很懵逼,今天我来讲清楚。。。
1. 0-0.9(阶段1)
allowSceneActivation设为false的时候,阻止Scene的完全激活,这时进度会一直卡在0.9。这个阶段会做一些磁盘相关的IO操作,比如载入资源等,这些操作不会阻塞当前主线程。当你的场景中引用了比较多的material啊,texture啊,那很多时间会花费在这里。
2. 0.9-1(阶段2)
将allowSceneActivation设为true,这个过程做的事情包括所有的awake,start函数的执行以及setactive的时间,应该也包括所有组件如collider,animator内部初始化相关的操作。通过实验发现,直接在场景中加入成千上万个立方体,最后场景加载的时间基本都花在这个阶段。你手动启用这些物体也是会造成卡顿的。这一步会造成主进程阻塞,会使得unity难以避免的卡顿,这一步暂无办法去优化。了解了这两步之后,实现这个需求就很清楚了。
Main切A加载B:
执行A的阶段1,阶段2,unload掉Main,再执行B的阶段1,让B卡在progress为0.9的位置。真正加载B的时候再执行B的阶段2。如果说场景中没有大量物体,而引用的资源又大又多,是可以做到秒切场景的。
提供了两个函数,EnterScene需要提供切换到的场景和需要缓存的场景。当不需要走这套机制,直接从场景中跳出到别的场景时,直接将precachescene设为null或者直接调用EnterOutScene退出即可。
完整代码:
DialogSceneMgr
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine.SceneManagement;
public class DialogSceneMgr : Singleton<DialogSceneMgr>
{
private string _curSceneName;
private string _precacheSceneName;
private bool _fromOuterScene = false;
private AsyncOperation _curResult;
private AsyncOperation _precacheResult;
void OnGUI()
{
if (GUILayout.Button("BaseLight2 cache BaseLight", GUILayout.Width(200)))
{
EnterScene("BaseLight2", "BaseLight");
//DialogSceneMgr.Instance.InitScenes(new List<string>() { "BaseLight", "BaseLight2" });
}
if (GUILayout.Button("BaseLight cache Context", GUILayout.Width(200)))
{
EnterScene("BaseLight","Context");
}
if (GUILayout.Button("SwitchOut", GUILayout.Width(200)))
{
EnterOuterScene("Context");
}
// if (GUILayout.Button("BaseLight2", GUILayout.Width(200)))
// {
// EnterScene("BaseLight2", null);
// }
// if (GUILayout.Button("BaseLight", GUILayout.Width(200)))
// {
// EnterScene("BaseLight", null);
// }
if (_curResult!=null)
GUILayout.TextArea("CurProgress:" + _curResult.progress);
if (_precacheResult != null)
GUILayout.TextArea("PrecacheProgress:" + _precacheResult.progress);
}
public void EnterScene(string sceneName,string precacheSceneName )
{
_curSceneName = sceneName;
//没有precache的scene说明是第一次进入
_fromOuterScene = string.IsNullOrEmpty(_precacheSceneName);
//缓存的场景和即将进入的场景不一致直接Load场景
if (_curSceneName != _precacheSceneName && !_fromOuterScene)
{
Clear();
SceneManager.LoadScene(_curSceneName);
return;
}
_precacheSceneName = precacheSceneName;
StartCoroutine(LoadSceneCor());
}
private void Clear()
{
_precacheSceneName = null;
}
public void EnterOuterScene(string sceneName)
{
EnterScene(sceneName, null);
}
private IEnumerator LoadSceneCor()
{
//from prev to cur
if (_fromOuterScene)
{
LoadAsyncApi(ref _curResult,_curSceneName);
yield return StartCoroutine(LoadInProgress(_curResult));
yield return StartCoroutine(LoadAfterProgress(_curResult, _curSceneName));
}
else
{
_curResult = _precacheResult;
yield return StartCoroutine(LoadAfterProgress(_curResult, _curSceneName));
}
UnLoadPrevScene();
SetActiveScene(_curSceneName);
//precache next
Debug.Log("precache next");
//没有缓存场景了,不需要缓存
if (string.IsNullOrEmpty(_precacheSceneName))
{
yield break;
}
LoadAsyncApi(ref _precacheResult,_precacheSceneName);
yield return StartCoroutine(LoadInProgress(_precacheResult));
}
private void UnLoadPrevScene()
{
Scene prevScene = SceneManager.GetActiveScene();
SceneManager.UnloadScene(prevScene.name);
}
private void SetActiveScene(string sceneName)
{
SceneManager.SetActiveScene(SceneManager.GetSceneByName(sceneName));
}
//0-0.9
private IEnumerator LoadInProgress(AsyncOperation result)
{
result.allowSceneActivation = false;
while (result.progress < 0.9f)
{
yield return null;
}
}
private void LoadAsyncApi(ref AsyncOperation result,string sceneName)
{
result = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
}
//0.9-1(start awake ,not include coroutine)
private IEnumerator LoadAfterProgress(AsyncOperation result,string sceneName )
{
result.allowSceneActivation = true;
while (!SceneManager.GetSceneByName(sceneName).isLoaded)
{
yield return null;
}
}
}
附singleton
using System;
using UnityEngine;
using System.Collections;
public class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
private static T mInstance;
private static bool isQuiting;
public static T Instance
{
get
{
if (mInstance == null && !isQuiting)
{
mInstance = new GameObject("(Singleton) " + typeof (T).Name).AddComponent<T>();
}
return mInstance;
}
}
private void Awake()
{
T instance = this as T;
if (mInstance != null && mInstance != instance)
{
T cacheInstance = mInstance;//因为下一步destroy的时候更改了static的mInstance
DestroyImmediate(this.gameObject);
mInstance = cacheInstance;
return;
}
// 切换场景不要销毁GameObject
DontDestroyOnLoad(gameObject);
mInstance = instance;
OnInit();
}
private void OnDestroy()
{
OnRelease();
mInstance = null;
}
private void OnApplicationQuit()
{
isQuiting = true;
}
/// <summary>
/// 初始化
/// </summary>
protected virtual void OnInit()
{
}
/// <summary>
/// 释放
/// </summary>
protected virtual void OnRelease()
{
}
}