一、簡述
? ? ? ? 在我們實際的項目開發(fā)中,項目中通常有一個以上的場景Scene,尤以MOBA和RGP有大量的Scene。場景的加載為何用異步加載,最為主要的是用戶體驗。當Scene較大時,使用同步加載時,直接在內(nèi)存中映射加載,會造成許久的卡頓后才正常顯示,且用戶體驗很差,所以選擇異步加載且用一個過渡的UI界面來顯示場景的加載進步。
二、基本思路
????????在當前場景中開啟一個協(xié)程Coroutines,并使用UnityEngine.SceneManagement.SceneManager中的異步加載方法LoadSceneAsync(string sceneName)。詳細代碼請見下方。
private string strNextSceneName = null;//下一需要加載的場景
private void LoadScene(string sceneName)
{
strNextSceneName = sceneName;
StartCoroutine(StartLoadScene());//開啟協(xié)程
}
private IEnumerator StartLoadScene() {
AsyncOperation async = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(strNextSceneName);//使用異步加載
if (null == async)
{
yield break;//關(guān)閉協(xié)程
}
}
三、項目開發(fā)思路
? ? ? ? 在實際項目中,我們可能會返回上一個場景,用一個UI界面來顯示場景加載的進度,且在加載完成后回調(diào)。那么新建場景管理類來統(tǒng)一調(diào)度場景。
using System;
using System.Collections;
using UnityEngine;
/// /// 場景管理類///
public class SceneManager : MonoBehaviour{
private static SceneManager _mInstance;//私有單例
?private Action onSceneLoaded = null;//場景加載完成回調(diào)
private string strNextSceneName = null;//將要加載的場景名
private string strCurSceneName = null;//當前場景名,如若沒有場景,則默認返回Login
private string strPreSceneName = null;//上一個場景名
private bool bLoading = false; //是否正在加載中
private bool bDestroyAuto = true;//自動刪除loading背景
private const string _strLoadSceneName = "LoadingScene"; //加載場景名字
/// /// 獲取當前場景名 ///
public static string strLoadedSceneName => _mInstance.strCurSceneName;
public static void CreateInstance(GameObject go) {
if (null != _mInstance) return;
{
_mInstance = go.AddComponent();
DontDestroyOnLoad(_mInstance);
_mInstance.strCurSceneName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name; }
}
/// /// 加載前一個場景 ///
public static void LoadPreScene() {
if (string.IsNullOrEmpty(_mInstance.strPreSceneName)) return;
{
LoadScene(_mInstance.strPreSceneName); }
}
public static void LoadScene(string strLevelName) {
_mInstance.LoadLevel(strLevelName,null);
}
public static void LoadScene(string strLevelName, Action onSecenLoaded) {
_mInstance.LoadLevel(strLevelName, onSecenLoaded);
}
//加載場景 bDestroyAuto 自動刪除loading背景
void LoadLevel(string strLevelName, Action onSecenLoaded, bool isDestroyAuto = true) {
//是否可加載
if (bLoading) { return; }
if (strCurSceneName == strLevelName) {
//return;
}
bLoading = true; //鎖屏
//開始加載 onSceneLoaded = onSecenLoaded;
strNextSceneName = strLevelName;
strPreSceneName = strCurSceneName;
strCurSceneName = _strLoadSceneName;
bDestroyAuto = isDestroyAuto;
//加載進度UI界面
InitLoadingUI();
StartCoroutine(StartLoadSceneOnEditor(_strLoadSceneName, OnLoadingSceneLoaded, null)); //StartCoroutine(StartLoadSceneOnEditor(strNextSceneName, OnNextSceneLoaded, OnNextSceneProgress));
}
//加載顯示進度的UI界面
private void InitLoadingUI() {
UILoadingView.Show();//UI加載進度界面
}
//當加載過度場景加載完成
void OnLoadingSceneLoaded() {
StartCoroutine(StartLoadSceneOnEditor(strNextSceneName, OnNextSceneLoaded, OnNextSceneProgress));
}
//場景加載進度變化
void OnNextSceneProgress(float fProgress) {
UILoadingView.Instance.UpdateProgress(fProgress);
}
//加載下一場景王城回調(diào)
void OnNextSceneLoaded() {
bLoading = false;
OnNextSceneProgress(1);
strCurSceneName = strNextSceneName;
strNextSceneName = null;
onSceneLoaded?.Invoke();
}
//開始加載
private IEnumerator StartLoadSceneOnEditor(string strLevelName, Action OnSecenLoaded, Action OnSceneProgress) {
AsyncOperation async = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(strLevelName);
if (null == async) {
yield break;
}
while (!async.isDone) {//是否加載完成
//若未加載完成,則回調(diào)進度Action
OnSceneProgress?.Invoke(async.progress);
yield return null;
}
OnSecenLoaded?.Invoke();
}
}
注:
? ? ? ? 如果考慮到代碼性能的話,進度回調(diào)和加載完成回調(diào)使用delegate委托,去除System.dll的引用;
????????協(xié)程的yield return 0應(yīng)該使用yield return null,減少GC,因為0=>nul中有個裝箱過程,會產(chǎn)生不必要的GC;
? ? ? ? 協(xié)程的開啟需要在MonoBehaviour中實現(xiàn),則需要SceneManager的實例,且掛在場景中,保證場景在加載中不被摧毀;