Unity–異步加載場景
異步加載場景其實和異步加載資源是一樣的,只是加載的內容比較特殊而已. 也可以將場景視為特殊資源.
1.SceneManager.LoadScene
加載場景的方式,在Unity 中加載場景是通過SceneManager.LoadScene("場景名稱");
來實現加載場景,這和UE4中的OpenLevel
也是一樣的. 其中SceneManager是untiy中自帶的場景管理器,可以用于加載場景,卸載場景等.需要引入using UnityEngine.SceneManagement
才能使用
2.同步加載場景
和資源一樣,場景默認是同步加載的,也就是直接使用SceneManager.LoadScene("場景名稱")
來實現同步加載. 如果一個場景中的資源比較多,比如:游戲模型,粒子特效等,那么就會導致加載場景時候卡頓,很久才能加載場景.
需要注意的是,它會立即切換到新場景,這可能導致短暫的凍結或卡頓,特別是在加載較大或資源密集的場景時…為了解決這個問題,我們一般使用異步加載. 減輕主線程的壓力.
3.異步加載場景
和異步加載資源一樣,場景的異步加載也是有兩個過程: 加載中與加載完成.
異步加載的重要性:異步加載(LoadSceneAsync
)允許場景在后臺加載,這樣主線程可以繼續處理其他任務,如更新UI、處理玩家輸入等。這對于提高用戶體驗至關重要,特別是在資源密集型游戲中。
仔細分析就是因為資源過大,內容過多導致加載中的時間過長,我們一般的設計方式就是進度條,加載完畢一段內容,進度條走了20%或者其他.直到加載完畢才走到100%,當然這個進度條有可能是假的.
在Unity中異步加載場景的寫法如下:使用異步加載關鍵 + 加載完畢的回調函數
/// <summary>
/// 普通異步加載場景 + 調用回調函數
/// </summary>
/// <param name="scenenName">場景名稱</param>
void LoadSceneAsychonized(string sceneName)
{// 加載場景AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName);operation.completed += LoadSceneCompleted;
}
其中SceneManager.LoadSceneAsync(sceneName);
sceneName就是我們要加載的場景名稱. 返回值是一個異步加載操作的對象AsyncOperation
.和上面將的一樣,加載場景有兩個狀態:加載中與加載完畢.
AsyncOperation對象:其中AsyncOperation operation
就是記錄了場景是否加載完畢isDown
,沒有加載完畢就是在加載中. 當場景處于加載中,我們就能獲取場景的加載進度progress
, 優先級priority
以及當場景準備好了就激活場景allowSceneActivation
. 還有加載完畢的回調函數completed
.
AsyncOperation對象:這是異步加載的核心。它提供了加載進度(progress
)、是否完成(isDone
)等重要信息。通過這些屬性,可以創建進度條或執行其他加載相關的邏輯。
即
屬性/方法 | 含義 |
---|---|
isDown | 是否加載完成 |
progress | 場景的加載進度0-1的值,Unity很多時候是0.9,這個值準確 |
priority | 優先級 |
allowSceneActivation | 收否在場景準備好了就激活場景 |
completed | 加載完畢的回到函數 |
以下是AsyncOperation
C#中的代碼
namespace UnityEngine
{//// 摘要:// Asynchronous operation coroutine.[NativeHeader("Runtime/Export/Scripting/AsyncOperation.bindings.h")][NativeHeader("Runtime/Misc/AsyncOperation.h")][RequiredByNativeCode]public class AsyncOperation : YieldInstruction{public AsyncOperation();~AsyncOperation();// 摘要:Has the operation finished? (Read Only)public bool isDone { get; }// 摘要: What's the operation's progress. (Read Only)public float progress { get; }// 摘要: Priority lets you tweak in which order async operation calls will be performed.public int priority { get; set; }// 摘要:Allow Scenes to be activated as soon as it is ready.public bool allowSceneActivation { get; set; }public event Action<AsyncOperation> completed;}
}
注意,場景加載完畢后我們需要用一個函數來做一些其他內容, 比如:設置場景初始化[這里需要說明的是加載場景不等于初始化場景],還可以設置游戲狀態,UI的顯示隱藏等.
private void LoadSceneCompleted(AsyncOperation operation){// 場景加載完成后執行的代碼Debug.Log("Scene loaded successfully");// ... ...// 在這里可以進行場景初始化,例如查找和初始化游戲對象,設置游戲狀態等}
4.使用協程的方式異步加載場景
利用AsyncOperation operation
的isDone數顯來判斷是否加載完畢, 如果沒有加載完畢,就不可以做一些其他事情,并使用yield return
來等待一段時間,然后繼續判斷是否加載完畢,代碼如下:
/// <summary>/// 自定義協程加載場景/// </summary>/// <param name="operation"></param>/// <returns></returns>IEnumerator LoadWaitScene(AsyncOperation operation){// 獲得加載進度while(! operation.isDone){Debug.Log("加載中...\t進度: " + operation.progress);if (operation.progress >= 0.9f){// 激活場景 Allow Scenes to be activated as soon as it is ready.operation.allowSceneActivation = true;}// 自己做個假的進度條yield return null;}}
我們也可以直接在協程里使用yield return operaiton
來判斷是否記加載完畢, 需要注意的是,一旦獲得了加載操作的對象那么yeild return xxx
后的代碼就無法執行.因為場景加載好了,切換到了新的場景,舊的場景中的內容會被銷毀,也包括我們掛載的腳本
IEnumerator LoadScene(string sceneName){DontDestroyOnLoad(this.gameObject);AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName);Debug.Log("加載中...");yield return operation;// 后面的內容無法打印,因為場景被加載完畢,當前場景上游戲物體,腳本被移除Debug.Log("場景加載完畢后打印數據");// 要想場景加載完畢后也可以繼續執行yield return 后的代碼,需要使用 DontDestroyOnLoad 來保存數據// 注意: DontDestroyOnLoad 這個代碼要放在異步加載場景之前的任意位置,可以是在協程前,可以是在開啟異步加載場景協程前.Debug.Log("加載場景時不銷毀對象");// 場景加載結束,但不急著顯示場景// 場景加載結束, 進度條更新一段// 接著加載場景中的其他信息// 加載怪物-怪物加載完畢進度條更新一段// 動態加載 場景模型// 這時候就認為加載完畢,進度條設置100%, 隱藏進度條}
5.DontDestroyOnLoad
如何保持舊場景指定游戲對象/腳本/組件不被銷毀? 這時候需要使用DontDestroyOnLoad
這個方法來讓我們指定的兌現不銷毀.下面的代碼表示加載場景后銷毀氣其他資源 ,不銷毀當前腳本掛載的游戲物體,自然,當前腳本就不會被銷毀了. DontDestroyOnLoad()
這是一個重要的方法,用于在場景切換時保留特定的游戲對象。這在某些情況下非常有用,比如保留音效管理器或全局配置對象
DontDestroyOnLoad(this.gameObject);
6.自己寫一個場景管理器
為了避免每一次加載場景的時候都要自己手動寫鞋廠或者回調函數,我們可以將這樣的方案構成一個類, 值需要傳入一個場景名稱和一個加載完畢的函數名稱就行. 該類最好可以在任意地方使用,因此,我們可以將場景管理類寫成一個單例. 這和Unity自帶的ScenManager是一個意思,只是自己有了自己自定義的部分. 代碼如下:
public class MySceneManager
{private static MySceneManager instance = new MySceneManager();private MySceneManager() { }public static MySceneManager Instance => instance;/// <summary>/// 外部調用異步加載場景的方法/// </summary>/// <param name="sceneName"> 場景名 </param>public void LoadScene(string sceneName, UnityAction action){AsyncOperation ao = SceneManager.LoadSceneAsync(sceneName);ao.completed += (a) =>{action(); // 調用外部的函數};}
}
測試腳本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class TestMySceneManager : MonoBehaviour
{ void Update(){if(Input.GetKeyDown(KeyCode.A)){MySceneManager.Instance.LoadScene("測試場景", loadCompleteAction);}}private void loadCompleteAction(){Debug.Log("場景加載完畢");}
}
7.測試和優化:
在實現異步加載時,測試不同的場景大小和資源負載非常重要。這有助于發現潛在的性能瓶頸并優化加載過程。
8.資源打包和加載策略:
除了異步加載,合理的資源打包和加載策略也對性能有顯著影響。考慮使用AssetBundles
或Addressables
來優化資源的加載和管理。
9.用戶體驗:
在加載過程中,提供清晰的反饋(如進度條、加載動畫)對于提升用戶體驗至關重要。這可以讓玩家知道游戲正在加載,而不是卡頓或無響應。
最后,確保在實現異步加載時,對Unity的版本和平臺特性有一定的了解,因為它們可能會影響異步加載的行為和性能。