Unity--异步加载场景

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 加载完毕的回到函数

以下是AsyncOperationC#中的代码

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.资源打包和加载策略:

除了异步加载,合理的资源打包和加载策略也对性能有显著影响。考虑使用AssetBundlesAddressables来优化资源的加载和管理。

9.用户体验:

在加载过程中,提供清晰的反馈(如进度条、加载动画)对于提升用户体验至关重要。这可以让玩家知道游戏正在加载,而不是卡顿或无响应。

最后,确保在实现异步加载时,对Unity的版本和平台特性有一定的了解,因为它们可能会影响异步加载的行为和性能。

相关推荐
Artistation Game1 天前
九、怪物行为逻辑
游戏·unity·游戏引擎
百里香酚兰1 天前
【AI学习笔记】基于Unity+DeepSeek开发的一些BUG记录&解决方案
人工智能·学习·unity·大模型·deepseek
妙为1 天前
unreal engine5制作动作类游戏时,我们使用刀剑等武器攻击怪物或敌方单位时,发现攻击特效、伤害等没有触发
游戏·游戏引擎·虚幻·碰撞预设
dangoxiba1 天前
[Unity Demo]从零开始制作空洞骑士Hollow Knight第十三集:制作小骑士的接触地刺复活机制以及完善地图的可交互对象
游戏·unity·visualstudio·c#·游戏引擎
先生沉默先2 天前
使用Materialize制作unity的贴图,Materialize的简单教程,Materialize学习日志
学习·unity·贴图
十画_8242 天前
Visual Studio 小技巧记录
unity·visual studio
red_redemption2 天前
cpp,git,unity学习
git·unity·游戏引擎
tealcwu2 天前
【Unity踩坑】Unity更新Google Play结算库
unity·游戏引擎
先生沉默先2 天前
unity 默认渲染管线材质球的材质通道,材质球的材质通道
unity·游戏引擎·材质
白鹭float.2 天前
【Unity AI】基于 WebSocket 和 讯飞星火大模型
人工智能·websocket·unity