一、场景同步加载:回顾与局限性
1. 同步加载的实现代码
Unity 提供SceneManager类管理场景,同步加载仅需一行核心代码:
using UnityEngine.SceneManagement; // 必须引入命名空间
// 同步加载名为"Lesson20Test"的场景
SceneManager.LoadScene("Lesson20Test");
2.同步加载的致命缺点
原理:
Unity 会先删除当前场景的所有对象,再一次性加载下一个场景的所有资源
导致的问题:
- 卡顿明显:若场景资源量大(如复杂场景、大量预制体),加载时间过长,玩家会看到 “卡死” 的画面
- 无进度反馈:加载过程中无法向玩家展示加载进度,容易让玩家误以为游戏崩溃
- 主线程阻塞:加载期间无法执行任何其他逻辑(如 UI 动画、进度条更新)
二、场景异步加载:核心解决方案
原理:
在后台线程加载场景资源,主线程正常执行游戏逻辑,实现 “加载不卡顿、进度可展示” 的效果。
1.关键前提:让加载脚本 “跨场景存活”
在使用异步加载前,必须解决一个关键问题:场景切换时,Unity 会删除当前场景的所有对象。如果处理加载逻辑的脚本依附的对象被删除,加载过程会中断。
解决方案:使用DontDestroyOnLoad方法,让脚本所在的游戏对象在场景切换时不被销毁:
// 让当前脚本依附的游戏对象,在场景切换时保持存活
DontDestroyOnLoad(this.gameObject);
注意:该代码需在场景加载前执行(如Start方法中),且确保该对象没有依赖当前场景的其他资源。
2.方式一:事件回调实现异步加载
通过AsyncOperation的completed事件,可以在场景加载完成后执行回调逻辑。这种方式的优点是写法简单、逻辑清晰,适合仅需在加载完成后处理逻辑的场景。
(1)实现步骤
- 调用
SceneManager.LoadSceneAsync获取异步加载对象AsyncOperation - 为
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)原理
- 调用
SceneManager.LoadSceneAsync发起异步加载,获取AsyncOperation对象 - 通过
yield return null或yield return ao控制协程暂停,避免阻塞主线程 - 在循环中实时检测加载状态(如
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);
}
2259

被折叠的 条评论
为什么被折叠?



