Unity 中的动态资源加载
前言
游戏中资源的状态可以分为三种:
- 状态1: 仅在磁盘中.
- 状态2: 被读取到内存中, 但是游戏此时并没有引用此资源.
- 状态3: 被读取到内存中, 并且游戏此时正在引用此资源.
Unity 有两种模式: 编辑器模式和运行模式. 两种模式下都可以实现动态资源加载.
编辑模式
编辑模式下可以使用 AssetDatabase 类来实现资源的动态加载.
AssetDatabase.LoadAssetAtPath(filePath);
由于 Unity 资源的根目录为 Assets, 因此 filePath 参数必须以 Assets/ 开头, 另外需要加后缀名. 通过这个方法就可以加载指定 Assets 路径下的资源, 资源加载后便处于状态 2 了, 如果紧接着使用了此资源, 资源状态就变成了状态 3.
如果从资源目录中读取了一个 Prefab 资源, 运行模式下可以使用 GameObject.Instantiate()
来实例化 Prefab, 但是编辑模式下怎么实例化 Prefab 呢? 可以使用 PrefabUtility.InstantiatePrefab()
方法在编辑器模式下实例化 Prefab.
另外在编辑模式下使用以下方法来更新或者创建新的 Prefab, 需要传入游戏物体, 保存路径等信息.
- PrefabUtility.SavePrefabAsset()
- PrefabUtility.SaveAsPrefabAsset()
- PrefabUtility.SaveAsPrefabAssetAndConnect()
如果想要销毁一个游戏对象, 运行模式下可以使用 Destroy();
方法, 但是编辑器模式下怎么销毁一个游戏对象呢? 编辑模式下只能使用 Object.DestroyImmediate(Selection.activeObject, true);
来销毁游戏对象, 第二个参数决定是否卸载游戏物体引用的资源.
- 执行
Object.DestroyImmediate(go, false);
之后资源就变成状态 2 了. - 执行
Object.DestroyImmediate(go, false);
之后资源就变成状态 1 了.
运行模式
运行模式下实现资源的动态加载有两种方式: Resources 和 AssetsBundle.
Resources 实现运行时资源的动态加载
Unity 在发布打包的时候会自动排除掉没有引用的资源, 只有 Resources 和 StreamingAssets 文件夹中的资源无论是否被引用都会被打包.
另外场景中如果直接引用了 Resources 中的资源, 打包的时候 Resources 文件夹中的资源会被场景和 Resources 重复打包成两份.
基于上述两个原因约定:
- Resources 文件夹只能存放运行时动态加载的资源
- Scene 不能直接引用 Resources 中的资源
Resources.Load(fileName)
可以实现在运行模式下动态加载 Resources 文件夹下的资源. 其中 fileName 参数必须是 Resources 文件夹的相对路径, 且不能带有后缀名.
运行模式下删除对象的方法:
1 | // 立即删除游戏对象 |
运行模式下 Resources 卸载资源的方式:
- 使用
Resources.UnloadAsset(go);
卸载内存中指定游戏物体所引用的资源. - 使用
Resources.UnloadUnusedAssets();
卸载内存中所有未被引用的资源. 另外这是一个异步进程, 可以在 Update 中使用isDone
来判断是否执行完毕.
下面是推荐的 Resources 卸载资源的工具代码:
1 | using System; |
AssetBundle 实现运行时资源的动态加载
要想使用 AssetBundle 的方式实现动态资源加载, 首先需要将资源打包成一个 AssetBundle 包, 打包之前要对待打包资源进行依赖设置, 设置依赖的方式有两种:
在 Inspector 面板的最下方设置, 不推荐使用.
选中所有需要打入 AssetBundle 包的资源, 在这些资源的 Inspector 面板中设置 AssetBundle 的名称和后缀名.
对于会被重复依赖的贴图材质等资源需要进行单独的 AssetBundle 名称以及后缀名设置, 不设置的话这些被依赖的资源会被重复打包, 浪费资源.
直接在一个脚本中设置好需要打包的资源以及他们的依赖关系.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var builds = new List<AssetBundleBuild> {
new AssetBundleBuild() {
assetBundleName = "prefab",
assetNames = new[] {
"Assets/Resources/Materials/1.prefab",
"Assets/Resources/Materials/2.prefab",
"Assets/Resources/Materials/3.prefab"
}
},
new AssetBundleBuild() {
assetBundleName = "material",
assetNames = new[] {
"Assets/Resources/Materials/ground.mat"
}
}
};
之后在编辑器模式的脚本中调用构建管线来创建 AssetBundle, 需要的参数有: 输出路径, 压缩选项, 目标平台.
BuildPipeline.BuildAssetBundles(outPath, BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.StandaloneWindows);
下面是推荐的输出路径 outPath 构建代码.
1 |
|
[注] 推荐的代码中使用的路径是 Assets 根目录下的 StreamingAssets 文件夹, 因为这个文件夹中的所有资源都会直接打包并发布, 不会进行任何的压缩以及改变, 适合游戏中长期使用且不随版本变化的资源.
AssetBundle 资源包已经打好了, 那 Unity 怎么读取这种资源包呢?
因为 AssetBundle 包体之间是具有依赖关系的, 因此在读取可能具有依赖关系的资源之前, 应该先将这个包体所依赖的所有包体全部读取出来, 然后再对需要使用的资源进行处理.
假设需要加载一个名称为 abPre 的包体, 那么首先应该读取这个包体的依赖关系, 所有的依赖关系均在 StreamingAssets 这个 AssetBundle 中, 依赖关系的文件名为: AssetBundleManifest.
1 | var assetBundlePath = Application.streamingAssetsPath; |
abPre 包体所依赖的包已经全部使用 AssetBundle.LoadFromFile() 加载完毕了, 接下来就可以加载 adPre 包体了, 加载完之后便可以读取 abPre 包体中的资源了. 假设需要读取 adPre 包体中的一个名为 myCube 的预制体, 则:
1 | var assetBundle = AssetBundle.LoadFromFile(Path.Combine(assetBundlePath, "abPre")); |
之后可以使用 Instantiate(go);
来实例化资源.
使用完之后需要卸载资源, 卸载 AssetBundle 中的资源需要使用 AssetBundle.UnloadAllAssetBundles(false);
方法. 其中的参数 unloadAllObjects:
- false: 只卸载 AssetBundle 对象, 不卸载资源对象.
- true: 同时卸载 AssetBundle 对象和资源对象.