Unity 的 AssetBundle 系统提供了多种加载方式,以满足不同场景下的资源管理和性能需求。
同步加载(LoadFromFile)
同步加载使用 AssetBundle.LoadFromFile 方法从文件系统中直接加载 AssetBundle。这种方式会阻塞主线程,直到加载完成。
基本用法
// 同步加载
AssetBundle bundle = AssetBundle.LoadFromFile(path);
使用场景
- 小型 AssetBundle:适用于加载速度快、不会导致明显卡顿的资源。
- 预加载:在游戏启动或场景切换时加载必要的资源。
- 测试和调试:开发阶段快速验证资源加载。
优缺点
- 优点:
- 实现简单,代码量少。
- 加载速度快,适合小型资源。
- 缺点:
- 阻塞主线程,可能导致游戏卡顿。
- 不适合加载大型 AssetBundle。
应用建议
- 建议在加载小型资源或加载屏幕期间使用,避免在游戏运行时加载大型 AssetBundle,以免影响用户体验。
异步加载(LoadFromFileAsync)
异步加载使用 AssetBundle.LoadFromFileAsync 方法,加载过程在后台进行,不会阻塞主线程。返回一个 AssetBundleCreateRequest 对象,可通过协程或回调监控加载进度。
基本用法
// 异步加载
AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(path);
yield return request;
AssetBundle bundle = request.assetBundle;
使用场景
- 大型 AssetBundle:加载大文件时避免游戏卡顿。
- 后台加载:在游戏运行时动态加载资源。
- 加载进度显示:需要展示加载进度条的场景。
优缺点
- 优点:
- 不阻塞主线程,保持游戏流畅。
- 支持加载进度监控。
- 缺点:
- 实现相对复杂,需要处理异步逻辑。
- 加载速度可能略慢于同步加载。
应用建议
- 推荐在加载大型资源时使用,结合协程或回调函数处理加载完成后的逻辑,是游戏中常用的加载方式。
从内存加载(LoadFromMemory)
从内存中的字节数组加载 AssetBundle,使用 AssetBundle.LoadFromMemory 方法。通常用于从网络下载的字节数据直接加载。
基本用法
// 同步加载
byte[] bundleData = GetBundleBytes(); // 从某处获取字节数据
AssetBundle bundle = AssetBundle.LoadFromMemory(bundleData);
// 异步加载
AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync(bundleData);
yield return request;
AssetBundle bundle = request.assetBundle;
使用场景
- 网络下载后处理:下载完成后立即加载
- 加密资源:解密后的数据直接加载
- 内存缓存:将常用Bundle缓存在内存中
- 自定义存储:从数据库或特殊格式中读取
优缺点
- 优点:
- 灵活性高:可以从任何数据源加载
- 支持加密:可以对Bundle数据进行加密处理
- 无文件依赖:不需要实际的文件系统
- 即时加载:数据已在内存中,加载速度快
- 缺点:
- 内存占用大:需要同时存储原始数据和Bundle
- GC压力:大量字节数组可能触发垃圾回收
- 不支持大文件:受限于可用内存大小
- 数据拷贝开销:需要完整拷贝Bundle数据
应用建议
- 适用于需要从网络下载并立即加载的场景,注意内存管理以避免泄漏。
应用示例
public class MemoryBundleLoader : MonoBehaviour
{
private Dictionary<string, byte[]> bundleCache = new Dictionary<string, byte[]>();
// 加密Bundle加载
public IEnumerator LoadEncryptedBundle(string bundleName, string encryptionKey)
{
// 从网络或本地获取加密数据
byte[] encryptedData = yield return DownloadEncryptedBundle(bundleName);
// 解密数据
byte[] decryptedData = DecryptBundle(encryptedData, encryptionKey);
// 从内存加载
AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync(decryptedData);
yield return request;
if (request.assetBundle != null)
{
Debug.Log($"Encrypted bundle loaded: {bundleName}");
}
// 清理敏感数据
Array.Clear(encryptedData, 0, encryptedData.Length);
Array.Clear(decryptedData, 0, decryptedData.Length);
}
// 内存缓存系统
public IEnumerator PreloadBundleToMemory(string bundleName)
{
if (bundleCache.ContainsKey(bundleName))
yield break;
string path = GetBundlePath(bundleName);
byte[] bundleData = File.ReadAllBytes(path);
bundleCache[bundleName] = bundleData;
Debug.Log($"Bundle cached to memory: {bundleName}");
}
public AssetBundle LoadFromCache(string bundleName)
{
if (bundleCache.ContainsKey(bundleName))
{
return AssetBundle.LoadFromMemory(bundleCache[bundleName]);
}
return null;
}
}
从网络加载(UnityWebRequestAssetBundle)
使用 UnityWebRequestAssetBundle 从 URL 下载并加载 AssetBundle,集成了网络请求和资源加载,适合热更新场景。
基本用法
public IEnumerator LoadBundleFromWeb(string url)
{
UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(url);
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
// 使用bundle...
}
else
{
Debug.LogError($"Download failed: {request.error}");
}
}
使用场景
- 热更新:从服务器下载最新资源
- 按需加载:用户触发时才下载特定内容
- CDN分发:利用CDN加速资源分发
- A/B测试:动态加载不同版本的资源
优缺点
- 优点:
- 支持网络协议:HTTP/HTTPS等标准协议
- 内置缓存:自动处理HTTP缓存机制
- 进度监控:可以获取下载进度
- 错误处理:完善的网络错误处理机制
- 缺点:
- 网络依赖:需要稳定的网络连接
- 下载延迟:首次加载需要等待下载完成
- 带宽消耗:消耗用户流量
- 缓存管理:需要处理缓存清理和版本控制
应用建议
- 在热更新系统中使用,建议结合版本管理和哈希校验,确保加载正确的资源,同时保证网络请求的稳定性。
应用示例
public class WebBundleLoader : MonoBehaviour
{
[System.Serializable]
public class DownloadProgress
{
public float progress;
public long downloadedBytes;
public long totalBytes;
public string status;
}
public IEnumerator LoadBundleWithProgress(string url, System.Action<DownloadProgress> onProgress)
{
DownloadProgress progress = new DownloadProgress();
using (UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(url))
{
// 设置超时时间
request.timeout = 30;
// 开始下载
request.SendWebRequest();
// 监控下载进度
while (!request.isDone)
{
progress.progress = request.downloadProgress;
progress.downloadedBytes = (long)(request.downloadedBytes);
progress.status = $"Downloading... {progress.progress * 100:F1}%";
onProgress?.Invoke(progress);
yield return null;
}
// 检查结果
if (request.result == UnityWebRequest.Result.Success)
{
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
progress.status = "Download completed";
onProgress?.Invoke(progress);
// 使用bundle...
}
else
{
progress.status = $"Download failed: {request.error}";
onProgress?.Invoke(progress);
}
}
}
// 带重试机制的下载
public IEnumerator LoadBundleWithRetry(string url, int maxRetries = 3)
{
int retryCount = 0;
while (retryCount < maxRetries)
{
using (UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(url))
{
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);
Debug.Log($"Bundle downloaded successfully: {url}");
yield break;
}
else
{
retryCount++;
Debug.LogWarning($"Download attempt {retryCount} failed: {request.error}");
if (retryCount < maxRetries)
{
yield return new WaitForSeconds(Mathf.Pow(2, retryCount)); // 指数退避
}
}
}
}
Debug.LogError($"Failed to download bundle after {maxRetries} attempts: {url}");
}
}
流式加载(LoadFromStream)
从流中加载 AssetBundle,使用 AssetBundle.LoadFromStream 方法,支持从文件流或网络流加载,适合分块加载或流式传输。
基本用法
public IEnumerator LoadBundleFromStream()
{
// 创建文件流
FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read);
// 异步加载
AssetBundleCreateRequest request = AssetBundle.LoadFromStreamAsync(stream);
yield return request;
AssetBundle bundle = request.assetBundle;
// 注意:stream需要保持打开状态直到bundle卸载
}
使用场景
- 大文件分块加载:逐步加载大型Bundle
- 自定义数据源:从数据库或网络流加载
- 内存控制:精确控制内存使用
- 特殊格式处理:处理压缩或加密的流数据
优缺点
- 优点:
- 内存效率:只加载需要的部分到内存
- 支持大文件:适合处理超大Bundle
- 灵活的数据源:可以从任何Stream加载
- 精确控制:可以控制加载的时机和方式
- 缺点:
- 复杂性高:需要管理Stream的生命周期
- 平台限制:某些平台可能不支持
- 调试困难:Stream相关的问题不容易定位
- 性能开销:可能比直接文件加载慢
应用建议
- 适用于需要流式加载大型资源的场景,确保流的稳定性和错误处理机制。
应用示例
public class StreamBundleLoader : MonoBehaviour
{
private Dictionary<AssetBundle, Stream> activeStreams = new Dictionary<AssetBundle, Stream>();
// 分块加载大文件
public IEnumerator LoadLargeBundleFromStream(string path)
{
FileStream stream = null;
try
{
stream = new FileStream(path, FileMode.Open, FileAccess.Read);
// 检查文件大小
long fileSize = stream.Length;
Debug.Log($"Loading large bundle: {fileSize / (1024 * 1024)}MB");
// 异步加载
AssetBundleCreateRequest request = AssetBundle.LoadFromStreamAsync(stream);
// 显示加载进度(这里是模拟,实际Stream加载没有内置进度)
float startTime = Time.time;
while (!request.isDone)
{
float elapsed = Time.time - startTime;
Debug.Log($"Loading... {elapsed:F1}s");
yield return null;
}
if (request.assetBundle != null)
{
// 保存Stream引用,Bundle卸载前不能关闭
activeStreams[request.assetBundle] = stream;
Debug.Log("Large bundle loaded from stream");
}
else
{
stream?.Close();
}
}
catch (System.Exception e)
{
Debug.LogError($"Failed to load bundle from stream: {e.Message}");
stream?.Close();
}
}
// 自定义数据源加载
public IEnumerator LoadBundleFromCustomSource(string bundleId)
{
// 假设从某个自定义数据源获取流
Stream customStream = GetCustomDataStream(bundleId);
if (customStream != null)
{
AssetBundleCreateRequest request = AssetBundle.LoadFromStreamAsync(customStream);
yield return request;
if (request.assetBundle != null)
{
activeStreams[request.assetBundle] = customStream;
Debug.Log($"Bundle loaded from custom source: {bundleId}");
}
else
{
customStream.Close();
}
}
}
// 卸载Bundle时关闭对应的Stream
public void UnloadBundleWithStream(AssetBundle bundle)
{
if (activeStreams.ContainsKey(bundle))
{
activeStreams[bundle].Close();
activeStreams.Remove(bundle);
}
bundle.Unload(false);
}
private Stream GetCustomDataStream(string bundleId)
{
// 这里实现你的自定义数据源逻辑
// 比如从数据库、网络服务或加密文件中获取数据
return null;
}
void OnDestroy()
{
// 清理所有活跃的Stream
foreach (var stream in activeStreams.Values)
{
stream?.Close();
}
activeStreams.Clear();
}
}
性能对比和选择建议
性能排序(从快到慢)
- LoadFromFile - 内存映射,性能最佳
- LoadFromStream - 流式访问,适中
- LoadFromMemory - 需要数据拷贝,较慢
- UnityWebRequest - 网络延迟,最慢
选择合适的 AssetBundle 加载方式需根据项目需求和资源特点进行权衡。
加载方式 | 场景 | 优点 | 缺点 |
---|---|---|---|
同步加载
LoadFromFile | 小型资源、预加载 | 简单、快速 | 阻塞主线程 |
异步加载
LoadFromFileAsync | 大型资源、后台加载 | 不阻塞主线程、支持进度 | 实现复杂 |
从内存加载 LoadFromMemory | 网络下载、加密资源 | 灵活、支持加密 | 内存占用高 |
从网络加载 UnityWebRequestAssetBundle | 热更新、在线资源 | 支持异步下载和加载 | 依赖网络 |
流式加载 LoadFromStream | 流式传输、大文件加载 | 支持分块加载 | 实现复杂 |
在实际开发中,开发者应结合内存管理、性能优化和用户体验需求选择加载方式。小型资源可用同步加载快速处理,大型资源或动态加载场景推荐异步加载,而热更新和在线资源则优先考虑网络加载方式。通过合理规划,可确保游戏运行流畅并提升用户体验。