一、简述
在我们实际的项目开发中,项目中通常有一个以上的场景Scene,尤以MOBA和RGP有大量的Scene。场景的加载为何用异步加载,最为主要的是用户体验。当Scene较大时,使用同步加载时,直接在内存中映射加载,会造成许久的卡顿后才正常显示,且用户体验很差,所以选择异步加载且用一个过渡的UI界面来显示场景的加载进步。
二、基本思路
在当前场景中开启一个协程Coroutines,并使用UnityEngine.SceneManagement.SceneManager中的异步加载方法LoadSceneAsync(string sceneName)。详细代码请见下方。
private string strNextSceneName = null;//下一需要加载的场景
private void LoadScene(string sceneName)
{
strNextSceneName = sceneName;
StartCoroutine(StartLoadScene());//开启协程
}
private IEnumerator StartLoadScene() {
AsyncOperation async = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(strNextSceneName);//使用异步加载
if (null == async)
{
yield break;//关闭协程
}
}
三、项目开发思路
在实际项目中,我们可能会返回上一个场景,用一个UI界面来显示场景加载的进度,且在加载完成后回调。那么新建场景管理类来统一调度场景。
using System;
using System.Collections;
using UnityEngine;
/// /// 场景管理类///
public class SceneManager : MonoBehaviour{
private static SceneManager _mInstance;//私有单例
private Action onSceneLoaded = null;//场景加载完成回调
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);
}
//加载下一场景王城回调
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) {//是否加载完成
//若未加载完成,则回调进度Action
OnSceneProgress?.Invoke(async.progress);
yield return null;
}
OnSecenLoaded?.Invoke();
}
}
注:
如果考虑到代码性能的话,进度回调和加载完成回调使用delegate委托,去除System.dll的引用;
协程的yield return 0应该使用yield return null,减少GC,因为0=>nul中有个装箱过程,会产生不必要的GC;
协程的开启需要在MonoBehaviour中实现,则需要SceneManager的实例,且挂在场景中,保证场景在加载中不被摧毁;