本文介绍了 Unity 常用四种默认路径,以及 AssetDataBase、Resources、AssetBundle 和目前最新的 Addressable 四种资源管理方式
文中所有 API 均以版本 2019.3 为准
本文原地址:Unity学习—资源管理概览
官方文档
只读,Editor 可读写
游戏数据相对路径,即游戏安装路径,PC 上路径会使用 '/' 分割文件夹
<项目根路径>/Assets
/Contents
//Data
<可执行数据文件夹路径>
官方文档
可读写,用于持久化数据存储,在 iOS 和 Android 平台该路径指向设备的公共路径,该目录不会随 App 升级而删除,但可被用户直接删除
persistentDataPath
的路径由Bundle Identifier
生成的 GUID 组成,只要Bundle Identifier
不变,路径不变
iOS 会自动将 persistentDataPath 路径下的文件备份到 iCloud
%userprofile%AppDataLocalPackagesLocalState
/var/mobile/Containers/Data/Application//Documents
/storage/emulated/0/Android/data//files
该路径由 android.content.Context.getExternalFilesDir 获得,部分机型该路径会指向 SD 卡 ~/Library/Application Support//
,旧版本还可能为 ~/Library/Caches
或 ~/Library/Application Support/unity.company name.product name
,Unity 会查询并使用以上路径中最早的路径 官方文档 官方手册
只读,Editor 可读写
流数据存储的相对路径,该目录下 Asset 在 Unity 编译时不会被 Unity 打包,使其在运行时可直接通过路径获取,可将资源放入 Assets 目录下任何名为 StreamingAssets
文件夹
StreamingAssets
中资源可使用 I/O 读取,但 WebGL 和 Android 平台下该路径为 URL,不支持直接获取,因此需使用 UnityWebRequest
获取。若其他平台使用 UnityWebRequest
获取,则需在路径前加上"file://"
,如 "file://" + Application.streamingAssetsPath + "/file.mp4"
Application.dataPath + "/StreamingAssets"
Application.dataPath + "/Resources/Data/StreamingAssets"
Application.dataPath + "/Raw"
"jar:file://" + Application.dataPath + "!/assets"
(压缩后的 APK/JAR 文件) 可读写,临时数据和缓存路径,应用更新或覆盖安装时不会被清除,手机空间不足时才可能会被系统清除
说明
推荐官方教程
AssetDataBase 可在 Editor 环境下对项目 Asset 进行增删改查等操作(可实现与 Unity 编辑器顶部工具栏 Assets 选项下基本相同的功能),使用方法可参考官方手册 接口文档
接口文档
可在项目 Assets 目录下任意位置创建Resources
文件夹,打包时 Unity 会整合所有位于Resources
文件夹的 Asset 及其依赖,并生成一个只读的 resources.assets
资产文件,对于 Resources 目录中在游戏中被直接引用的资产,则会被另外打包到 sharedassets0.assets
中
官方的建议是不使用 Resources,有以下几点原因:
适合使用 Resources 的场景:
项目编译时会将所有 Resources 目录下 Asset 和 Object 合并到一个序列化的 resources.assets
文件,该文件中还包含了类似于 AssetBundle 的元数据(metadata)和索引信息,该信息包含了由对象名称转化得到的 GUID 和 Local ID 的查找树和对象位于序列化文件中的字节偏移量
对于大部分平台,查找树为时间复杂度为 O(n log(n)) 的平衡查找树,随着 Resources 中对象的增加,索引加载时间增长速度将超过线形增长速度
Resources 系统在 Splash 展示时初始化,该过程不可跳过,经观察在低端设备上,10000 个 Asset 文件就会导致该过程长达数秒,哪怕很多对象在第一个场景没用到也会被加载
void Start()
{
//Load a text file (Assets/Resources/Text/textFile01.txt)
var textFile = Resources.Load("Text/textFile01");
//Load text from a JSON file (Assets/Resources/Text/jsonFile01.json)
var jsonTextFile = Resources.Load("Text/jsonFile01");
//Then use JsonUtility.FromJson() to deserialize jsonTextFile into an object
//Load a Texture (Assets/Resources/Textures/texture01.png)
var texture = Resources.Load("Textures/texture01");
//Load a Sprite (Assets/Resources/Sprites/sprite01.png)
var sprite = Resources.Load("Sprites/sprite01");
//Load an AudioClip (Assets/Resources/Audio/audioClip01.mp3)
var audioClip = Resources.Load("Audio/audioClip01");
}
官方手册 接口文档
AssetBundle 是外部资产的集合,可独立于 Unity 构建过程外,是 Unity 更新非代码内容的主要工具,经常置于服务器上供用户终端动态获取;AssetBundle 使开发者可以提交更小的应用包,最小化运行时内存压力,使终端可以选择性加载优化内容
该部分仅简单介绍 AssetBundle,更多信息可见 Unity学习—AssetBundle
除此之外,Unity 还提供了 AssetImporter.assetBundleName
和AssetImporter.assetBundleVariant
等接口将资源分配到 AssetBundle
2. 然后即可构建 AssetBundle 了,使用BuildPipeline.BuildAssetBundles()
即可构建 AssetBundle,其中可配置参数输出路径、构建选项、目标平台
3. 或者可以使用 Unity 官方提供的工具管理 AssetBundle AssetBundles-Browser 官方手册
[MenuItem("Build Asset Bundles/Normal")]
static void BuildABsNone()
{
BuildPipeline.BuildAssetBundles("Assets/MyAssetBuilds", BuildAssetBundleOptions.None, BuildTarget.StandaloneOSX);
}
根据实际情况选择 AssetBundle 时随项目打包,或后续通过网络下载,一般移动平台由于初始安装大小和下载限制,会选择安装后下载,而主机和电脑则随项目打包
随项目打包有两个主要原因:
一般推荐使用 UnityWebRequest
下载 AssetBundle,若下载包为 LZMA 压缩,则缓存的为未压缩或使用 LZ4 重压缩的内容,若缓存已满,则 Unity 会删除最近最少使用的 AssetBundle
Unity 内置的 AssetBundle 缓存系统用于缓存 UnityWebRequestAssetBundle.GetAssetBundle
下载的包,缓存仅以名称作为唯一标识。另外可通过重载方法可传入版本号(开发者自己管理版本号),缓存系统会比对版本号,选择匹配版本或下载新包
缓存系统可通过 Caching.expirationDelay 和 Caching.maximumAvailableDiskSpace 修改最小未使用过期时间和最大缓存空间,当缓存文件在过期时间内没被打开过即被删除,或缓存空间不足,则优先删除最近最少打开的缓存
IEnumerator GetText()
{
using (UnityWebRequest uwr = UnityWebRequestAssetBundle.GetAssetBundle("http://www.my-server.com/mybundle"))
{
yield return uwr.SendWebRequest();
if (uwr.isNetworkError || uwr.isHttpError)
{
Debug.Log(uwr.error);
}
else
{
// Get downloaded asset bundle
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(uwr);
}
}
}
有四种不同的 API 用于加载 AssetBundle,但每个 API 的行为随压缩算法和平台而不同
IEnumerator LoadFromMemoryAsync(string path)
{
AssetBundleCreateRequest createRequest = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path));
yield return createRequest;
AssetBundle bundle = createRequest.assetBundle;
var prefab = bundle.LoadAsset("MyObject");
Instantiate(prefab);
}
该方法可高效地从硬盘加载未压缩或 LZ4 压缩的 Assetbundle,加载 LZMA 压缩包时会先解压再加载到内存
public class LoadFromFileExample : MonoBehaviour {
function Start() {
var myLoadedAssetBundle
= AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "myassetBundle"));
if (myLoadedAssetBundle == null) {
Debug.Log("Failed to load AssetBundle!");
return;
}
var prefab = myLoadedAssetBundle.LoadAsset.("MyObject");
Instantiate(prefab);
}
}
旧方法,已抛弃
先使用UnityWebRequest.GetAssetBundle
创建请求,再将请求传入DownloadHandlerAssetBundle.GetContent(UnityWebRequest)
,下载完成后可像AssetBundle.LoadFromFile
一样,直接使用 assetBundle 对象
该方法使开发者更灵活处理下载数据,选择临时存储或长期缓存,避免不必要的内存使用。同时,由于是原生代码,没有托管堆栈扩展风险,DownloadHandler 也不会保留下载数据,进一步减少了内存开销
LZMA 压缩包会在下载时解压,并以 LZ4 重新压缩缓存,可调用 Caching.CompressionEnabled 修改
IEnumerator InstantiateObject()
{
string uri = "file:///" + Application.dataPath + "/AssetBundles/" + assetBundleName;
UnityEngine.Networking.UnityWebRequest request
= UnityEngine.Networking.UnityWebRequest.GetAssetBundle(uri, 0);
yield return request.Send();
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
GameObject cube = bundle.LoadAsset("Cube");
GameObject sprite = bundle.LoadAsset("Sprite");
Instantiate(cube);
Instantiate(sprite);
}
官方推荐尽量使用 AssetBundle.LoadFromFile
,该 API 在速度、磁盘使用和运行时内存使用方面都最高效;需要下载则使用 UnityWebRequest
同步异步加载 Asset 一共有六种 API 可使用,同步方法一定比对应的异步方法快至少一帧
LoadAllAssets
适合加载包中大部分或所有独立 Unity 对象时使用,相较于多次重复调用另外两种 API,LoadAllAssets
速度要稍快一点。因此当 Asset 数量巨大且一次性需要加载的 Asset 少于 2/3 的时候,建议将 AssetBundle 拆分成多个小包体,再使用LoadAllAssets
加载
LoadAssetWithSubAssets
适合需要加载的对象内嵌了其他对象的情况,若加载对象均来自于一个 Asset 且包中有许多其他无关对象,则使用该 API
其他情况均用LoadAsset (LoadAssetAsync)
Unity 对象加载时在主线程执行,对象数据是在工作线程 worker thread,任何线程不敏感的操作都在工作线程执行
异步加载时会根据时间片限制每帧加载多个对象,自 Unity 5.3 后,对象加载就并行化了。多个对象在工作线程被反序列化、处理和集成,当对象加载完成,则触发 Awake 回调
同步加载方法 AssetBundle.Load
会暂停主线程知道加载完成,它们还将加载过程进行时间切片,以使对象集成所占用的帧时间不超过特定的毫秒数,该值可通过Application.backgroundLoadingPriority
设定
在其他因素相同的情况下,异步加载方法的调用到加载对象可用之间最小有一帧延迟,导致异步加载方法比同步方法执行所需时间更长
根据运行环境可以使用两个不同的 API 自动追踪 AssetBundle 之间的依赖。Editor 环境下,可使用AssetDatabase
查询依赖,使用AssetImporter
访问和修改 AssetBundle 的分配和依赖;运行时,可以通过基于 ScriptableObject 的 AssetBundleManifest
API 加载在 AssetBundle 构建期间生成的依赖项信息
当一个对象所在的 AssetBundle 被加载时,该对象就被分配了一个唯一的有效实例 ID,因此 AssetBundle 的加载顺序并不重要,重要的是在加载该对象本身之前,要优先把所有包含其依赖对象的 AssetBundle 加载好。
Unity 不会自动加载子 AssetBundle,具体可详见手册,例:
AssetBundle 1 中的 Material A 依赖于 AssetBundle 2 中的 Texture B,若要正常加载,与 AssetBundle 1 和 2 的加载顺序无关,但一定要保证加载 Material A 时,AssetBundle 2 已加载
在构建 AssetBundle 时,Unity 创建一个包含每一个 AssetBundle 依赖信息的类型为 AssetBundleManifest 的序列化对象,该文件存在一个与其他 AssetBundle 在同一打包路径下的单独的 AssetBundle 中,且与父层文件夹名相同
有两种 API 查询依赖
因该 API 会生成字符串数组,所以应尽量少用,且避免性能高峰时使用
官方建议,大部分场合下,在进入性能需求高的场景前,尽可能多地加载对象,尤其对于移动平台这种,访问本地存储慢,加载卸载对象引起内存流失会触发垃圾回收的平台
依据资源在项目功能块的使用位置,如 UI、角色、环境和其他在生命周期中常出现的内容等分包
该分包方式适用于制作 DLC,可以只下载单个实体而无需下载无变化的资源,其关键点在于需要开发者清楚了解每个打包的资源所要用到的时机和位置
该方式适用于针对多平台分包,例如音频文件的压缩设置在 Windows 和 Mac OS 平台一样,另外由于纹理压缩格式和设置等改变频率远低于脚本和预设体,使用该分配方式可以使 AssetBundle 兼容更多的 Unity 版本
并发内容分包可理解为以关卡为分组依据,将一个关卡内独有的角色、纹理、音乐等需要在同一时机加载的内容分为一包
Addressable 系统为 Unity 新推出的资源管理系统,整合了 Unity 直接引用,Resources 和 AssetBundle 全部三种资源加载方式。通过可寻址资产的方式,便捷地实现了内容包的创建和部署。Addressable 系统使用异步加载的方式实现从任何位置加载任何依赖项,使得任何引用方式都更加便捷动态化
注意:需Unity 2018.3 及其以后版本
Addressable 由两个包组成,Addressable Assets package(主要功能) 和 Scriptable Build Pipeline package(依赖项)
2. 安装完成后 Addressable 的主要功能都可在顶部工具栏 Window -> Asset Management -> Addressables 中找到
3. 打开Group,创建新的配置,项目 Assets 目录下会自动创建一个 AddressableAssetData 目录,无需直接改动该目录
AddressableAssetSettings.BuildPlayerContent()
4. (可选) 更改资源名,为资源添加 Label
5. 加载 Addressable Asset
Addressables.LoadAssetAsync(string)
异步加载资源对象Addressables.InstantiateAsync(string)
场景中创建对象 AssetReference
成员变量,在 Inspector 可选择 AssetReference 引用的资源对象public class AddressablesExample : MonoBehaviour {
GameObject myGameObject;
...
Addressables.LoadAssetAsync("AssetAddress").Completed += OnLoadDone;
}
private void OnLoadDone(UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle obj)
{
// In a production environment, you should add exception handling to catch scenarios such as a null result.
myGameObject = obj.Result;
}
}
Unity读取内部、外部资源详解
Unity资源管理
The Addressable Asset System 正式版应用