Unity场景异步切换详解

一、场景同步加载:回顾与局限性

1. 同步加载的实现代码

        Unity 提供SceneManager类管理场景,同步加载仅需一行核心代码:

using UnityEngine.SceneManagement; // 必须引入命名空间

// 同步加载名为"Lesson20Test"的场景
SceneManager.LoadScene("Lesson20Test");

2.同步加载的致命缺点

原理:

        Unity 会先删除当前场景的所有对象,再一次性加载下一个场景的所有资源

导致的问题:

  • 卡顿明显若场景资源量大(如复杂场景、大量预制体),加载时间过长,玩家会看到 “卡死” 的画面
  • 无进度反馈加载过程中无法向玩家展示加载进度,容易让玩家误以为游戏崩溃
  • 主线程阻塞加载期间无法执行任何其他逻辑(如 UI 动画、进度条更新)


二、场景异步加载:核心解决方案

原理:

        在后台线程加载场景资源,主线程正常执行游戏逻辑,实现 “加载不卡顿、进度可展示” 的效果。

1.关键前提:让加载脚本 “跨场景存活”

        在使用异步加载前,必须解决一个关键问题:场景切换时,Unity 会删除当前场景的所有对象。如果处理加载逻辑的脚本依附的对象被删除,加载过程会中断。

解决方案:使用DontDestroyOnLoad方法,让脚本所在的游戏对象在场景切换时不被销毁:

// 让当前脚本依附的游戏对象,在场景切换时保持存活
DontDestroyOnLoad(this.gameObject);

注意:该代码需在场景加载前执行(如Start方法中),且确保该对象没有依赖当前场景的其他资源。

2.方式一:事件回调实现异步加载

        通过AsyncOperationcompleted事件,可以在场景加载完成后执行回调逻辑。这种方式的优点是写法简单、逻辑清晰,适合仅需在加载完成后处理逻辑的场景。

(1)实现步骤

  1. 调用SceneManager.LoadSceneAsync获取异步加载对象AsyncOperation
  2. AsyncOperation.completed绑定回调函数(加载完成后自动执行)

(2)代码示例

using UnityEngine;
using UnityEngine.SceneManagement;

public class Lesson20 : MonoBehaviour
{
    void Start()
    {
        // 关键:让脚本所在对象跨场景存活
        DontDestroyOnLoad(this.gameObject);

        // 1. 发起异步加载,返回AsyncOperation对象(管理加载状态)
        AsyncOperation ao = SceneManager.LoadSceneAsync("Lesson20Test");

        // 2. 方式1:使用Lambda表达式绑定回调(适合简单逻辑)
        ao.completed += (a) =>
        {
            Debug.Log("场景加载完成(Lambda回调)");
            // 加载完成后可执行:隐藏进度条、播放音效等
        };

        // 2. 方式2:绑定单独的回调函数(适合复杂逻辑)
        ao.completed += LoadCompleteCallback;
    }

    // 加载完成的回调函数(参数必须是AsyncOperation类型)
    private void LoadCompleteCallback(AsyncOperation ao)
    {
        Debug.Log("场景加载完成(单独函数回调)");
        // 示例:加载完成后销毁当前加载对象(避免内存泄漏)
        Destroy(this.gameObject);
    }
}

(3)事件回调的优缺点

优点缺点
代码简洁,易于理解和维护无法在加载过程中处理逻辑(如实时更新进度条)
无需处理协程的迭代器逻辑仅能在加载完成后触发一次回调

3.方式二:协程实现异步加载

        协程(Coroutine)是 Unity 中处理异步逻辑的强大工具,通过yield return可以暂停和恢复代码执行。使用协程实现异步加载,可以在加载过程中实时处理逻辑(如更新进度条),是游戏开发中的主流方案。

(1)原理

  1. 调用SceneManager.LoadSceneAsync发起异步加载,获取AsyncOperation对象
  2. 通过yield return nullyield return ao控制协程暂停,避免阻塞主线程
  3. 在循环中实时检测加载状态(如ao.isDone),并处理进度更新等逻辑

