Unity场景加载(Scene Loading)是Unity引擎中非常核心的功能。虽然Unity的完整C++引擎源码不开源,但通过Unity官方文档、部分开源C#层代码、Unity反编译、以及官方演讲资料,我们可以较为清晰地梳理出Unity场景加载的整体流程。下面我将从高层调用、C#层、C++引擎层、资源管理、异步加载、生命周期回调等角度,详细讲解Unity场景加载的源码流程。
Unity场景加载的常用API有:
SceneManager.LoadScene(string sceneName)
SceneManager.LoadSceneAsync(string sceneName)
Application.LoadLevel
这些API都在C#层的UnityEngine.SceneManagement.SceneManager
类中。
以SceneManager.LoadScene
为例,源码大致如下(可通过反编译UnityEngine.dll获得):
public static void LoadScene(string sceneName, LoadSceneMode mode = LoadSceneMode.Single)
{
if (string.IsNullOrEmpty(sceneName))
throw new ArgumentException("Scene name cannot be null or empty");
// 内部调用
SceneManagerAPI.Internal_SceneManager.LoadScene(sceneName, mode);
}
而SceneManagerAPI.Internal_SceneManager.LoadScene
最终会调用到C++引擎层的内部方法(通过[NativeMethod]
或[MethodImpl(MethodImplOptions.InternalCall)]
)。
C#层的LoadScene
会通过P/Invoke或ICall机制,调用到C++引擎的内部实现,核心函数通常为:
LoadSceneInternal
LoadSceneAsyncInternal
这些函数的主要职责:
查找场景资源
资源解包与反序列化
对象实例化
激活场景
GC与资源管理
LoadSceneMode.Single
,会卸载前一个场景的对象和资源(除非被DontDestroyOnLoad标记)。LoadSceneAsync
会返回一个AsyncOperation
对象,允许你在加载过程中查询进度(progress
)和完成回调(completed
)。allowSceneActivation
属性,允许你手动控制场景激活时机(常用于加载界面)。场景加载过程中,Unity会自动调用以下MonoBehaviour回调:
OnDisable
(卸载前场景对象)OnDestroy
(卸载前场景对象)Awake
(新场景对象)OnEnable
(新场景对象)Start
(新场景对象)OnSceneLoaded
(通过SceneManager.sceneLoaded
事件)DontDestroyOnLoad
标记的对象不会随场景卸载而销毁,会被移动到特殊的“DontDestroyOnLoad”场景中。C#层API调用
↓
查找场景资源(AssetBundle/包体)
↓
加载场景依赖资源
↓
反序列化场景数据
↓
实例化GameObject/Component
↓
激活场景
↓
触发生命周期回调
↓
卸载旧场景(如有)
Unity场景加载流程本质上是:
下面我将继续深入讲解Unity场景加载流程,补充更多底层细节、异步加载机制、资源依赖处理、生命周期事件顺序、以及常见开发问题和优化建议。
.unity
文件,构建后会被序列化为二进制格式,包含场景结构、对象属性、资源引用等。SceneManager.LoadSceneAsync
会返回一个AsyncOperation
对象。AsyncOperation.progress
会在0~0.9之间变化,只有allowSceneActivation = true
时,才会进入最后的激活阶段(progress变为1)。AsyncOperation.allowSceneActivation = false
,让场景加载到90%时暂停,常用于加载界面。allowSceneActivation = true
时,场景才会真正切换和激活。场景切换时,Unity会自动调用一系列MonoBehaviour事件,顺序如下:
OnDisable
、OnDestroy
会被调用(除非被DontDestroyOnLoad标记)。Awake
(所有对象)OnEnable
(所有对象)Start
(所有对象)SceneManager.sceneLoaded
事件(C#事件,可注册回调)SceneManager.UnloadSceneAsync
用于卸载场景。LoadSceneAsync
和allowSceneActivation
,在加载界面时预加载场景,提升体验。Resources.UnloadUnusedAssets()
和GC.Collect()
,释放无用资源。// 1. 开始异步加载
AsyncOperation op = SceneManager.LoadSceneAsync("Level1");
op.allowSceneActivation = false;
// 2. 轮询进度
while (op.progress < 0.9f)
{
// 显示加载进度条
yield return null;
}
// 3. 加载完成,等待激活
// 显示“点击继续”按钮
// 用户点击后
op.allowSceneActivation = true;
// 4. 场景激活,触发生命周期回调
Unity场景加载流程涉及资源定位、依赖加载、反序列化、对象实例化、生命周期回调、资源卸载等多个环节。异步加载可提升体验,但主线程瓶颈和资源管理仍需开发者关注。合理分包、预加载、对象池和内存管理是优化场景加载体验的关键。
下面我将继续深入,补充更多Unity场景加载流程的底层细节,特别是AssetBundle场景加载、C++引擎伪代码、场景激活与切换机制、以及开发中常见的高级用法和注意事项。
AssetBundle.LoadFromFile
或AssetBundle.LoadFromMemory
加载AssetBundle包。AssetBundle.GetAllScenePaths()
获取包内所有场景路径。SceneManager.LoadSceneAsync(scenePath)
加载AssetBundle中的场景。注意:
Assets/xxx/xxx.unity
的形式,不能用场景名。// 1. 加载依赖AssetBundle
AssetBundle depBundle = AssetBundle.LoadFromFile(depPath);
// 2. 加载主场景AssetBundle
AssetBundle sceneBundle = AssetBundle.LoadFromFile(scenePath);
// 3. 获取场景路径
string[] scenePaths = sceneBundle.GetAllScenePaths();
string scenePath = scenePaths[0];
// 4. 异步加载场景
AsyncOperation op = SceneManager.LoadSceneAsync(scenePath, LoadSceneMode.Single);
虽然Unity C++源码不开源,但根据官方资料和反编译信息,可以推测大致流程如下:
// 伪代码:场景加载主流程
void LoadSceneInternal(string scenePath, LoadSceneMode mode)
{
// 1. 定位场景资源
SceneFile* sceneFile = ResourceManager::FindScene(scenePath);
// 2. 加载依赖资源
ResourceManager::LoadDependencies(sceneFile);
// 3. 反序列化场景数据
SceneData* sceneData = SceneDeserializer::Deserialize(sceneFile);
// 4. 实例化GameObject和Component
for (auto& objData : sceneData->objects)
{
GameObject* go = ObjectFactory::Create(objData);
SceneManager::AddToScene(go);
}
// 5. 激活场景
SceneManager::SetActiveScene(sceneData);
// 6. 卸载旧场景(如有)
if (mode == LoadSceneMode::Single)
SceneManager::UnloadPreviousScene();
}
SceneManager.SetActiveScene(Scene scene);
DontDestroyOnLoad
或脚本通信实现交互。LoadSceneAsync
提前加载场景,等到需要时再激活(allowSceneActivation = false
)。SceneManager.sceneUnloaded
和sceneLoaded
事件实现。OnDisable
、OnDestroy
Awake
OnEnable
Start
SceneManager.sceneLoaded
事件SceneManager.sceneUnloaded
事件Resources.UnloadUnusedAssets()
释放未被引用的资源。AssetBundle.Unload(false)
),否则会占用内存。DontDestroyOnLoad
对象不会自动释放,需手动管理。Unity场景加载流程涵盖了资源定位、依赖加载、反序列化、对象实例化、场景激活、生命周期回调、资源卸载等多个环节。AssetBundle场景加载需特别注意依赖管理和资源释放。多场景协作、异步加载、场景预加载等高级用法可大幅提升项目体验和性能。
下面我将继续深入,补充更多Unity场景加载相关的底层机制、实际开发中的高级技巧、以及一些常见的面试/笔试考点和源码分析思路。
Awake
,再调用OnEnable
,最后调用Start
。异步加载场景时,Unity内部大致分为以下几个阶段:
AsyncOperation.progress
在0~0.9之间表示资源加载和反序列化阶段。allowSceneActivation
为false时)。progress
才会变为1。LoadSceneMode.Additive
加载多个场景,实现地图分块、UI与主场景分离等。FindObjectOfType
、事件系统、单例、或DontDestroyOnLoad
对象实现场景间通信。SceneManager.UnloadSceneAsync
卸载指定场景,只销毁该场景的对象,不影响其他场景。虽然Unity底层C++源码不开源,但可以通过以下方式分析:
Shader.WarmupAllShaders()
等API。Resources.UnloadUnusedAssets()
和GC.Collect()
,释放未被引用的资源。sceneLoaded
和sceneUnloaded
事件,控制动画时机。// 1. 预加载下一个场景
AsyncOperation op = SceneManager.LoadSceneAsync("NextScene", LoadSceneMode.Additive);
op.allowSceneActivation = false;
// 2. 监控加载进度
while (op.progress < 0.9f)
{
// 更新进度条
yield return null;
}
// 3. 播放切换动画
PlayTransitionAnimation();
// 4. 激活新场景
op.allowSceneActivation = true;
// 5. 卸载旧场景
SceneManager.UnloadSceneAsync("CurrentScene");
Unity场景加载流程涉及资源定位、依赖加载、反序列化、对象实例化、场景激活、生命周期回调、资源卸载等多个环节。异步加载、分块加载、多场景协作、资源管理等高级用法是大型项目开发的关键。理解底层机制、合理优化资源和流程,是提升项目性能和体验的核心。