Unity--异步加载场景
异步加载场景其实和异步加载资源是一样的,只是加载的内容比较特殊而已. 也可以将场景视为特殊资源.
1.SceneManager.LoadScene
加载场景的方式,在Unity 中加载场景是通过SceneManager.LoadScene("场景名称");
来实现加载场景,这和UE4中的OpenLevel
也是一样的. 其中SceneManager是untiy中自带的场景管理器,可以用于加载场景,卸载场景等.需要引入using UnityEngine.SceneManagement
才能使用
2.同步加载场景
和资源一样,场景默认是同步加载的,也就是直接使用SceneManager.LoadScene("场景名称")
来实现同步加载. 如果一个场景中的资源比较多,比如:游戏模型,粒子特效等,那么就会导致加载场景时候卡顿,很久才能加载场景.
需要注意的是,它会立即切换到新场景,这可能导致短暂的冻结或卡顿,特别是在加载较大或资源密集的场景时...为了解决这个问题,我们一般使用异步加载. 减轻主线程的压力.
3.异步加载场景
和异步加载资源一样,场景的异步加载也是有两个过程: 加载中与加载完成.
异步加载的重要性 :异步加载(LoadSceneAsync
)允许场景在后台加载,这样主线程可以继续处理其他任务,如更新UI、处理玩家输入等。这对于提高用户体验至关重要,特别是在资源密集型游戏中。
仔细分析就是因为资源过大,内容过多导致加载中的时间过长,我们一般的设计方式就是进度条,加载完毕一段内容,进度条走了20%或者其他.直到加载完毕才走到100%,当然这个进度条有可能是假的.
在Unity中异步加载场景的写法如下:使用异步加载关键 + 加载完毕的回调函数
c#
/// <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#中的代码
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的显示隐藏等.
c#
private void LoadSceneCompleted(AsyncOperation operation)
{
// 场景加载完成后执行的代码
Debug.Log("Scene loaded successfully");
// ... ...
// 在这里可以进行场景初始化,例如查找和初始化游戏对象,设置游戏状态等
}
4.使用协程的方式异步加载场景
利用AsyncOperation operation
的isDone数显来判断是否加载完毕, 如果没有加载完毕,就不可以做一些其他事情,并使用yield return
来等待一段时间,然后继续判断是否加载完毕,代码如下:
c#
/// <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
后的代码就无法执行.因为场景加载好了,切换到了新的场景,旧的场景中的内容会被销毁,也包括我们挂载的脚本
c#
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()
这是一个重要的方法,用于在场景切换时保留特定的游戏对象。这在某些情况下非常有用,比如保留音效管理器或全局配置对象
c#
DontDestroyOnLoad(this.gameObject);
6.自己写一个场景管理器
为了避免每一次加载场景的时候都要自己手动写鞋厂或者回调函数,我们可以将这样的方案构成一个类, 值需要传入一个场景名称和一个加载完毕的函数名称就行. 该类最好可以在任意地方使用,因此,我们可以将场景管理类写成一个单例. 这和Unity自带的ScenManager是一个意思,只是自己有了自己自定义的部分. 代码如下:
c#
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(); // 调用外部的函数
};
}
}
测试脚本:
c#
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的版本和平台特性有一定的了解,因为它们可能会影响异步加载的行为和性能。