(2)基础实现:加载状态监测

using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections; // 协程需要引入该命名空间

public class Lesson20 : MonoBehaviour
{
    void Start()
    {
        // 关键:跨场景存活
        DontDestroyOnLoad(this.gameObject);

        // 启动协程,开始异步加载
        StartCoroutine(LoadSceneCoroutine("Lesson20Test"));
    }

    // 协程函数:异步加载场景(返回值必须是IEnumerator)
    IEnumerator LoadSceneCoroutine(string sceneName)
    {
        Debug.Log("开始异步加载场景");

        // 1. 发起异步加载,获取AsyncOperation对象
        AsyncOperation ao = SceneManager.LoadSceneAsync(sceneName);

        // 2. 等待加载完成(yield return ao会暂停协程,直到加载结束)
        yield return ao;

        // 3. 加载完成后执行的逻辑
        Debug.Log("场景加载完成!");
        Destroy(this.gameObject); // 销毁加载对象,避免内存泄漏
    }
}

(3)进阶实现:实时更新加载进度条

        协程的最大优势是可以在加载过程中实时更新进度条。Unity 提供了AsyncOperation.progress属性获取加载进度(范围:0~1),但实际开发中通常需要结合业务逻辑自定义进度,以下是两种常见方案:

方案 1:使用 Unity 内置进度(简单但精度低)

  ao.progress会返回场景加载的实时进度但由于 Unity 内部优化,进度可能不会完全平滑(如最后一步直接从 0.9 跳到 1.0),适合简单场景:

IEnumerator LoadSceneCoroutine(string sceneName)
{
    AsyncOperation ao = SceneManager.LoadSceneAsync(sceneName);

    // 循环检测加载状态,直到加载完成
    while (!ao.isDone)
    {
        // 获取当前加载进度(0~1),可乘以100转为百分比
        float progress = ao.progress;
        Debug.Log($"当前加载进度:{progress * 100:F1}%");

        // 关键:暂停协程,等待下一帧再执行(避免阻塞主线程)
        yield return null;
    }

    // 加载完成后,将进度条顶满(解决最后一步进度不跳满的问题)
    Debug.Log("加载完成!进度:100%");
    Destroy(this.gameObject);
}

方案 2:自定义业务进度(推荐)

        实际游戏中,场景加载往往还包含 “加载怪物资源”“初始化 UI”“加载配置表” 等后续步骤。此时可以根据业务逻辑自定义进度条更新规则,让进度更符合玩家预期:

IEnumerator LoadSceneCoroutine(string sceneName)
{
    // 1. 第一步:加载场景本身(占总进度的40%)
    AsyncOperation ao = SceneManager.LoadSceneAsync(sceneName);
    while (!ao.isDone)
    {
        // 场景加载进度映射为总进度的0~40%
        float totalProgress = ao.progress * 0.4f;
        Debug.Log($"当前总进度:{totalProgress * 100:F1}%");
        yield return null;
    }
    Debug.Log("场景加载完成!总进度:40%");

    // 2. 第二步:加载怪物预制体(占总进度的30%)
    yield return LoadMonsters(); // 假设LoadMonsters是另一个协程
    Debug.Log("怪物加载完成!总进度:70%");

    // 3. 第三步:初始化UI和配置表(占总进度的30%)
    yield return InitUIAndConfig();
    Debug.Log("UI和配置表初始化完成!总进度:100%");

    // 4. 加载全部完成,隐藏进度条
    Debug.Log("所有资源加载完成,进入游戏!");
    Destroy(this.gameObject);
}

// 模拟加载怪物资源的协程
IEnumerator LoadMonsters()
{
    // 模拟加载耗时(实际项目中替换为真实的资源加载逻辑)
    for (int i = 0; i < 10; i++)
    {
        yield return new WaitForSeconds(0.1f); // 每0.1秒加载一次
    }
}

// 模拟初始化UI和配置表的协程
IEnumerator InitUIAndConfig()
{
    // 模拟初始化耗时
    yield return new WaitForSeconds(0.5f);
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值