资源加载与缓存机制
篇章 :02-原理篇 · 基础 阅读时间 :约 40 分钟 前置知识:了解 Unity 基本资源加载方式
一、引言
资源加载与缓存是 Unity 资源管理的核心环节。合理的资源加载策略可以提高游戏启动速度,减少加载等待时间,而有效的缓存机制可以减少重复加载,降低内存占用。本章将深入解析资源加载和缓存的原理和方法,结合 YooAsset 的实际实现进行详细说明。
资源加载是游戏运行时的核心操作,直接影响玩家体验。一个高效的资源加载系统需要平衡加载速度、内存占用和资源复用等多个方面。而缓存机制则是优化资源加载性能的关键手段,通过合理的缓存策略,可以显著减少重复加载,提高游戏流畅度。
二、资源加载
2.1 加载方式
Unity 支持多种资源加载方式:
- 同步加载:阻塞主线程,直到资源加载完成
- 异步加载:不阻塞主线程,在后台加载资源
- 协程加载:使用 Unity 协程进行异步加载
- Task 加载:使用 C# Task 进行异步加载
同步加载详解:
同步加载是最简单的资源加载方式,但它会阻塞主线程,导致游戏卡顿。因此,同步加载只适用于小资源或启动时的资源加载。
csharp
// 同步加载资源
GameObject prefab = Resources.Load<GameObject>("MyPrefab");
异步加载详解:
异步加载不会阻塞主线程,可以在后台加载资源,加载完成后再通知主线程。异步加载适用于大资源或运行时的资源加载。
csharp
// 异步加载资源
AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync(fileData);
yield return request;
AssetBundle bundle = request.assetBundle;
GameObject prefab = bundle.LoadAsset<GameObject>("MyPrefab");
协程加载详解:
协程加载是 Unity 特有的异步加载方式,它可以在协程中等待异步操作完成,代码简洁易读。
csharp
IEnumerator LoadAssetCoroutine()
{
AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync(fileData);
yield return request;
AssetBundle bundle = request.assetBundle;
GameObject prefab = bundle.LoadAsset<GameObject>("MyPrefab");
Instantiate(prefab);
}
Task 加载详解:
Task 加载是 C# 内置的异步加载方式,它支持 async/await 语法,代码更加简洁。
csharp
async Task LoadAssetTask()
{
var bundle = await AssetBundle.LoadFromMemoryAsync(fileData);
var prefab = bundle.LoadAsset<GameObject>("MyPrefab");
Instantiate(prefab);
}
2.2 加载流程
资源加载的完整流程如下:
markdown
资源加载流程
├── 1. 请求加载
│ ├── 解析资源地址
│ └── 查找对应的 Bundle
├── 2. Bundle 加载
│ ├── 检查缓存
│ ├── 从磁盘/网络加载 Bundle
│ └── 解压 Bundle 数据
├── 3. 依赖加载
│ ├── 检查依赖 Bundle 是否已加载
│ └── 加载缺失的依赖 Bundle
├── 4. 资源加载
│ ├── 从 Bundle 中加载资源
│ └── 反序列化资源数据
└── 5. 资源返回
└── 返回加载的资源对象
请求加载详解:
请求加载是资源加载的第一步,需要解析资源地址并查找对应的 Bundle。资源地址可以是资源的完整路径,也可以是自定义的地址。
Bundle 加载详解:
Bundle 加载是从磁盘或网络加载 Bundle 文件的过程。在加载之前,需要先检查缓存,如果缓存中存在则直接从缓存中读取,否则从磁盘或网络加载。
依赖加载详解:
依赖加载是加载 Bundle 所依赖的其他 Bundle 的过程。在加载 Bundle 之前,需要先加载其依赖的 Bundle,确保依赖关系正确。
资源加载详解:
资源加载是从 Bundle 中加载指定资源的过程。在加载资源之前,需要先解压 Bundle 数据,然后将序列化数据反序列化为 Unity 对象。
资源返回详解:
资源返回是将加载的资源返回给调用者的过程。在返回之前,需要确保资源已经正确加载,并且依赖关系已经满足。
2.3 加载性能优化
预加载:
预加载是在需要之前提前加载资源,减少加载等待时间。
csharp
// 预加载资源
Addressables.PreloadAssets<GameObject>("MyPrefab", null, AddressableAssetLoadOptions.Default);
异步加载:
异步加载可以避免阻塞主线程,提升用户体验。
csharp
// 异步加载资源
IEnumerator LoadAssetAsync()
{
var handle = Addressables.LoadAsset<GameObject>("MyPrefab");
yield return handle;
GameObject prefab = handle.Result;
Instantiate(prefab);
}
流式加载:
流式加载可以逐步加载大型资源,避免一次性加载大量资源导致内存溢出。
三、缓存机制
3.1 缓存类型
Unity 支持多种缓存类型:
- 内存缓存:将已加载的资源缓存在内存中
- 磁盘缓存:将已下载的 Bundle 缓存在磁盘中
- AssetBundle 缓存:将已加载的 AssetBundle 缓存在内存中
内存缓存详解:
内存缓存是最常用的缓存类型。当资源被加载后,Unity 会将其缓存在内存中,下次加载时可以直接从缓存中获取,而不需要重新加载。
磁盘缓存详解:
磁盘缓存用于缓存从网络下载的 Bundle 文件。当 Bundle 被下载后,Unity 会将其缓存在磁盘中,下次需要时可以直接从磁盘中读取,而不需要重新下载。
AssetBundle 缓存详解:
AssetBundle 缓存用于缓存已加载的 AssetBundle 文件。当 AssetBundle 被加载后,Unity 会将其缓存在内存中,下次加载时可以直接从缓存中获取,而不需要重新从磁盘或网络加载。
3.2 缓存策略
LRU 缓存策略详解:
LRU(Least Recently Used)缓存策略是最常用的缓存策略。当缓存满时,LRU 策略会淘汰最近最少使用的缓存项。
LRU 缓存实现:
csharp
public class LRUCache<TKey, TValue>
{
private readonly int _capacity;
private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> _cache;
private readonly LinkedList<KeyValuePair<TKey, TValue>> _list;
public LRUCache(int capacity)
{
_capacity = capacity;
_cache = new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>();
_list = new LinkedList<KeyValuePair<TKey, TValue>>();
}
public TValue Get(TKey key)
{
if (_cache.TryGetValue(key, out var node))
{
_list.Remove(node);
_list.AddFirst(node.Value);
_cache[key] = _list.AddFirst(node.Value);
return node.Value.Value;
}
return default;
}
public void Put(TKey key, TValue value)
{
if (_cache.ContainsKey(key))
{
var node = _cache[key];
_list.Remove(node);
}
else if (_cache.Count >= _capacity)
{
var last = _list.Last;
_list.RemoveLast();
_cache.Remove(last.Value.Key);
}
_cache[key] = _list.AddFirst(new KeyValuePair<TKey, TValue>(key, value));
}
}
FIFO 缓存策略详解:
FIFO(First In First Out)缓存策略是最简单的缓存策略。当缓存满时,FIFO 策略会淘汰最早进入缓存的缓存项。
LFU 缓存策略详解:
LFU(Least Frequently Used)缓存策略会淘汰使用频率最低的缓存项。与 LRU 策略不同,LFU 策略关注的是缓存项的使用频率,而不是使用时间。
3.3 缓存管理
缓存大小限制:
需要设置缓存大小限制,避免缓存占用过多内存或磁盘空间。
缓存过期策略:
需要设置缓存过期策略,定期清理过期的缓存项。
缓存一致性:
需要确保缓存的一致性,当资源被更新时,需要清除对应的缓存项。
四、YooAsset 缓存机制
4.1 YooAsset 缓存架构
YooAsset 提供了灵活的缓存机制,支持多种缓存策略:
markdown
YooAsset 缓存架构
├── 内存缓存
│ ├── AssetBundle 缓存
│ ├── 资源对象缓存
│ └── 场景缓存
├── 磁盘缓存
│ ├── Bundle 文件缓存
│ └── 原生文件缓存
└── 缓存管理
├── 缓存大小限制
├── 缓存过期策略
└── 缓存一致性
4.2 YooAsset 缓存 API
csharp
// 创建文件系统参数
var fileSystemParameters = new FileSystemParameters()
{
QueriDownloadAfterUpdate = true,
FileExtensionHandler = new ExtensionModuleFileExtensionHandler(),
ManifestFileName = "yoo_manifest"
};
// 创建缓存文件系统
var cacheFileSystem = new CacheFileSystem(fileSystemParameters);
// 初始化 Package
var package = YooAsset.CreatePackage("DefaultPackage");
var initOp = package.InitializeAsync();
yield return initOp;
4.3 YooAsset 缓存优化
缓存预热:
在启动时预加载常用资源,减少游戏运行时的加载等待时间。
缓存清理:
定期清理不再使用的缓存,释放内存和磁盘空间。
缓存监控:
使用 Profiler 等工具监控缓存使用情况,及时发现缓存问题。
五、总结
资源加载与缓存是 Unity 资源管理的核心环节。合理的资源加载策略可以提高游戏启动速度,减少加载等待时间,而有效的缓存机制可以减少重复加载,降低内存占用。
在实际项目中,建议:
- 使用异步加载:避免阻塞主线程
- 合理使用缓存:充分利用内置缓存机制
- 设置缓存限制:避免缓存占用过多资源
- 监控加载性能:使用 Profiler 监控加载性能
通过深入理解资源加载和缓存的原理,我们可以更好地利用 Unity 的资源管理功能,开发出性能更优、体验更好的游戏。
下一篇 :热更新与版本管理