一、AssetBundle 基本概念
1.1 什么是 AssetBundle
AssetBundle 是 Unity 提供的资源打包系统,可以将游戏资源(模型、纹理、音频、预制体等)打包成独立的文件,用于:
-
资源热更新
-
按需加载(减少初始包体大小)
-
DLC 内容分发
-
平台适配(不同平台使用不同资源)
1.2 AssetBundle 结构
text
AssetBundle 文件 ├── 资源数据(序列化数据) ├── 资源头信息 ├── 资源依赖信息 └── 文件清单(Manifest)
二、AssetBundle 完整工作流程
2.1 创建与打包流程
csharp
// 1. 标记资源 AssetBundle 标签
// 在 Editor 中为资源设置 AssetBundle 名称和变体
// 2. 构建 AssetBundle
using UnityEditor;
using System.IO;
public class AssetBundleBuilder
{
[MenuItem("Tools/Build AssetBundles")]
static void BuildAllAssetBundles()
{
string outputPath = "Assets/AssetBundles";
if (!Directory.Exists(outputPath))
Directory.CreateDirectory(outputPath);
// 构建选项
BuildAssetBundleOptions options =
BuildAssetBundleOptions.ChunkBasedCompression | // LZ4压缩
BuildAssetBundleOptions.DeterministicAssetBundle; // 确定性构建
// 构建平台
BuildTarget target = BuildTarget.StandaloneWindows;
// 执行构建
BuildPipeline.BuildAssetBundles(
outputPath,
options,
target
);
Debug.Log("AssetBundles 构建完成!");
}
}
2.2 加载流程架构
csharp
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
/// <summary>
/// AssetBundle 加载模式
/// </summary>
public enum BundleLoadMode
{
Local, // 本地加载
Remote, // 远程加载
Persistent // 持久化目录加载
}
/// <summary>
/// AssetBundle 加载优先级
/// </summary>
public enum BundleLoadPriority
{
Low = 0,
Normal = 1,
High = 2,
Immediate = 3 // 同步加载,只在必要时使用
}
/// <summary>
/// AssetBundle 加载状态
/// </summary>
public enum BundleLoadState
{
NotLoaded,
Loading,
Loaded,
Failed
}
/// <summary>
/// AssetBundle 引用信息
/// </summary>
[System.Serializable]
public class BundleReferenceInfo
{
public string bundleName;
public int referenceCount; // 引用计数
public DateTime lastAccessTime; // 最后访问时间
public long bundleSize; // Bundle大小(字节)
public bool isPersistent; // 是否常驻内存
public List<string> dependentBundles; // 依赖此Bundle的其他Bundle
public BundleReferenceInfo(string name)
{
bundleName = name;
referenceCount = 0;
lastAccessTime = DateTime.Now;
dependentBundles = new List<string>();
}
}
/// <summary>
/// 完善的 AssetBundle 管理器
/// </summary>
public class AssetBundleManager : MonoBehaviour
{
#region 单例模式
private static AssetBundleManager _instance;
public static AssetBundleManager Instance
{
get
{
if (_instance == null)
{
GameObject go = new GameObject("AssetBundleManager");
_instance = go.AddComponent<AssetBundleManager>();
DontDestroyOnLoad(go);
}
return _instance;
}
}
#endregion
#region 配置参数
[Header("配置参数")]
[SerializeField] private bool enableLog = true;
[SerializeField] private bool enableProfiling = true;
[SerializeField] private int maxConcurrentLoads = 3; // 最大并发加载数
[SerializeField] private float unloadUnusedDelay = 60f; // 清理未使用资源延迟(秒)
[SerializeField] private long memoryThreshold = 100 * 1024 * 1024; // 内存阈值(100MB)
[SerializeField] private int maxCacheBundles = 20; // 最大缓存Bundle数量
[Header("路径配置")]
[SerializeField] private string remoteBundleUrl = "http://your-server.com/assetbundles/";
[SerializeField] private string localBundlePath = "AssetBundles";
#endregion
#region 内部数据结构
// 已加载的AssetBundle缓存
private Dictionary<string, AssetBundle> loadedBundles = new Dictionary<string, AssetBundle>();
// Bundle引用信息
private Dictionary<string, BundleReferenceInfo> bundleReferences = new Dictionary<string, BundleReferenceInfo>();
// 正在加载的Bundle
private Dictionary<string, Coroutine> loadingCoroutines = new Dictionary<string, Coroutine>();
// 加载队列
private Queue<BundleLoadRequest> loadQueue = new Queue<BundleLoadRequest>();
private List<BundleLoadRequest> activeLoads = new List<BundleLoadRequest>();
// Manifest缓存
private AssetBundleManifest manifest;
private bool isManifestLoaded = false;
private Hash128 manifestHash;
// 版本信息
private Dictionary<string, Hash128> bundleHashes = new Dictionary<string, Hash128>();
private Dictionary<string, long> bundleSizes = new Dictionary<string, long>();
// 事件回调
public event Action<string> OnBundleLoaded;
public event Action<string, string> OnBundleLoadFailed;
public event Action<long> OnMemoryWarning;
#endregion
#region 公共接口
/// <summary>
/// 初始化AssetBundle管理器
/// </summary>
public IEnumerator Initialize(BundleLoadMode loadMode = BundleLoadMode.Local)
{
Log("AssetBundleManager 初始化...");
// 1. 加载Manifest
yield return LoadManifest(loadMode);
if (!isManifestLoaded)
{
LogError("Manifest加载失败!");
yield break;
}
// 2. 加载版本信息
yield return LoadVersionInfo(loadMode);
// 3. 启动内存监控
StartCoroutine(MemoryMonitor());
// 4. 启动加载队列处理器
StartCoroutine(ProcessLoadQueue());
Log("AssetBundleManager 初始化完成");
}
/// <summary>
/// 异步加载AssetBundle
/// </summary>
public void LoadBundleAsync(string bundleName,
Action<AssetBundle> onComplete,
BundleLoadPriority priority = BundleLoadPriority.Normal,
bool loadDependencies = true)
{
BundleLoadRequest request = new BundleLoadRequest
{
bundleName = bundleName,
onComplete = onComplete,
priority = (int)priority,
loadDependencies = loadDependencies,
requestTime = Time.time
};
// 添加到队列
lock (loadQueue)
{
loadQueue.Enqueue(request);
}
}
/// <summary>
/// 同步加载AssetBundle(谨慎使用)
/// </summary>
public AssetBundle LoadBundleSync(string bundleName, bool loadDependencies = true)
{
if (IsBundleLoaded(bundleName))
{
IncreaseReference(bundleName);
return loadedBundles[bundleName];
}
try
{
// 加载依赖
if (loadDependencies && isManifestLoaded)
{
string[] dependencies = manifest.GetAllDependencies(bundleName);
foreach (string dep in dependencies)
{
if (!IsBundleLoaded(dep))
{
LoadBundleSync(dep, false);
}
}
}
// 加载Bundle
string path = GetBundlePath(bundleName, BundleLoadMode.Local);
AssetBundle bundle = AssetBundle.LoadFromFile(path);
if (bundle != null)
{
AddBundleToCache(bundleName, bundle);
return bundle;
}
}
catch (Exception e)
{
LogError($"同步加载Bundle失败: {bundleName}, Error: {e.Message}");
}
return null;
}
/// <summary>
/// 异步加载资源
/// </summary>
public IEnumerator LoadAssetAsync<T>(string bundleName, string assetName,
Action<T> onComplete) where T : UnityEngine.Object
{
// 等待Bundle加载完成
AssetBundle bundle = null;
bool isLoaded = false;
LoadBundleAsync(bundleName, (ab) =>
{
bundle = ab;
isLoaded = true;
});
while (!isLoaded && bundle == null)
yield return null;
if (bundle == null)
{
onComplete?.Invoke(null);
yield break;
}
// 异步加载资源
AssetBundleRequest request = bundle.LoadAssetAsync<T>(assetName);
yield return request;
T asset = request.asset as T;
onComplete?.Invoke(asset);
}
/// <summary>
/// 卸载AssetBundle
/// </summary>
public void UnloadBundle(string bundleName, bool unloadAllLoadedObjects = false)
{
if (!loadedBundles.ContainsKey(bundleName))
{
LogWarning($"尝试卸载未加载的Bundle: {bundleName}");
return;
}
BundleReferenceInfo info = bundleReferences[bundleName];
info.referenceCount--;
info.lastAccessTime = DateTime.Now;
if (info.referenceCount <= 0 && !info.isPersistent)
{
StartCoroutine(UnloadBundleInternal(bundleName, unloadAllLoadedObjects));
}
}
/// <summary>
/// 强制卸载所有Bundle(场景切换时调用)
/// </summary>
public void UnloadAllBundles(bool unloadAllLoadedObjects = false)
{
List<string> bundlesToUnload = new List<string>(loadedBundles.Keys);
foreach (string bundleName in bundlesToUnload)
{
if (bundleReferences.ContainsKey(bundleName) &&
!bundleReferences[bundleName].isPersistent)
{
StartCoroutine(UnloadBundleInternal(bundleName, unloadAllLoadedObjects));
}
}
// 清理未使用的资源
StartCoroutine(CleanupUnusedAssets());
}
/// <summary>
/// 设置Bundle常驻内存
/// </summary>
public void SetBundlePersistent(string bundleName, bool persistent)
{
if (bundleReferences.ContainsKey(bundleName))
{
bundleReferences[bundleName].isPersistent = persistent;
}
}
/// <summary>
/// 检查Bundle是否已加载
/// </summary>
public bool IsBundleLoaded(string bundleName)
{
return loadedBundles.ContainsKey(bundleName);
}
/// <summary>
/// 获取Bundle信息
/// </summary>
public BundleReferenceInfo GetBundleInfo(string bundleName)
{
return bundleReferences.ContainsKey(bundleName) ? bundleReferences[bundleName] : null;
}
/// <summary>
/// 获取所有已加载Bundle
/// </summary>
public List<string> GetAllLoadedBundles()
{
return new List<string>(loadedBundles.Keys);
}
/// <summary>
/// 获取Bundle大小
/// </summary>
public long GetBundleSize(string bundleName)
{
if (bundleSizes.ContainsKey(bundleName))
return bundleSizes[bundleName];
string path = GetBundlePath(bundleName, BundleLoadMode.Local);
if (File.Exists(path))
return new FileInfo(path).Length;
return 0;
}
/// <summary>
/// 预加载常用Bundle
/// </summary>
public IEnumerator PreloadBundles(List<string> bundlesToPreload, Action<float> onProgress = null)
{
int total = bundlesToPreload.Count;
int loaded = 0;
foreach (string bundle in bundlesToPreload)
{
if (!IsBundleLoaded(bundle))
{
bool isLoaded = false;
LoadBundleAsync(bundle, (ab) => isLoaded = true, BundleLoadPriority.Low);
while (!isLoaded)
yield return null;
}
loaded++;
onProgress?.Invoke((float)loaded / total);
}
}
#endregion
#region 内部实现
/// <summary>
/// 加载Manifest
/// </summary>
private IEnumerator LoadManifest(BundleLoadMode loadMode)
{
string platformFolder = GetPlatformFolder();
string manifestBundleName = platformFolder;
Log($"加载Manifest: {manifestBundleName}");
AssetBundle bundle = null;
string bundlePath = GetBundlePath(manifestBundleName, loadMode);
if (loadMode == BundleLoadMode.Remote)
{
// 远程加载
using (UnityWebRequest uwr = UnityWebRequestAssetBundle.GetAssetBundle(bundlePath, manifestHash, 0))
{
yield return uwr.SendWebRequest();
if (uwr.result == UnityWebRequest.Result.Success)
{
bundle = DownloadHandlerAssetBundle.GetContent(uwr);
}
}
}
else
{
// 本地加载
if (File.Exists(bundlePath))
{
bundle = AssetBundle.LoadFromFile(bundlePath);
}
}
if (bundle != null)
{
manifest = bundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
bundle.Unload(false);
isManifestLoaded = true;
Log("Manifest加载成功");
}
}
/// <summary>
/// 加载版本信息
/// </summary>
private IEnumerator LoadVersionInfo(BundleLoadMode loadMode)
{
string versionPath = GetVersionFilePath(loadMode);
if (loadMode == BundleLoadMode.Remote)
{
using (UnityWebRequest uwr = UnityWebRequest.Get(versionPath))
{
yield return uwr.SendWebRequest();
if (uwr.result == UnityWebRequest.Result.Success)
{
ParseVersionInfo(uwr.downloadHandler.text);
}
}
}
else if (File.Exists(versionPath))
{
string json = File.ReadAllText(versionPath);
ParseVersionInfo(json);
}
}
/// <summary>
/// 解析版本信息
/// </summary>
private void ParseVersionInfo(string json)
{
try
{
BundleVersionInfo versionInfo = JsonUtility.FromJson<BundleVersionInfo>(json);
foreach (BundleInfo info in versionInfo.bundles)
{
bundleHashes[info.name] = Hash128.Parse(info.hash);
bundleSizes[info.name] = info.size;
}
Log($"版本信息加载完成,共{versionInfo.bundles.Count}个Bundle");
}
catch (Exception e)
{
LogWarning($"解析版本信息失败: {e.Message}");
}
}
/// <summary>
/// 处理加载队列
/// </summary>
private IEnumerator ProcessLoadQueue()
{
while (true)
{
// 检查是否有空闲的加载槽
if (activeLoads.Count < maxConcurrentLoads && loadQueue.Count > 0)
{
BundleLoadRequest request;
lock (loadQueue)
{
request = loadQueue.Dequeue();
}
// 开始加载
Coroutine coroutine = StartCoroutine(LoadBundleWithDependencies(request));
activeLoads.Add(request);
loadingCoroutines[request.bundleName] = coroutine;
}
// 清理已完成的任务
activeLoads.RemoveAll(req => req.isCompleted);
yield return null;
}
}
/// <summary>
/// 加载Bundle及其依赖
/// </summary>
private IEnumerator LoadBundleWithDependencies(BundleLoadRequest request)
{
string bundleName = request.bundleName;
// 检查是否已加载
if (IsBundleLoaded(bundleName))
{
IncreaseReference(bundleName);
request.OnComplete(loadedBundles[bundleName]);
request.isCompleted = true;
yield break;
}
// 加载依赖
if (request.loadDependencies && isManifestLoaded)
{
string[] dependencies = manifest.GetAllDependencies(bundleName);
List<Coroutine> dependencyCoroutines = new List<Coroutine>();
foreach (string dep in dependencies)
{
if (!IsBundleLoaded(dep) && !loadingCoroutines.ContainsKey(dep))
{
BundleLoadRequest depRequest = new BundleLoadRequest
{
bundleName = dep,
priority = request.priority - 1, // 依赖优先级稍低
loadDependencies = false
};
Coroutine depCoroutine = StartCoroutine(LoadSingleBundle(depRequest));
dependencyCoroutines.Add(depCoroutine);
}
}
// 等待所有依赖加载完成
foreach (Coroutine coroutine in dependencyCoroutines)
{
yield return coroutine;
}
}
// 加载目标Bundle
yield return LoadSingleBundle(request);
// 记录依赖关系
if (isManifestLoaded)
{
string[] dependencies = manifest.GetAllDependencies(bundleName);
foreach (string dep in dependencies)
{
if (bundleReferences.ContainsKey(dep))
{
bundleReferences[dep].dependentBundles.Add(bundleName);
}
}
}
}
/// <summary>
/// 加载单个Bundle
/// </summary>
private IEnumerator LoadSingleBundle(BundleLoadRequest request)
{
string bundleName = request.bundleName;
BundleLoadState loadState = BundleLoadState.Loading;
Log($"开始加载Bundle: {bundleName}");
try
{
string path = GetBundlePath(bundleName, BundleLoadMode.Local);
if (!File.Exists(path))
{
LogError($"Bundle文件不存在: {path}");
loadState = BundleLoadState.Failed;
request.OnError("文件不存在");
yield break;
}
// 异步加载Bundle
AssetBundleCreateRequest createRequest = AssetBundle.LoadFromFileAsync(path);
yield return createRequest;
if (createRequest.assetBundle == null)
{
LogError($"Bundle加载失败: {bundleName}");
loadState = BundleLoadState.Failed;
request.OnError("加载失败");
yield break;
}
// 添加到缓存
AddBundleToCache(bundleName, createRequest.assetBundle);
loadState = BundleLoadState.Loaded;
// 回调
request.OnComplete(createRequest.assetBundle);
OnBundleLoaded?.Invoke(bundleName);
Log($"Bundle加载完成: {bundleName}");
}
catch (Exception e)
{
LogError($"加载Bundle异常: {bundleName}, Error: {e.Message}");
loadState = BundleLoadState.Failed;
request.OnError(e.Message);
OnBundleLoadFailed?.Invoke(bundleName, e.Message);
}
finally
{
request.isCompleted = true;
loadingCoroutines.Remove(bundleName);
}
}
/// <summary>
/// 卸载Bundle内部实现
/// </summary>
private IEnumerator UnloadBundleInternal(string bundleName, bool unloadAllLoadedObjects)
{
if (!loadedBundles.ContainsKey(bundleName))
yield break;
Log($"卸载Bundle: {bundleName}");
AssetBundle bundle = loadedBundles[bundleName];
BundleReferenceInfo info = bundleReferences[bundleName];
// 先卸载依赖此Bundle的其他Bundle
foreach (string dependentBundle in info.dependentBundles)
{
if (loadedBundles.ContainsKey(dependentBundle))
{
LogWarning($"Bundle {bundleName} 被 {dependentBundle} 依赖,先卸载依赖Bundle");
UnloadBundle(dependentBundle, unloadAllLoadedObjects);
}
}
// 等待一帧,让依赖Bundle先处理
yield return null;
// 卸载Bundle
bundle.Unload(unloadAllLoadedObjects);
loadedBundles.Remove(bundleName);
bundleReferences.Remove(bundleName);
// 触发GC(可选)
if (unloadAllLoadedObjects)
{
yield return Resources.UnloadUnusedAssets();
}
}
/// <summary>
/// 添加Bundle到缓存
/// </summary>
private void AddBundleToCache(string bundleName, AssetBundle bundle)
{
if (!loadedBundles.ContainsKey(bundleName))
{
loadedBundles.Add(bundleName, bundle);
if (!bundleReferences.ContainsKey(bundleName))
{
bundleReferences.Add(bundleName, new BundleReferenceInfo(bundleName));
}
bundleReferences[bundleName].referenceCount++;
bundleReferences[bundleName].lastAccessTime = DateTime.Now;
// 如果超过最大缓存数量,清理最久未使用的
if (loadedBundles.Count > maxCacheBundles)
{
CleanupLeastUsedBundles();
}
}
}
/// <summary>
/// 增加引用计数
/// </summary>
private void IncreaseReference(string bundleName)
{
if (bundleReferences.ContainsKey(bundleName))
{
bundleReferences[bundleName].referenceCount++;
bundleReferences[bundleName].lastAccessTime = DateTime.Now;
}
}
/// <summary>
/// 清理最久未使用的Bundle
/// </summary>
private void CleanupLeastUsedBundles()
{
List<BundleReferenceInfo> infos = new List<BundleReferenceInfo>(bundleReferences.Values);
// 按最后访问时间排序,排除常驻Bundle
infos.Sort((a, b) => a.lastAccessTime.CompareTo(b.lastAccessTime));
int bundlesToRemove = loadedBundles.Count - maxCacheBundles;
int removed = 0;
foreach (BundleReferenceInfo info in infos)
{
if (removed >= bundlesToRemove) break;
if (!info.isPersistent && info.referenceCount <= 0)
{
StartCoroutine(UnloadBundleInternal(info.bundleName, false));
removed++;
}
}
}
/// <summary>
/// 清理未使用的资源
/// </summary>
private IEnumerator CleanupUnusedAssets()
{
yield return new WaitForSeconds(unloadUnusedDelay);
Log("开始清理未使用资源...");
AsyncOperation asyncOp = Resources.UnloadUnusedAssets();
yield return asyncOp;
GC.Collect();
Log($"资源清理完成,释放内存: {Profiler.GetTotalAllocatedMemoryLong() / 1024 / 1024}MB");
}
/// <summary>
/// 内存监控
/// </summary>
private IEnumerator MemoryMonitor()
{
while (true)
{
yield return new WaitForSeconds(10f);
long totalMemory = Profiler.GetTotalAllocatedMemoryLong();
if (totalMemory > memoryThreshold)
{
LogWarning($"内存使用过高: {totalMemory / 1024 / 1024}MB");
OnMemoryWarning?.Invoke(totalMemory);
// 自动清理
UnloadAllBundles(false);
}
}
}
/// <summary>
/// 获取Bundle路径
/// </summary>
private string GetBundlePath(string bundleName, BundleLoadMode mode)
{
string platformFolder = GetPlatformFolder();
switch (mode)
{
case BundleLoadMode.Remote:
return $"{remoteBundleUrl}{platformFolder}/{bundleName}";
case BundleLoadMode.Persistent:
return $"{Application.persistentDataPath}/{localBundlePath}/{platformFolder}/{bundleName}";
case BundleLoadMode.Local:
default:
#if UNITY_EDITOR || UNITY_STANDALONE
return $"{Application.dataPath}/../{localBundlePath}/{platformFolder}/{bundleName}";
#else
return $"{Application.streamingAssetsPath}/{localBundlePath}/{platformFolder}/{bundleName}";
#endif
}
}
/// <summary>
/// 获取版本文件路径
/// </summary>
private string GetVersionFilePath(BundleLoadMode mode)
{
string platformFolder = GetPlatformFolder();
switch (mode)
{
case BundleLoadMode.Remote:
return $"{remoteBundleUrl}{platformFolder}/version.json";
default:
string bundlePath = GetBundlePath("", mode);
return Path.Combine(Path.GetDirectoryName(bundlePath), "version.json");
}
}
/// <summary>
/// 获取平台文件夹名称
/// </summary>
private string GetPlatformFolder()
{
#if UNITY_EDITOR
return GetPlatformForAssetBundles(UnityEditor.EditorUserBuildSettings.activeBuildTarget);
#else
return GetPlatformForAssetBundles(Application.platform);
#endif
}
private string GetPlatformForAssetBundles(RuntimePlatform platform)
{
switch (platform)
{
case RuntimePlatform.Android:
return "Android";
case RuntimePlatform.IPhonePlayer:
return "iOS";
case RuntimePlatform.WebGLPlayer:
return "WebGL";
case RuntimePlatform.WindowsPlayer:
case RuntimePlatform.WindowsEditor:
return "Windows";
case RuntimePlatform.OSXPlayer:
case RuntimePlatform.OSXEditor:
return "OSX";
default:
return platform.ToString();
}
}
#if UNITY_EDITOR
private string GetPlatformForAssetBundles(UnityEditor.BuildTarget target)
{
switch (target)
{
case UnityEditor.BuildTarget.Android:
return "Android";
case UnityEditor.BuildTarget.iOS:
return "iOS";
case UnityEditor.BuildTarget.WebGL:
return "WebGL";
case UnityEditor.BuildTarget.StandaloneWindows:
case UnityEditor.BuildTarget.StandaloneWindows64:
return "Windows";
case UnityEditor.BuildTarget.StandaloneOSX:
return "OSX";
default:
return target.ToString();
}
}
#endif
/// <summary>
/// 日志输出
/// </summary>
private void Log(string message)
{
if (enableLog)
Debug.Log($"[AssetBundleManager] {message}");
}
private void LogWarning(string message)
{
Debug.LogWarning($"[AssetBundleManager] {message}");
}
private void LogError(string message)
{
Debug.LogError($"[AssetBundleManager] {message}");
}
#endregion
#region 辅助类
/// <summary>
/// Bundle加载请求
/// </summary>
private class BundleLoadRequest
{
public string bundleName;
public Action<AssetBundle> onComplete;
public Action<string> onError;
public int priority;
public bool loadDependencies;
public float requestTime;
public bool isCompleted;
public void OnComplete(AssetBundle bundle)
{
onComplete?.Invoke(bundle);
}
public void OnError(string error)
{
onError?.Invoke(error);
}
}
/// <summary>
/// Bundle版本信息
/// </summary>
[System.Serializable]
private class BundleVersionInfo
{
public int version;
public string buildTime;
public List<BundleInfo> bundles;
}
[System.Serializable]
private class BundleInfo
{
public string name;
public string hash;
public long size;
public List<string> dependencies;
}
#endregion
#region Unity生命周期
void OnApplicationQuit()
{
// 游戏退出时清理所有Bundle
UnloadAllBundles(true);
}
void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus)
{
// 应用暂停时清理部分资源
CleanupLeastUsedBundles();
}
}
#endregion
}
三、AssetBundle 加载方式对比
3.1 同步加载方法
csharp
public class AssetBundleLoader
{
// 方法1:从文件同步加载(推荐)
public void LoadFromFile()
{
string path = Application.streamingAssetsPath + "/bundle_name";
AssetBundle bundle = AssetBundle.LoadFromFile(path);
if (bundle != null)
{
GameObject prefab = bundle.LoadAsset<GameObject>("asset_name");
Instantiate(prefab);
}
}
// 方法2:从内存同步加载
public void LoadFromMemory(byte[] binaryData)
{
AssetBundle bundle = AssetBundle.LoadFromMemory(binaryData);
// 使用资源...
}
}
3.2 异步加载方法
csharp
public class AssetBundleAsyncLoader : MonoBehaviour
{
// 方法1:异步加载 AssetBundle
public IEnumerator LoadBundleAsync(string bundlePath)
{
AssetBundleCreateRequest request =
AssetBundle.LoadFromFileAsync(bundlePath);
yield return request;
AssetBundle bundle = request.assetBundle;
if (bundle != null)
{
// 异步加载资源
AssetBundleRequest assetRequest =
bundle.LoadAssetAsync<GameObject>("prefab_name");
yield return assetRequest;
GameObject prefab = assetRequest.asset as GameObject;
Instantiate(prefab);
}
}
// 方法2:使用 UnityWebRequest(支持网络加载)
public IEnumerator LoadBundleWebRequest(string url)
{
using (UnityWebRequest webRequest =
UnityWebRequestAssetBundle.GetAssetBundle(url))
{
yield return webRequest.SendWebRequest();
if (webRequest.result == UnityWebRequest.Result.Success)
{
AssetBundle bundle =
DownloadHandlerAssetBundle.GetContent(webRequest);
// 使用资源...
}
}
}
}
3.3 加载方式对比表
| 加载方式 | 适用场景 | 内存占用 | 加载速度 | 推荐指数 |
|---|---|---|---|---|
LoadFromFile | 本地 StreamingAssets | 低 | 快 | ★★★★★ |
LoadFromMemory | 加密资源、内存数据 | 高 | 中 | ★★☆☆☆ |
LoadFromFileAsync | 大资源加载 | 低 | 中 | ★★★★☆ |
UnityWebRequest | 网络下载、热更新 | 中 | 依赖网络 | ★★★★☆ |
四、依赖管理最佳实践
4.1 依赖加载实现
csharp
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Networking;
/// <summary>
/// 依赖关系解析模式
/// </summary>
public enum DependencyResolutionMode
{
Eager, // 急加载:立即加载所有依赖
Lazy, // 懒加载:按需加载依赖
Hybrid // 混合模式:急加载直接依赖,懒加载间接依赖
}
/// <summary>
/// 依赖关系图节点
/// </summary>
public class DependencyNode
{
public string bundleName;
public List<DependencyNode> dependencies; // 依赖的节点
public List<DependencyNode> dependents; // 依赖此节点的节点
public int referenceCount; // 引用计数
public bool isLoaded; // 是否已加载
public bool isRoot; // 是否是根节点(用户显式加载的)
public DateTime lastAccessTime; // 最后访问时间
public DependencyNode(string name)
{
bundleName = name;
dependencies = new List<DependencyNode>();
dependents = new List<DependencyNode>();
referenceCount = 0;
isLoaded = false;
isRoot = false;
lastAccessTime = DateTime.Now;
}
/// <summary>
/// 获取所有直接和间接依赖
/// </summary>
public List<DependencyNode> GetAllDependencies(bool includeSelf = false)
{
List<DependencyNode> result = new List<DependencyNode>();
HashSet<DependencyNode> visited = new HashSet<DependencyNode>();
if (includeSelf)
{
result.Add(this);
visited.Add(this);
}
GetAllDependenciesRecursive(this, result, visited);
return result;
}
private void GetAllDependenciesRecursive(DependencyNode node,
List<DependencyNode> result,
HashSet<DependencyNode> visited)
{
foreach (var dep in node.dependencies)
{
if (!visited.Contains(dep))
{
visited.Add(dep);
result.Add(dep);
GetAllDependenciesRecursive(dep, result, visited);
}
}
}
/// <summary>
/// 获取所有直接和间接依赖者
/// </summary>
public List<DependencyNode> GetAllDependents()
{
List<DependencyNode> result = new List<DependencyNode>();
HashSet<DependencyNode> visited = new HashSet<DependencyNode>();
GetAllDependentsRecursive(this, result, visited);
return result;
}
private void GetAllDependentsRecursive(DependencyNode node,
List<DependencyNode> result,
HashSet<DependencyNode> visited)
{
foreach (var dependent in node.dependents)
{
if (!visited.Contains(dependent))
{
visited.Add(dependent);
result.Add(dependent);
GetAllDependentsRecursive(dependent, result, visited);
}
}
}
/// <summary>
/// 检查循环依赖
/// </summary>
public bool HasCircularDependency()
{
HashSet<DependencyNode> visited = new HashSet<DependencyNode>();
HashSet<DependencyNode> recursionStack = new HashSet<DependencyNode>();
return CheckCircularDependencyRecursive(this, visited, recursionStack);
}
private bool CheckCircularDependencyRecursive(DependencyNode node,
HashSet<DependencyNode> visited,
HashSet<DependencyNode> recursionStack)
{
visited.Add(node);
recursionStack.Add(node);
foreach (var dep in node.dependencies)
{
if (!visited.Contains(dep))
{
if (CheckCircularDependencyRecursive(dep, visited, recursionStack))
return true;
}
else if (recursionStack.Contains(dep))
{
return true; // 发现循环依赖
}
}
recursionStack.Remove(node);
return false;
}
}
/// <summary>
/// 依赖加载任务
/// </summary>
public class DependencyLoadTask
{
public string bundleName;
public List<string> dependencies;
public Action<AssetBundle> onComplete;
public Action<string> onError;
public bool loadDependencies;
public int priority;
public DependencyLoadTask(string bundleName)
{
this.bundleName = bundleName;
dependencies = new List<string>();
loadDependencies = true;
priority = 1;
}
}
/// <summary>
/// 依赖关系图
/// </summary>
public class DependencyGraph
{
private Dictionary<string, DependencyNode> nodes = new Dictionary<string, DependencyNode>();
public bool AddNode(string bundleName)
{
if (!nodes.ContainsKey(bundleName))
{
nodes[bundleName] = new DependencyNode(bundleName);
return true;
}
return false;
}
public bool AddDependency(string fromBundle, string toBundle)
{
if (!nodes.ContainsKey(fromBundle))
AddNode(fromBundle);
if (!nodes.ContainsKey(toBundle))
AddNode(toBundle);
DependencyNode fromNode = nodes[fromBundle];
DependencyNode toNode = nodes[toBundle];
// 检查是否已存在此依赖
if (!fromNode.dependencies.Contains(toNode))
{
fromNode.dependencies.Add(toNode);
toNode.dependents.Add(fromNode);
// 检查是否创建了循环依赖
if (fromNode.HasCircularDependency())
{
// 回滚
fromNode.dependencies.Remove(toNode);
toNode.dependents.Remove(fromNode);
Debug.LogError($"添加依赖 {fromBundle} -> {toBundle} 会导致循环依赖!");
return false;
}
return true;
}
return false;
}
public DependencyNode GetNode(string bundleName)
{
nodes.TryGetValue(bundleName, out DependencyNode node);
return node;
}
public List<string> GetAllDependencies(string bundleName, bool includeSelf = false)
{
if (!nodes.TryGetValue(bundleName, out DependencyNode node))
return new List<string>();
var dependencyNodes = node.GetAllDependencies(includeSelf);
return dependencyNodes.Select(n => n.bundleName).ToList();
}
public List<string> GetDirectDependencies(string bundleName)
{
if (!nodes.TryGetValue(bundleName, out DependencyNode node))
return new List<string>();
return node.dependencies.Select(d => d.bundleName).ToList();
}
public List<string> GetDirectDependents(string bundleName)
{
if (!nodes.TryGetValue(bundleName, out DependencyNode node))
return new List<string>();
return node.dependents.Select(d => d.bundleName).ToList();
}
public List<string> GetAllDependents(string bundleName)
{
if (!nodes.TryGetValue(bundleName, out DependencyNode node))
return new List<string>();
var dependentNodes = node.GetAllDependents();
return dependentNodes.Select(n => n.bundleName).ToList();
}
/// <summary>
/// 拓扑排序,获取加载顺序
/// </summary>
public List<string> GetLoadOrder(List<string> bundles)
{
List<string> result = new List<string>();
HashSet<string> visited = new HashSet<string>();
HashSet<string> tempMark = new HashSet<string>();
foreach (string bundle in bundles)
{
if (!visited.Contains(bundle))
{
if (!TopologicalSort(bundle, visited, tempMark, result))
{
Debug.LogError("发现循环依赖,无法确定加载顺序");
return new List<string>();
}
}
}
result.Reverse(); // 拓扑排序结果是逆序的,反转后得到正确的加载顺序
return result;
}
private bool TopologicalSort(string bundleName,
HashSet<string> visited,
HashSet<string> tempMark,
List<string> result)
{
if (!nodes.ContainsKey(bundleName))
return true;
if (tempMark.Contains(bundleName))
return false; // 发现循环依赖
if (visited.Contains(bundleName))
return true;
tempMark.Add(bundleName);
foreach (var dep in nodes[bundleName].dependencies)
{
if (!TopologicalSort(dep.bundleName, visited, tempMark, result))
return false;
}
tempMark.Remove(bundleName);
visited.Add(bundleName);
result.Add(bundleName);
return true;
}
/// <summary>
/// 获取逆拓扑排序,用于卸载
/// </summary>
public List<string> GetUnloadOrder(List<string> bundles)
{
var loadOrder = GetLoadOrder(bundles);
loadOrder.Reverse(); // 卸载顺序是加载顺序的逆序
return loadOrder;
}
public bool HasCircularDependency(string bundleName = null)
{
if (bundleName != null)
{
if (nodes.TryGetValue(bundleName, out DependencyNode node))
return node.HasCircularDependency();
return false;
}
// 检查整个图的循环依赖
foreach (var node in nodes.Values)
{
if (node.HasCircularDependency())
return true;
}
return false;
}
public void Clear()
{
nodes.Clear();
}
public int NodeCount => nodes.Count;
}
/// <summary>
/// 完善的依赖关系管理器
/// </summary>
public class DependencyManager : MonoBehaviour
{
#region 单例模式
private static DependencyManager _instance;
public static DependencyManager Instance
{
get
{
if (_instance == null)
{
GameObject go = new GameObject("DependencyManager");
_instance = go.AddComponent<DependencyManager>();
DontDestroyOnLoad(go);
}
return _instance;
}
}
#endregion
#region 配置参数
[Header("依赖管理配置")]
[SerializeField] private bool enableLog = true;
[SerializeField] private DependencyResolutionMode resolutionMode = DependencyResolutionMode.Hybrid;
[SerializeField] private bool autoResolveCircularDependencies = true;
[SerializeField] private int maxDependencyDepth = 10; // 最大依赖深度
[Header("性能优化")]
[SerializeField] private bool enableDependencyCaching = true;
[SerializeField] private int dependencyCacheSize = 50;
[SerializeField] private bool preloadCommonDependencies = true;
[SerializeField] private List<string> commonDependencies = new List<string> { "shared", "common", "core" };
#endregion
#region 内部数据结构
// 依赖关系图
private DependencyGraph dependencyGraph = new DependencyGraph();
// Manifest引用
private AssetBundleManifest manifest;
private bool isManifestLoaded = false;
// 依赖缓存
private Dictionary<string, List<string>> dependencyCache = new Dictionary<string, List<string>>();
private Dictionary<string, DateTime> cacheTimestamps = new Dictionary<string, DateTime>();
// 加载状态跟踪
private Dictionary<string, DependencyLoadTask> activeTasks = new Dictionary<string, DependencyLoadTask>();
private Dictionary<string, List<DependencyLoadTask>> waitingTasks = new Dictionary<string, List<DependencyLoadTask>>();
// 依赖加载队列
private PriorityQueue<DependencyLoadTask> loadQueue = new PriorityQueue<DependencyLoadTask>(
Comparer<DependencyLoadTask>.Create((a, b) => b.priority.CompareTo(a.priority))
);
// 统计信息
private DependencyStats stats = new DependencyStats();
// 事件
public event Action<string, List<string>> OnDependenciesResolved;
public event Action<string> OnCircularDependencyDetected;
public event Action<DependencyStats> OnStatsUpdated;
#endregion
#region 公共接口
/// <summary>
/// 初始化依赖管理器
/// </summary>
public IEnumerator Initialize(AssetBundleManifest manifest)
{
if (manifest == null)
{
Debug.LogError("DependencyManager: Manifest不能为空");
yield break;
}
this.manifest = manifest;
isManifestLoaded = true;
// 构建依赖关系图
yield return BuildDependencyGraph();
// 预加载常用依赖
if (preloadCommonDependencies)
{
yield return PreloadCommonDependencies();
}
// 启动依赖处理器
StartCoroutine(ProcessDependencyQueue());
Log("DependencyManager 初始化完成");
}
/// <summary>
/// 解析Bundle的依赖关系
/// </summary>
public DependencyResult ResolveDependencies(string bundleName,
bool includeSelf = false,
bool forceRefresh = false)
{
if (!isManifestLoaded)
{
return new DependencyResult
{
success = false,
error = "Manifest未加载"
};
}
// 检查缓存
if (!forceRefresh && enableDependencyCaching && dependencyCache.ContainsKey(bundleName))
{
DateTime lastUpdate = cacheTimestamps[bundleName];
if ((DateTime.Now - lastUpdate).TotalMinutes < 5) // 5分钟缓存
{
stats.cacheHits++;
return new DependencyResult
{
success = true,
dependencies = dependencyCache[bundleName],
fromCache = true
};
}
}
try
{
List<string> allDependencies = new List<string>();
// 获取直接依赖
string[] directDeps = manifest.GetAllDependencies(bundleName);
// 检查依赖深度
int depth = CalculateDependencyDepth(bundleName, new HashSet<string>());
if (depth > maxDependencyDepth)
{
LogWarning($"Bundle {bundleName} 的依赖深度({depth})超过限制({maxDependencyDepth})");
return new DependencyResult
{
success = false,
error = $"依赖深度超过限制: {depth} > {maxDependencyDepth}"
};
}
// 根据模式解析依赖
switch (resolutionMode)
{
case DependencyResolutionMode.Eager:
allDependencies = GetAllDependenciesEager(bundleName);
break;
case DependencyResolutionMode.Lazy:
allDependencies = new List<string>(directDeps);
break;
case DependencyResolutionMode.Hybrid:
allDependencies = GetDependenciesHybrid(bundleName);
break;
}
// 去重
allDependencies = allDependencies.Distinct().ToList();
if (includeSelf)
{
allDependencies.Insert(0, bundleName);
}
// 缓存结果
if (enableDependencyCaching)
{
CacheDependencies(bundleName, allDependencies);
}
stats.dependencyResolutions++;
OnStatsUpdated?.Invoke(stats);
return new DependencyResult
{
success = true,
dependencies = allDependencies,
directDependencies = directDeps.ToList(),
dependencyDepth = depth,
fromCache = false
};
}
catch (Exception e)
{
LogError($"解析依赖失败: {bundleName}, Error: {e.Message}");
return new DependencyResult
{
success = false,
error = e.Message
};
}
}
/// <summary>
/// 创建依赖加载任务
/// </summary>
public DependencyLoadTask CreateLoadTask(string bundleName,
Action<AssetBundle> onComplete = null,
Action<string> onError = null,
int priority = 1)
{
var result = ResolveDependencies(bundleName);
if (!result.success)
{
onError?.Invoke($"无法解析依赖: {result.error}");
return null;
}
var task = new DependencyLoadTask(bundleName)
{
onComplete = onComplete,
onError = onError,
priority = priority,
dependencies = result.dependencies
};
// 检查循环依赖
if (HasCircularDependency(bundleName))
{
HandleCircularDependency(bundleName);
return null;
}
return task;
}
/// <summary>
/// 提交依赖加载任务
/// </summary>
public void SubmitLoadTask(DependencyLoadTask task)
{
if (task == null) return;
lock (loadQueue)
{
loadQueue.Enqueue(task);
}
stats.tasksSubmitted++;
Log($"提交加载任务: {task.bundleName}, 优先级: {task.priority}");
}
/// <summary>
/// 获取Bundle的依赖树
/// </summary>
public DependencyTree GetDependencyTree(string bundleName, bool includeSelf = true)
{
var tree = new DependencyTree
{
rootBundle = bundleName,
nodes = new List<DependencyTreeNode>()
};
BuildDependencyTreeRecursive(bundleName, tree, 0, new HashSet<string>());
return tree;
}
/// <summary>
/// 可视化依赖关系
/// </summary>
public string VisualizeDependencies(string bundleName, bool includeIndirect = true)
{
StringBuilder sb = new StringBuilder();
if (includeIndirect)
{
var allDeps = GetAllDependenciesEager(bundleName);
sb.AppendLine($"Bundle: {bundleName}");
sb.AppendLine($"总依赖数: {allDeps.Count}");
sb.AppendLine("依赖关系:");
foreach (string dep in allDeps)
{
int depth = CalculateDependencyDepth(dep, new HashSet<string>());
string indent = new string(' ', depth * 2);
sb.AppendLine($"{indent}└─ {dep}");
}
}
else
{
string[] directDeps = manifest.GetDirectDependencies(bundleName);
sb.AppendLine($"Bundle: {bundleName}");
sb.AppendLine($"直接依赖数: {directDeps.Length}");
foreach (string dep in directDeps)
{
sb.AppendLine($" └─ {dep}");
}
}
return sb.ToString();
}
/// <summary>
/// 检查是否存在循环依赖
/// </summary>
public bool HasCircularDependency(string bundleName = null)
{
return dependencyGraph.HasCircularDependency(bundleName);
}
/// <summary>
/// 分析依赖关系健康度
/// </summary>
public DependencyHealthReport AnalyzeDependencyHealth()
{
var report = new DependencyHealthReport
{
totalBundles = dependencyGraph.NodeCount,
analyzedAt = DateTime.Now
};
List<string> problematicBundles = new List<string>();
foreach (var node in GetAllNodes())
{
var deps = GetAllDependenciesEager(node);
// 检查深度
int depth = CalculateDependencyDepth(node, new HashSet<string>());
if (depth > maxDependencyDepth)
{
report.deepDependencies++;
problematicBundles.Add(node);
}
// 检查循环依赖
if (HasCircularDependency(node))
{
report.circularDependencies++;
problematicBundles.Add(node);
}
// 检查过度耦合
int dependentCount = GetDirectDependents(node).Count;
if (dependentCount > 10) // 假设超过10个依赖者视为过度耦合
{
report.highCouplingBundles++;
problematicBundles.Add(node);
}
}
report.problematicBundles = problematicBundles.Distinct().ToList();
report.healthScore = CalculateHealthScore(report);
return report;
}
/// <summary>
/// 优化依赖关系
/// </summary>
public IEnumerator OptimizeDependencies(List<string> frequentlyUsedBundles)
{
Log("开始依赖关系优化...");
// 1. 合并常用依赖
yield return MergeCommonDependencies(frequentlyUsedBundles);
// 2. 重构深度依赖
yield return RefactorDeepDependencies();
// 3. 清理无用依赖
yield return CleanupUnusedDependencies();
Log("依赖关系优化完成");
}
/// <summary>
/// 获取统计信息
/// </summary>
public DependencyStats GetStats()
{
return stats;
}
/// <summary>
/// 清理缓存
/// </summary>
public void ClearCache()
{
dependencyCache.Clear();
cacheTimestamps.Clear();
Log("依赖缓存已清理");
}
/// <summary>
/// 重新加载依赖关系
/// </summary>
public IEnumerator ReloadDependencies(AssetBundleManifest newManifest)
{
manifest = newManifest;
dependencyGraph.Clear();
ClearCache();
yield return BuildDependencyGraph();
Log("依赖关系已重新加载");
}
#endregion
#region 内部实现
/// <summary>
/// 构建依赖关系图
/// </summary>
private IEnumerator BuildDependencyGraph()
{
if (manifest == null)
yield break;
string[] allBundles = manifest.GetAllAssetBundles();
int processed = 0;
foreach (string bundle in allBundles)
{
dependencyGraph.AddNode(bundle);
string[] dependencies = manifest.GetAllDependencies(bundle);
foreach (string dep in dependencies)
{
dependencyGraph.AddDependency(bundle, dep);
}
processed++;
// 每处理100个Bundle给一帧休息时间
if (processed % 100 == 0)
{
yield return null;
}
}
Log($"依赖关系图构建完成,共{allBundles.Length}个Bundle");
}
/// <summary>
/// 急加载模式:获取所有依赖
/// </summary>
private List<string> GetAllDependenciesEager(string bundleName)
{
List<string> allDependencies = new List<string>();
HashSet<string> visited = new HashSet<string>();
GetAllDependenciesRecursive(bundleName, allDependencies, visited);
return allDependencies;
}
private void GetAllDependenciesRecursive(string bundleName,
List<string> result,
HashSet<string> visited)
{
if (!isManifestLoaded || visited.Contains(bundleName))
return;
visited.Add(bundleName);
string[] dependencies = manifest.GetAllDependencies(bundleName);
foreach (string dep in dependencies)
{
GetAllDependenciesRecursive(dep, result, visited);
if (!result.Contains(dep))
{
result.Add(dep);
}
}
}
/// <summary>
/// 混合模式:获取依赖
/// </summary>
private List<string> GetDependenciesHybrid(string bundleName)
{
List<string> result = new List<string>();
HashSet<string> visited = new HashSet<string>();
GetDependenciesHybridRecursive(bundleName, result, visited, 0);
return result;
}
private void GetDependenciesHybridRecursive(string bundleName,
List<string> result,
HashSet<string> visited,
int depth)
{
if (!isManifestLoaded || visited.Contains(bundleName))
return;
visited.Add(bundleName);
string[] dependencies = manifest.GetAllDependencies(bundleName);
// 直接依赖总是加载
foreach (string dep in dependencies)
{
if (!result.Contains(dep))
{
result.Add(dep);
}
}
// 深度超过1的间接依赖按需加载
if (depth < 1)
{
foreach (string dep in dependencies)
{
GetDependenciesHybridRecursive(dep, result, visited, depth + 1);
}
}
}
/// <summary>
/// 计算依赖深度
/// </summary>
private int CalculateDependencyDepth(string bundleName, HashSet<string> visited)
{
if (!isManifestLoaded || visited.Contains(bundleName))
return 0;
visited.Add(bundleName);
string[] dependencies = manifest.GetAllDependencies(bundleName);
int maxDepth = 0;
foreach (string dep in dependencies)
{
int depth = CalculateDependencyDepth(dep, visited) + 1;
maxDepth = Mathf.Max(maxDepth, depth);
}
return maxDepth;
}
/// <summary>
/// 缓存依赖关系
/// </summary>
private void CacheDependencies(string bundleName, List<string> dependencies)
{
// 如果缓存已满,移除最旧的
if (dependencyCache.Count >= dependencyCacheSize)
{
string oldestKey = null;
DateTime oldestTime = DateTime.MaxValue;
foreach (var kvp in cacheTimestamps)
{
if (kvp.Value < oldestTime)
{
oldestTime = kvp.Value;
oldestKey = kvp.Key;
}
}
if (oldestKey != null)
{
dependencyCache.Remove(oldestKey);
cacheTimestamps.Remove(oldestKey);
}
}
dependencyCache[bundleName] = dependencies;
cacheTimestamps[bundleName] = DateTime.Now;
}
/// <summary>
/// 处理循环依赖
/// </summary>
private void HandleCircularDependency(string bundleName)
{
LogError($"检测到循环依赖: {bundleName}");
if (autoResolveCircularDependencies)
{
LogWarning($"尝试自动修复循环依赖: {bundleName}");
// 这里可以实现自动修复逻辑,比如移除最弱的依赖
// 暂时只是记录日志
}
OnCircularDependencyDetected?.Invoke(bundleName);
}
/// <summary>
/// 处理依赖加载队列
/// </summary>
private IEnumerator ProcessDependencyQueue()
{
while (true)
{
DependencyLoadTask task = null;
lock (loadQueue)
{
if (loadQueue.Count > 0)
{
task = loadQueue.Dequeue();
}
}
if (task != null)
{
yield return StartCoroutine(ExecuteDependencyLoadTask(task));
}
yield return null;
}
}
/// <summary>
/// 执行依赖加载任务
/// </summary>
private IEnumerator ExecuteDependencyLoadTask(DependencyLoadTask task)
{
Log($"开始执行依赖加载任务: {task.bundleName}");
try
{
// 获取加载顺序
var loadOrder = dependencyGraph.GetLoadOrder(task.dependencies);
// 按顺序加载依赖
foreach (string dep in loadOrder)
{
if (!IsBundleLoaded(dep))
{
yield return LoadDependencyAsync(dep);
}
}
// 加载目标Bundle
yield return LoadTargetBundle(task);
stats.tasksCompleted++;
OnStatsUpdated?.Invoke(stats);
}
catch (Exception e)
{
task.onError?.Invoke($"依赖加载失败: {e.Message}");
stats.tasksFailed++;
}
}
/// <summary>
/// 异步加载依赖
/// </summary>
private IEnumerator LoadDependencyAsync(string bundleName)
{
// 这里应该调用AssetBundleManager的加载方法
// 为了简化示例,这里使用伪代码
yield return null;
}
/// <summary>
/// 加载目标Bundle
/// </summary>
private IEnumerator LoadTargetBundle(DependencyLoadTask task)
{
// 这里应该调用AssetBundleManager的加载方法
yield return null;
// 模拟加载完成
task.onComplete?.Invoke(null);
}
/// <summary>
/// 构建依赖树(递归)
/// </summary>
private void BuildDependencyTreeRecursive(string bundleName,
DependencyTree tree,
int depth,
HashSet<string> visited)
{
if (visited.Contains(bundleName))
return;
visited.Add(bundleName);
var node = new DependencyTreeNode
{
bundleName = bundleName,
depth = depth
};
tree.nodes.Add(node);
string[] dependencies = manifest.GetAllDependencies(bundleName);
foreach (string dep in dependencies)
{
node.dependencies.Add(dep);
BuildDependencyTreeRecursive(dep, tree, depth + 1, visited);
}
}
/// <summary>
/// 获取所有节点
/// </summary>
private List<string> GetAllNodes()
{
return new List<string>(dependencyCache.Keys);
}
/// <summary>
/// 获取直接依赖者
/// </summary>
private List<string> GetDirectDependents(string bundleName)
{
return dependencyGraph.GetDirectDependents(bundleName);
}
/// <summary>
/// 检查Bundle是否已加载
/// </summary>
private bool IsBundleLoaded(string bundleName)
{
// 这里应该调用AssetBundleManager的检查方法
return false;
}
/// <summary>
/// 计算健康度分数
/// </summary>
private float CalculateHealthScore(DependencyHealthReport report)
{
float score = 100f;
// 扣分项
score -= report.circularDependencies * 10f;
score -= report.deepDependencies * 5f;
score -= report.highCouplingBundles * 3f;
// 问题Bundle比例扣分
float problemRatio = (float)report.problematicBundles.Count / report.totalBundles;
score -= problemRatio * 30f;
return Mathf.Clamp(score, 0f, 100f);
}
/// <summary>
/// 预加载常用依赖
/// </summary>
private IEnumerator PreloadCommonDependencies()
{
Log("开始预加载常用依赖...");
foreach (string dep in commonDependencies)
{
if (dependencyGraph.GetNode(dep) != null)
{
yield return LoadDependencyAsync(dep);
}
}
Log("常用依赖预加载完成");
}
/// <summary>
/// 合并常用依赖
/// </summary>
private IEnumerator MergeCommonDependencies(List<string> frequentlyUsedBundles)
{
// 分析常用依赖
Dictionary<string, int> dependencyFrequency = new Dictionary<string, int>();
foreach (string bundle in frequentlyUsedBundles)
{
var deps = ResolveDependencies(bundle).dependencies;
foreach (string dep in deps)
{
dependencyFrequency.TryGetValue(dep, out int count);
dependencyFrequency[dep] = count + 1;
}
}
// 找出高频依赖
var highFrequencyDeps = dependencyFrequency
.Where(kvp => kvp.Value > frequentlyUsedBundles.Count * 0.3f) // 30%以上的Bundle使用
.Select(kvp => kvp.Key)
.ToList();
Log($"找到 {highFrequencyDeps.Count} 个高频依赖");
// 这里可以实现合并逻辑,比如创建共享Bundle
yield return null;
}
/// <summary>
/// 重构深度依赖
/// </summary>
private IEnumerator RefactorDeepDependencies()
{
// 找出深度过大的依赖链
List<string> deepChains = new List<string>();
foreach (var bundle in GetAllNodes())
{
int depth = CalculateDependencyDepth(bundle, new HashSet<string>());
if (depth > 5) // 深度大于5的依赖链
{
deepChains.Add(bundle);
}
}
Log($"找到 {deepChains.Count} 个深度依赖链");
// 重构逻辑:将深度依赖拆分为多个中间Bundle
yield return null;
}
/// <summary>
/// 清理无用依赖
/// </summary>
private IEnumerator CleanupUnusedDependencies()
{
// 找出没有被任何Bundle依赖的叶子节点
List<string> unusedDeps = new List<string>();
foreach (var bundle in GetAllNodes())
{
var dependents = dependencyGraph.GetAllDependents(bundle);
if (dependents.Count == 0)
{
unusedDeps.Add(bundle);
}
}
Log($"找到 {unusedDeps.Count} 个无用依赖");
// 清理逻辑:标记或移除无用依赖
yield return null;
}
/// <summary>
/// 日志输出
/// </summary>
private void Log(string message)
{
if (enableLog)
Debug.Log($"[DependencyManager] {message}");
}
private void LogWarning(string message)
{
Debug.LogWarning($"[DependencyManager] {message}");
}
private void LogError(string message)
{
Debug.LogError($"[DependencyManager] {message}");
}
#endregion
#region 辅助类
/// <summary>
/// 依赖关系解析结果
/// </summary>
public class DependencyResult
{
public bool success;
public List<string> dependencies;
public List<string> directDependencies;
public string error;
public int dependencyDepth;
public bool fromCache;
}
/// <summary>
/// 依赖树节点
/// </summary>
public class DependencyTreeNode
{
public string bundleName;
public int depth;
public List<string> dependencies = new List<string>();
}
/// <summary>
/// 依赖树
/// </summary>
public class DependencyTree
{
public string rootBundle;
public List<DependencyTreeNode> nodes;
public void PrintTree()
{
foreach (var node in nodes)
{
string indent = new string(' ', node.depth * 2);
Debug.Log($"{indent}{node.bundleName}");
}
}
}
/// <summary>
/// 依赖健康度报告
/// </summary>
public class DependencyHealthReport
{
public int totalBundles;
public int circularDependencies;
public int deepDependencies; // 深度超过限制
public int highCouplingBundles; // 高耦合Bundle
public List<string> problematicBundles;
public float healthScore; // 健康度分数 0-100
public DateTime analyzedAt;
public void PrintReport()
{
Debug.Log($"=== 依赖健康度报告 ===");
Debug.Log($"分析时间: {analyzedAt}");
Debug.Log($"总Bundle数: {totalBundles}");
Debug.Log($"循环依赖: {circularDependencies}");
Debug.Log($"深度依赖: {deepDependencies}");
Debug.Log($"高耦合Bundle: {highCouplingBundles}");
Debug.Log($"问题Bundle数: {problematicBundles?.Count ?? 0}");
Debug.Log($"健康度分数: {healthScore:F1}/100");
if (problematicBundles != null && problematicBundles.Count > 0)
{
Debug.Log("问题Bundle列表:");
foreach (string bundle in problematicBundles)
{
Debug.Log($" - {bundle}");
}
}
}
}
/// <summary>
/// 依赖统计信息
/// </summary>
public class DependencyStats
{
public int dependencyResolutions;
public int cacheHits;
public int tasksSubmitted;
public int tasksCompleted;
public int tasksFailed;
public DateTime startTime = DateTime.Now;
public TimeSpan Uptime => DateTime.Now - startTime;
public void Reset()
{
dependencyResolutions = 0;
cacheHits = 0;
tasksSubmitted = 0;
tasksCompleted = 0;
tasksFailed = 0;
startTime = DateTime.Now;
}
public void PrintStats()
{
Debug.Log($"=== 依赖统计信息 ===");
Debug.Log($"运行时间: {Uptime:hh\\:mm\\:ss}");
Debug.Log($"依赖解析次数: {dependencyResolutions}");
Debug.Log($"缓存命中次数: {cacheHits}");
Debug.Log($"任务提交数: {tasksSubmitted}");
Debug.Log($"任务完成数: {tasksCompleted}");
Debug.Log($"任务失败数: {tasksFailed}");
if (dependencyResolutions > 0)
{
float hitRate = (float)cacheHits / dependencyResolutions * 100;
Debug.Log($"缓存命中率: {hitRate:F1}%");
}
}
}
/// <summary>
/// 优先级队列
/// </summary>
public class PriorityQueue<T>
{
private List<T> data;
private IComparer<T> comparer;
public PriorityQueue(IComparer<T> comparer)
{
this.data = new List<T>();
this.comparer = comparer;
}
public void Enqueue(T item)
{
data.Add(item);
int i = data.Count - 1;
while (i > 0)
{
int parent = (i - 1) / 2;
if (comparer.Compare(data[i], data[parent]) <= 0)
break;
Swap(i, parent);
i = parent;
}
}
public T Dequeue()
{
if (data.Count == 0) return default(T);
T frontItem = data[0];
int lastIndex = data.Count - 1;
data[0] = data[lastIndex];
data.RemoveAt(lastIndex);
lastIndex--;
int parent = 0;
while (true)
{
int leftChild = parent * 2 + 1;
if (leftChild > lastIndex) break;
int rightChild = leftChild + 1;
int maxChild = leftChild;
if (rightChild <= lastIndex &&
comparer.Compare(data[rightChild], data[leftChild]) > 0)
{
maxChild = rightChild;
}
if (comparer.Compare(data[parent], data[maxChild]) >= 0)
break;
Swap(parent, maxChild);
parent = maxChild;
}
return frontItem;
}
public T Peek()
{
return data.Count > 0 ? data[0] : default(T);
}
public int Count => data.Count;
private void Swap(int i, int j)
{
T temp = data[i];
data[i] = data[j];
data[j] = temp;
}
}
#endregion
#region Unity生命周期
void OnApplicationQuit()
{
// 保存统计信息
stats.PrintStats();
// 生成健康度报告
var report = AnalyzeDependencyHealth();
report.PrintReport();
}
void OnDestroy()
{
dependencyGraph.Clear();
ClearCache();
}
#endregion
}
4.2 依赖关系优化策略
csharp
// 1. 共享资源打包策略
public class BundlePackingStrategy
{
/*
推荐打包策略:
- 公共资源(UI字体、通用材质) → common.bundle
- 场景专用资源 → scene_xxx.bundle
- 角色模型/动画 → character_xxx.bundle
- 按需加载的配置表 → config.bundle
*/
// 2. Bundle 粒度控制
/*
小Bundle优点:加载快、内存占用小
大Bundle优点:依赖少、管理简单
推荐方案:
- 单个Bundle < 10MB
- 同类型资源打包在一起
- 频繁更新资源独立打包
*/
}
五、内存管理与卸载
5.1 正确的卸载策略
csharp
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.ResourceManagement.AsyncOperations;
/// <summary>
/// 内存压力级别
/// </summary>
public enum MemoryPressureLevel
{
Low, // 低压力
Normal, // 正常
High, // 高压力
Critical // 临界压力
}
/// <summary>
/// 内存清理策略
/// </summary>
public enum MemoryCleanupStrategy
{
Conservative, // 保守:只清理无引用的资源
Aggressive, // 激进:清理长期未使用的资源
Custom // 自定义策略
}
/// <summary>
/// 内存监控配置
/// </summary>
[System.Serializable]
public class MemoryMonitorConfig
{
[Header("内存阈值配置")]
[Tooltip("低内存警告阈值(MB)")]
public float lowMemoryThresholdMB = 300f;
[Tooltip("高内存警告阈值(MB)")]
public float highMemoryThresholdMB = 500f;
[Tooltip("临界内存阈值(MB)")]
public float criticalMemoryThresholdMB = 700f;
[Tooltip("目标内存限制(MB)")]
public float targetMemoryLimitMB = 400f;
[Header("自动清理配置")]
[Tooltip("自动清理间隔(秒)")]
public float autoCleanupInterval = 30f;
[Tooltip("未使用资源保留时间(秒)")]
public float unusedAssetRetentionTime = 60f;
[Tooltip("最小清理间隔(秒)")]
public float minCleanupInterval = 10f;
[Tooltip("内存压力检测频率(秒)")]
public float pressureCheckInterval = 5f;
[Header("清理策略")]
[Tooltip("默认清理策略")]
public MemoryCleanupStrategy defaultStrategy = MemoryCleanupStrategy.Conservative;
[Tooltip("高压力时自动切换到激进清理")]
public bool autoSwitchToAggressive = true;
[Header("性能优化")]
[Tooltip("启用内存碎片整理")]
public bool enableDefragmentation = true;
[Tooltip("最大同时清理操作数")]
public int maxConcurrentCleanups = 3;
[Tooltip("帧预算(每帧清理时间限制ms)")]
public float frameBudgetMS = 5f;
}
/// <summary>
/// 内存资源信息
/// </summary>
[System.Serializable]
public class MemoryResourceInfo
{
public string resourceId;
public string bundleName;
public long memorySize;
public int referenceCount;
public DateTime lastAccessTime;
public DateTime loadTime;
public bool isPersistent;
public string resourceType;
public List<string> references = new List<string>();
[NonSerialized]
public UnityEngine.Object resourceObject;
public TimeSpan Age => DateTime.Now - loadTime;
public TimeSpan IdleTime => DateTime.Now - lastAccessTime;
public MemoryResourceInfo(string id, string bundle, UnityEngine.Object obj, long size)
{
resourceId = id;
bundleName = bundle;
resourceObject = obj;
memorySize = size;
loadTime = DateTime.Now;
lastAccessTime = DateTime.Now;
referenceCount = 0;
isPersistent = false;
resourceType = obj.GetType().Name;
}
public void AddReference(string reference)
{
if (!references.Contains(reference))
{
references.Add(reference);
}
referenceCount++;
lastAccessTime = DateTime.Now;
}
public void RemoveReference(string reference)
{
if (references.Contains(reference))
{
references.Remove(reference);
}
referenceCount = Math.Max(0, referenceCount - 1);
}
public bool CanUnload(bool force = false)
{
if (isPersistent) return false;
if (force) return true;
return referenceCount <= 0;
}
}
/// <summary>
/// 内存使用报告
/// </summary>
[System.Serializable]
public class MemoryUsageReport
{
public DateTime reportTime;
public long totalMemoryBytes;
public long usedMemoryBytes;
public long freeMemoryBytes;
public float memoryUsagePercentage;
public MemoryPressureLevel pressureLevel;
public int totalResources;
public int loadedResources;
public int referencedResources;
public int leakedResources;
public List<MemoryLeakInfo> potentialLeaks = new List<MemoryLeakInfo>();
public MemoryBreakdown breakdown = new MemoryBreakdown();
public string ToFormattedString(bool detailed = false)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("=== 内存使用报告 ===");
sb.AppendLine($"报告时间: {reportTime:yyyy-MM-dd HH:mm:ss}");
sb.AppendLine($"总内存: {totalMemoryBytes / 1024 / 1024:F1}MB");
sb.AppendLine($"已用内存: {usedMemoryBytes / 1024 / 1024:F1}MB");
sb.AppendLine($"可用内存: {freeMemoryBytes / 1024 / 1024:F1}MB");
sb.AppendLine($"使用率: {memoryUsagePercentage:F1}%");
sb.AppendLine($"压力级别: {pressureLevel}");
sb.AppendLine($"资源总数: {totalResources}");
sb.AppendLine($"已加载资源: {loadedResources}");
sb.AppendLine($"引用资源: {referencedResources}");
sb.AppendLine($"疑似泄漏: {leakedResources}");
if (detailed)
{
sb.AppendLine("\n--- 内存分布 ---");
sb.AppendLine($"Textures: {breakdown.textureMemory / 1024 / 1024:F1}MB");
sb.AppendLine($"Meshes: {breakdown.meshMemory / 1024 / 1024:F1}MB");
sb.AppendLine($"Materials: {breakdown.materialMemory / 1024 / 1024:F1}MB");
sb.AppendLine($"Animations: {breakdown.animationMemory / 1024 / 1024:F1}MB");
sb.AppendLine($"AudioClips: {breakdown.audioMemory / 1024 / 1024:F1}MB");
sb.AppendLine($"AssetBundles: {breakdown.bundleMemory / 1024 / 1024:F1}MB");
sb.AppendLine($"Other: {breakdown.otherMemory / 1024 / 1024:F1}MB");
if (potentialLeaks.Count > 0)
{
sb.AppendLine("\n--- 潜在内存泄漏 ---");
foreach (var leak in potentialLeaks.Take(10))
{
sb.AppendLine($" {leak.resourceId} ({leak.resourceType}): {leak.memorySize / 1024:F1}KB, 年龄: {leak.age.TotalSeconds:F0}s");
}
}
}
return sb.ToString();
}
}
/// <summary>
/// 内存泄漏信息
/// </summary>
[System.Serializable]
public class MemoryLeakInfo
{
public string resourceId;
public string resourceType;
public long memorySize;
public TimeSpan age;
public DateTime loadTime;
public int referenceCount;
public List<string> referenceChain;
}
/// <summary>
/// 内存分布详情
/// </summary>
[System.Serializable]
public class MemoryBreakdown
{
public long textureMemory;
public long meshMemory;
public long materialMemory;
public long animationMemory;
public long audioMemory;
public long bundleMemory;
public long otherMemory;
public void Clear()
{
textureMemory = 0;
meshMemory = 0;
materialMemory = 0;
animationMemory = 0;
audioMemory = 0;
bundleMemory = 0;
otherMemory = 0;
}
}
/// <summary>
/// 内存清理任务
/// </summary>
public class MemoryCleanupTask
{
public string taskId;
public MemoryCleanupStrategy strategy;
public float targetMemoryMB;
public Action<float> onProgress;
public Action<bool, string> onComplete;
public List<string> resourcesToUnload = new List<string>();
public bool isRunning;
public float progress;
public MemoryCleanupTask(string id)
{
taskId = id;
isRunning = false;
progress = 0f;
}
}
/// <summary>
/// 完善的 MemoryManager 类
/// </summary>
public class MemoryManager : MonoBehaviour
{
#region 单例模式
private static MemoryManager _instance;
public static MemoryManager Instance
{
get
{
if (_instance == null)
{
GameObject go = new GameObject("MemoryManager");
_instance = go.AddComponent<MemoryManager>();
DontDestroyOnLoad(go);
}
return _instance;
}
}
#endregion
#region 配置参数
[Header("内存管理配置")]
[SerializeField] private MemoryMonitorConfig config = new MemoryMonitorConfig();
[Header("调试选项")]
[SerializeField] private bool enableLogging = true;
[SerializeField] private bool enableProfiling = true;
[SerializeField] private bool logMemoryChanges = false;
[SerializeField] private bool autoGenerateReports = true;
[SerializeField] private float reportInterval = 60f;
#endregion
#region 内部数据结构
// 资源管理
private Dictionary<string, MemoryResourceInfo> resourceRegistry = new Dictionary<string, MemoryResourceInfo>();
private Dictionary<string, List<string>> bundleResources = new Dictionary<string, List<string>>();
// 内存清理
private Queue<MemoryCleanupTask> cleanupQueue = new Queue<MemoryCleanupTask>();
private Dictionary<string, MemoryCleanupTask> activeCleanups = new Dictionary<string, MemoryCleanupTask>();
private MemoryCleanupStrategy currentStrategy;
// 内存监控
private MemoryPressureLevel currentPressure = MemoryPressureLevel.Low;
private long peakMemoryUsage = 0;
private DateTime lastCleanupTime;
private DateTime lastReportTime;
private float lastMemoryCheckTime;
// 统计信息
private MemoryStats stats = new MemoryStats();
private List<MemoryUsageReport> historicalReports = new List<MemoryUsageReport>();
// 事件系统
public event Action<MemoryPressureLevel> OnMemoryPressureChanged;
public event Action<MemoryUsageReport> OnMemoryReportGenerated;
public event Action<string, long> OnResourceLoaded;
public event Action<string, long> OnResourceUnloaded;
public event Action<MemoryCleanupTask> OnCleanupStarted;
public event Action<MemoryCleanupTask, bool> OnCleanupCompleted;
#endregion
#region 公共接口
/// <summary>
/// 初始化内存管理器
/// </summary>
public void Initialize()
{
if (config == null)
{
config = new MemoryMonitorConfig();
}
currentStrategy = config.defaultStrategy;
lastCleanupTime = DateTime.Now;
lastReportTime = DateTime.Now;
lastMemoryCheckTime = Time.time;
// 启动监控协程
StartCoroutine(MemoryMonitorRoutine());
StartCoroutine(AutoCleanupRoutine());
if (autoGenerateReports)
{
StartCoroutine(AutoReportRoutine());
}
Log("MemoryManager 初始化完成");
}
/// <summary>
/// 注册资源
/// </summary>
public void RegisterResource(string resourceId, string bundleName, UnityEngine.Object resource, long estimatedSize = 0)
{
if (string.IsNullOrEmpty(resourceId) || resource == null)
{
Debug.LogError($"无效的资源注册: {resourceId}");
return;
}
if (resourceRegistry.ContainsKey(resourceId))
{
UpdateResourceAccess(resourceId);
return;
}
long actualSize = estimatedSize > 0 ? estimatedSize : EstimateMemorySize(resource);
MemoryResourceInfo info = new MemoryResourceInfo(resourceId, bundleName, resource, actualSize);
resourceRegistry[resourceId] = info;
// 关联到Bundle
if (!string.IsNullOrEmpty(bundleName))
{
if (!bundleResources.ContainsKey(bundleName))
bundleResources[bundleName] = new List<string>();
if (!bundleResources[bundleName].Contains(resourceId))
bundleResources[bundleName].Add(resourceId);
}
stats.totalResourcesRegistered++;
stats.totalMemoryAllocated += actualSize;
if (logMemoryChanges)
{
Log($"资源注册: {resourceId}, 大小: {actualSize / 1024:F1}KB, 类型: {resource.GetType().Name}");
}
OnResourceLoaded?.Invoke(resourceId, actualSize);
}
/// <summary>
/// 增加资源引用
/// </summary>
public void AddResourceReference(string resourceId, string referenceSource)
{
if (resourceRegistry.TryGetValue(resourceId, out MemoryResourceInfo info))
{
info.AddReference(referenceSource);
stats.referenceOperations++;
if (logMemoryChanges)
{
Log($"增加引用: {resourceId} <- {referenceSource}, 引用数: {info.referenceCount}");
}
}
}
/// <summary>
/// 移除资源引用
/// </summary>
public void RemoveResourceReference(string resourceId, string referenceSource)
{
if (resourceRegistry.TryGetValue(resourceId, out MemoryResourceInfo info))
{
info.RemoveReference(referenceSource);
stats.referenceOperations++;
if (logMemoryChanges && info.referenceCount == 0)
{
Log($"资源无引用: {resourceId}, 空闲: {info.IdleTime.TotalSeconds:F1}s");
}
}
}
/// <summary>
/// 更新资源访问时间
/// </summary>
public void UpdateResourceAccess(string resourceId)
{
if (resourceRegistry.TryGetValue(resourceId, out MemoryResourceInfo info))
{
info.lastAccessTime = DateTime.Now;
stats.accessOperations++;
}
}
/// <summary>
/// 设置资源常驻内存
/// </summary>
public void SetResourcePersistent(string resourceId, bool persistent)
{
if (resourceRegistry.TryGetValue(resourceId, out MemoryResourceInfo info))
{
info.isPersistent = persistent;
Log($"资源常驻设置: {resourceId} = {persistent}");
}
}
/// <summary>
/// 卸载单个资源
/// </summary>
public bool UnloadResource(string resourceId, bool force = false)
{
if (!resourceRegistry.TryGetValue(resourceId, out MemoryResourceInfo info))
{
LogWarning($"尝试卸载未注册的资源: {resourceId}");
return false;
}
if (!info.CanUnload(force))
{
LogWarning($"资源 {resourceId} 仍有引用 ({info.referenceCount}),无法卸载");
return false;
}
// 执行卸载
if (info.resourceObject != null)
{
if (info.resourceObject is GameObject gameObject)
{
if (gameObject != null)
Destroy(gameObject);
}
else
{
Resources.UnloadAsset(info.resourceObject);
}
stats.totalMemoryFreed += info.memorySize;
stats.resourcesUnloaded++;
if (logMemoryChanges)
{
Log($"资源卸载: {resourceId}, 释放: {info.memorySize / 1024:F1}KB");
}
OnResourceUnloaded?.Invoke(resourceId, info.memorySize);
}
// 清理注册信息
if (!string.IsNullOrEmpty(info.bundleName) &&
bundleResources.ContainsKey(info.bundleName))
{
bundleResources[info.bundleName].Remove(resourceId);
if (bundleResources[info.bundleName].Count == 0)
{
bundleResources.Remove(info.bundleName);
}
}
resourceRegistry.Remove(resourceId);
return true;
}
/// <summary>
/// 卸载Bundle所有资源
/// </summary>
public int UnloadBundleResources(string bundleName, bool force = false)
{
if (!bundleResources.TryGetValue(bundleName, out List<string> resources))
{
return 0;
}
int unloadedCount = 0;
List<string> resourcesToUnload = new List<string>(resources);
foreach (string resourceId in resourcesToUnload)
{
if (UnloadResource(resourceId, force))
{
unloadedCount++;
}
}
Log($"卸载Bundle资源: {bundleName}, 成功: {unloadedCount}/{resourcesToUnload.Count}");
return unloadedCount;
}
/// <summary>
/// 执行内存清理
/// </summary>
public string RequestMemoryCleanup(MemoryCleanupStrategy strategy = MemoryCleanupStrategy.Custom,
float targetMemoryMB = -1f,
Action<float> onProgress = null,
Action<bool, string> onComplete = null)
{
string taskId = $"cleanup_{DateTime.Now:HHmmssfff}";
MemoryCleanupTask task = new MemoryCleanupTask(taskId)
{
strategy = strategy == MemoryCleanupStrategy.Custom ? currentStrategy : strategy,
targetMemoryMB = targetMemoryMB > 0 ? targetMemoryMB : config.targetMemoryLimitMB,
onProgress = onProgress,
onComplete = onComplete
};
lock (cleanupQueue)
{
cleanupQueue.Enqueue(task);
}
Log($"内存清理任务提交: {taskId}, 策略: {task.strategy}, 目标: {task.targetMemoryMB}MB");
return taskId;
}
/// <summary>
/// 强制GC和资源清理
/// </summary>
public IEnumerator ForceCleanup(bool unloadAllUnused = true)
{
Log("开始强制内存清理...");
yield return StartCoroutine(CleanupUnusedResources(unloadAllUnused));
// 触发GC
GC.Collect();
GC.WaitForPendingFinalizers();
// 等待一帧
yield return null;
Log("强制内存清理完成");
}
/// <summary>
/// 获取内存使用报告
/// </summary>
public MemoryUsageReport GetMemoryUsageReport(bool detailed = false)
{
MemoryUsageReport report = new MemoryUsageReport
{
reportTime = DateTime.Now,
pressureLevel = currentPressure
};
// 获取系统内存信息
long totalMemory = SystemInfo.systemMemorySize * 1024L * 1024L;
long usedMemory = Profiler.GetTotalAllocatedMemoryLong();
long freeMemory = totalMemory - usedMemory;
report.totalMemoryBytes = totalMemory;
report.usedMemoryBytes = usedMemory;
report.freeMemoryBytes = freeMemory;
report.memoryUsagePercentage = totalMemory > 0 ? (float)usedMemory / totalMemory * 100 : 0;
// 统计资源信息
report.totalResources = resourceRegistry.Count;
report.loadedResources = resourceRegistry.Count;
report.referencedResources = resourceRegistry.Values.Count(r => r.referenceCount > 0);
// 分析内存分布
AnalyzeMemoryBreakdown(report.breakdown);
// 检测潜在内存泄漏
DetectPotentialLeaks(report);
// 更新峰值内存
if (usedMemory > peakMemoryUsage)
{
peakMemoryUsage = usedMemory;
}
// 添加到历史记录
historicalReports.Add(report);
// 触发事件
OnMemoryReportGenerated?.Invoke(report);
return report;
}
/// <summary>
/// 获取资源信息
/// </summary>
public MemoryResourceInfo GetResourceInfo(string resourceId)
{
resourceRegistry.TryGetValue(resourceId, out MemoryResourceInfo info);
return info;
}
/// <summary>
/// 获取Bundle资源列表
/// </summary>
public List<string> GetBundleResources(string bundleName)
{
if (bundleResources.TryGetValue(bundleName, out List<string> resources))
{
return new List<string>(resources);
}
return new List<string>();
}
/// <summary>
/// 获取所有资源ID
/// </summary>
public List<string> GetAllResourceIds()
{
return new List<string>(resourceRegistry.Keys);
}
/// <summary>
/// 检查内存压力
/// </summary>
public MemoryPressureLevel CheckMemoryPressure()
{
long usedMemoryMB = Profiler.GetTotalAllocatedMemoryLong() / 1024 / 1024;
if (usedMemoryMB >= config.criticalMemoryThresholdMB)
{
return MemoryPressureLevel.Critical;
}
else if (usedMemoryMB >= config.highMemoryThresholdMB)
{
return MemoryPressureLevel.High;
}
else if (usedMemoryMB >= config.lowMemoryThresholdMB)
{
return MemoryPressureLevel.Normal;
}
else
{
return MemoryPressureLevel.Low;
}
}
/// <summary>
/// 更改清理策略
/// </summary>
public void SetCleanupStrategy(MemoryCleanupStrategy strategy)
{
currentStrategy = strategy;
Log($"清理策略更改为: {strategy}");
}
/// <summary>
/// 获取统计信息
/// </summary>
public MemoryStats GetStats()
{
return stats;
}
/// <summary>
/// 重置统计信息
/// </summary>
public void ResetStats()
{
stats = new MemoryStats();
Log("统计信息已重置");
}
/// <summary>
/// 导出内存报告
/// </summary>
public string ExportMemoryReport(bool includeHistory = false)
{
StringBuilder sb = new StringBuilder();
// 当前报告
var currentReport = GetMemoryUsageReport(true);
sb.AppendLine(currentReport.ToFormattedString(true));
// 统计信息
sb.AppendLine("\n=== 内存统计信息 ===");
sb.AppendLine($"峰值内存使用: {peakMemoryUsage / 1024 / 1024:F1}MB");
sb.AppendLine($"总注册资源数: {stats.totalResourcesRegistered}");
sb.AppendLine($"总卸载资源数: {stats.resourcesUnloaded}");
sb.AppendLine($"总分配内存: {stats.totalMemoryAllocated / 1024 / 1024:F1}MB");
sb.AppendLine($"总释放内存: {stats.totalMemoryFreed / 1024 / 1024:F1}MB");
sb.AppendLine($"引用操作次数: {stats.referenceOperations}");
sb.AppendLine($"访问操作次数: {stats.accessOperations}");
sb.AppendLine($"自动清理次数: {stats.autoCleanupsPerformed}");
sb.AppendLine($"内存警告次数: {stats.memoryWarnings}");
// 历史报告(如果需要)
if (includeHistory && historicalReports.Count > 0)
{
sb.AppendLine("\n=== 历史报告摘要 ===");
int count = Mathf.Min(5, historicalReports.Count);
for (int i = historicalReports.Count - count; i < historicalReports.Count; i++)
{
var report = historicalReports[i];
sb.AppendLine($"[{report.reportTime:HH:mm:ss}] {report.memoryUsagePercentage:F1}%, {report.pressureLevel}");
}
}
// 资源详情(前20个最大的资源)
sb.AppendLine("\n=== 最大内存资源(前20) ===");
var topResources = resourceRegistry.Values
.OrderByDescending(r => r.memorySize)
.Take(20)
.ToList();
foreach (var resource in topResources)
{
sb.AppendLine($" {resource.resourceId}: {resource.memorySize / 1024:F1}KB, " +
$"引用: {resource.referenceCount}, " +
$"年龄: {resource.Age.TotalMinutes:F0}m, " +
$"类型: {resource.resourceType}");
}
return sb.ToString();
}
/// <summary>
/// 优化内存使用
/// </summary>
public IEnumerator OptimizeMemoryUsage()
{
Log("开始内存优化...");
// 1. 卸载长期未使用的资源
yield return StartCoroutine(CleanupIdleResources());
// 2. 整理内存碎片
if (config.enableDefragmentation)
{
yield return StartCoroutine(DefragmentMemory());
}
// 3. 压缩纹理(如果支持)
yield return StartCoroutine(CompressTextures());
// 4. 清理AssetBundle缓存
yield return StartCoroutine(CleanupAssetBundleCache());
Log("内存优化完成");
}
#endregion
#region 内部实现
/// <summary>
/// 内存监控协程
/// </summary>
private IEnumerator MemoryMonitorRoutine()
{
while (true)
{
yield return new WaitForSeconds(config.pressureCheckInterval);
// 检查内存压力
MemoryPressureLevel newPressure = CheckMemoryPressure();
if (newPressure != currentPressure)
{
currentPressure = newPressure;
OnMemoryPressureChanged?.Invoke(currentPressure);
Log($"内存压力级别变化: {currentPressure}");
// 高压力时自动切换清理策略
if (config.autoSwitchToAggressive &&
currentPressure >= MemoryPressureLevel.High)
{
SetCleanupStrategy(MemoryCleanupStrategy.Aggressive);
// 自动触发清理
RequestMemoryCleanup(MemoryCleanupStrategy.Aggressive);
}
if (currentPressure >= MemoryPressureLevel.High)
{
stats.memoryWarnings++;
}
}
// 记录内存峰值
long currentMemory = Profiler.GetTotalAllocatedMemoryLong();
if (currentMemory > peakMemoryUsage)
{
peakMemoryUsage = currentMemory;
}
}
}
/// <summary>
/// 自动清理协程
/// </summary>
private IEnumerator AutoCleanupRoutine()
{
while (true)
{
yield return new WaitForSeconds(config.autoCleanupInterval);
// 检查是否需要清理
if (ShouldPerformAutoCleanup())
{
Log("执行自动内存清理...");
string taskId = RequestMemoryCleanup(currentStrategy,
onComplete: (success, message) => {
if (success)
{
stats.autoCleanupsPerformed++;
Log($"自动清理完成: {message}");
}
});
lastCleanupTime = DateTime.Now;
}
// 处理清理队列
yield return StartCoroutine(ProcessCleanupQueue());
}
}
/// <summary>
/// 自动报告协程
/// </summary>
private IEnumerator AutoReportRoutine()
{
while (true)
{
yield return new WaitForSeconds(reportInterval);
if (currentPressure >= MemoryPressureLevel.High ||
(DateTime.Now - lastReportTime).TotalMinutes >= 5)
{
var report = GetMemoryUsageReport();
Log(report.ToFormattedString(false));
lastReportTime = DateTime.Now;
}
}
}
/// <summary>
/// 处理清理队列
/// </summary>
private IEnumerator ProcessCleanupQueue()
{
while (cleanupQueue.Count > 0 && activeCleanups.Count < config.maxConcurrentCleanups)
{
MemoryCleanupTask task;
lock (cleanupQueue)
{
task = cleanupQueue.Dequeue();
}
activeCleanups[task.taskId] = task;
StartCoroutine(ExecuteCleanupTask(task));
yield return null;
}
}
/// <summary>
/// 执行清理任务
/// </summary>
private IEnumerator ExecuteCleanupTask(MemoryCleanupTask task)
{
task.isRunning = true;
OnCleanupStarted?.Invoke(task);
Log($"开始执行清理任务: {task.taskId}");
try
{
// 根据策略选择资源卸载
SelectResourcesForCleanup(task);
int totalResources = task.resourcesToUnload.Count;
int unloadedCount = 0;
task.progress = 0f;
task.onProgress?.Invoke(0f);
// 分批卸载资源,避免卡顿
for (int i = 0; i < totalResources; i++)
{
string resourceId = task.resourcesToUnload[i];
if (UnloadResource(resourceId, false))
{
unloadedCount++;
}
// 更新进度
task.progress = (float)(i + 1) / totalResources;
task.onProgress?.Invoke(task.progress);
// 每卸载10个资源检查一次帧时间
if (i % 10 == 0)
{
yield return null;
}
}
// 执行GC
yield return StartCoroutine(CleanupUnusedResources(false));
// 检查是否达到目标
bool success = CheckCleanupSuccess(task);
string message = $"清理完成: {unloadedCount}/{totalResources} 个资源已卸载";
task.onComplete?.Invoke(success, message);
OnCleanupCompleted?.Invoke(task, success);
Log($"清理任务完成: {task.taskId}, {message}");
}
catch (Exception e)
{
string error = $"清理任务失败: {e.Message}";
task.onComplete?.Invoke(false, error);
OnCleanupCompleted?.Invoke(task, false);
LogError(error);
}
finally
{
task.isRunning = false;
activeCleanups.Remove(task.taskId);
}
}
/// <summary>
/// 选择需要清理的资源
/// </summary>
private void SelectResourcesForCleanup(MemoryCleanupTask task)
{
task.resourcesToUnload.Clear();
List<MemoryResourceInfo> candidates = new List<MemoryResourceInfo>();
foreach (var kvp in resourceRegistry)
{
MemoryResourceInfo info = kvp.Value;
// 跳过常驻资源
if (info.isPersistent)
continue;
// 根据策略筛选
switch (task.strategy)
{
case MemoryCleanupStrategy.Conservative:
// 只清理无引用的资源
if (info.referenceCount <= 0)
{
candidates.Add(info);
}
break;
case MemoryCleanupStrategy.Aggressive:
// 清理长期未使用的资源,即使有引用
if (info.IdleTime.TotalSeconds > config.unusedAssetRetentionTime * 2)
{
candidates.Add(info);
}
else if (info.referenceCount <= 0)
{
candidates.Add(info);
}
break;
}
}
// 排序:按最后访问时间(最旧优先)或内存大小(最大优先)
if (task.strategy == MemoryCleanupStrategy.Aggressive)
{
candidates.Sort((a, b) => {
// 优先清理无引用的
if (a.referenceCount == 0 && b.referenceCount > 0) return -1;
if (a.referenceCount > 0 && b.referenceCount == 0) return 1;
// 然后按内存大小降序
return b.memorySize.CompareTo(a.memorySize);
});
}
else
{
candidates.Sort((a, b) => a.lastAccessTime.CompareTo(b.lastAccessTime));
}
// 选择资源直到达到目标内存或选择所有候选
long targetMemoryBytes = (long)(task.targetMemoryMB * 1024 * 1024);
long currentMemory = Profiler.GetTotalAllocatedMemoryLong();
long memoryToFree = currentMemory - targetMemoryBytes;
if (memoryToFree > 0)
{
long freedMemory = 0;
foreach (var info in candidates)
{
if (freedMemory >= memoryToFree)
break;
task.resourcesToUnload.Add(info.resourceId);
freedMemory += info.memorySize;
}
}
else
{
// 如果不需要释放内存,仍然清理一些最旧的无引用资源
foreach (var info in candidates)
{
if (info.referenceCount <= 0 &&
info.IdleTime.TotalSeconds > config.unusedAssetRetentionTime)
{
task.resourcesToUnload.Add(info.resourceId);
// 最多清理10个
if (task.resourcesToUnload.Count >= 10)
break;
}
}
}
}
/// <summary>
/// 检查清理是否成功
/// </summary>
private bool CheckCleanupSuccess(MemoryCleanupTask task)
{
long currentMemoryMB = Profiler.GetTotalAllocatedMemoryLong() / 1024 / 1024;
if (task.targetMemoryMB > 0)
{
return currentMemoryMB <= task.targetMemoryMB;
}
return task.resourcesToUnload.Count > 0;
}
/// <summary>
/// 清理未使用的资源
/// </summary>
private IEnumerator CleanupUnusedResources(bool unloadAll)
{
AsyncOperation asyncOp = Resources.UnloadUnusedAssets();
while (!asyncOp.isDone)
{
yield return null;
}
if (unloadAll)
{
// 强制GC
GC.Collect();
yield return null;
}
}
/// <summary>
/// 清理空闲资源
/// </summary>
private IEnumerator CleanupIdleResources()
{
List<string> idleResources = new List<string>();
foreach (var kvp in resourceRegistry)
{
MemoryResourceInfo info = kvp.Value;
if (!info.isPersistent &&
info.referenceCount <= 0 &&
info.IdleTime.TotalSeconds > config.unusedAssetRetentionTime)
{
idleResources.Add(kvp.Key);
}
}
int unloaded = 0;
foreach (string resourceId in idleResources)
{
if (UnloadResource(resourceId, false))
{
unloaded++;
if (unloaded % 5 == 0)
{
yield return null;
}
}
}
if (unloaded > 0)
{
Log($"清理空闲资源: {unloaded} 个资源已卸载");
}
}
/// <summary>
/// 内存碎片整理
/// </summary>
private IEnumerator DefragmentMemory()
{
Log("开始内存碎片整理...");
// 1. 卸载所有非必要资源
yield return StartCoroutine(CleanupIdleResources());
// 2. 触发GC
GC.Collect();
GC.WaitForPendingFinalizers();
// 3. 等待几帧让内存稳定
for (int i = 0; i < 3; i++)
{
yield return null;
}
Log("内存碎片整理完成");
}
/// <summary>
/// 压缩纹理(如果支持)
/// </summary>
private IEnumerator CompressTextures()
{
// 这里可以实现纹理压缩逻辑
// 注意:纹理压缩可能会影响性能,需要谨慎使用
yield return null;
}
/// <summary>
/// 清理AssetBundle缓存
/// </summary>
private IEnumerator CleanupAssetBundleCache()
{
// 清理未使用的AssetBundle
Caching.ClearCache();
yield return null;
Log("AssetBundle缓存已清理");
}
/// <summary>
/// 分析内存分布
/// </summary>
private void AnalyzeMemoryBreakdown(MemoryBreakdown breakdown)
{
breakdown.Clear();
foreach (var info in resourceRegistry.Values)
{
switch (info.resourceType)
{
case "Texture2D":
case "Texture":
breakdown.textureMemory += info.memorySize;
break;
case "Mesh":
breakdown.meshMemory += info.memorySize;
break;
case "Material":
breakdown.materialMemory += info.memorySize;
break;
case "AnimationClip":
breakdown.animationMemory += info.memorySize;
break;
case "AudioClip":
breakdown.audioMemory += info.memorySize;
break;
case "AssetBundle":
breakdown.bundleMemory += info.memorySize;
break;
default:
breakdown.otherMemory += info.memorySize;
break;
}
}
}
/// <summary>
/// 检测潜在内存泄漏
/// </summary>
private void DetectPotentialLeaks(MemoryUsageReport report)
{
report.potentialLeaks.Clear();
foreach (var info in resourceRegistry.Values)
{
// 检测标准:无引用但长期未卸载
if (info.referenceCount <= 0 && info.Age.TotalSeconds > config.unusedAssetRetentionTime * 3)
{
var leak = new MemoryLeakInfo
{
resourceId = info.resourceId,
resourceType = info.resourceType,
memorySize = info.memorySize,
age = info.Age,
loadTime = info.loadTime,
referenceCount = info.referenceCount,
referenceChain = new List<string>(info.references)
};
report.potentialLeaks.Add(leak);
report.leakedResources++;
}
}
}
/// <summary>
/// 估计资源内存大小
/// </summary>
private long EstimateMemorySize(UnityEngine.Object obj)
{
if (obj == null) return 0;
try
{
if (obj is Texture2D texture)
{
return texture.width * texture.height * 4; // 假设RGBA32格式
}
else if (obj is Mesh mesh)
{
int vertexSize = mesh.vertexCount * 12; // Vector3 = 12 bytes
int triangleSize = mesh.triangles.Length * 4; // int = 4 bytes
return vertexSize + triangleSize;
}
else if (obj is AudioClip audioClip)
{
return (long)(audioClip.length * audioClip.frequency * 2 * audioClip.channels);
}
else if (obj is GameObject gameObject)
{
// 估算GameObject的内存比较复杂,这里返回一个基础值
return 1024; // 1KB基础值
}
else
{
// 其他类型的基础估计
return 512;
}
}
catch
{
return 1024; // 默认1KB
}
}
/// <summary>
/// 检查是否应该执行自动清理
/// </summary>
private bool ShouldPerformAutoCleanup()
{
// 检查时间间隔
if ((DateTime.Now - lastCleanupTime).TotalSeconds < config.minCleanupInterval)
return false;
// 检查内存压力
MemoryPressureLevel pressure = CheckMemoryPressure();
// 高压力时总是清理
if (pressure >= MemoryPressureLevel.High)
return true;
// 正常压力时按概率清理
if (pressure == MemoryPressureLevel.Normal)
{
float memoryUsage = (float)Profiler.GetTotalAllocatedMemoryLong() /
(config.highMemoryThresholdMB * 1024 * 1024);
// 内存使用率越高,清理概率越大
float cleanupProbability = Mathf.Clamp01(memoryUsage - 0.5f) * 0.5f;
return UnityEngine.Random.value < cleanupProbability;
}
return false;
}
/// <summary>
/// 日志输出
/// </summary>
private void Log(string message)
{
if (enableLogging)
Debug.Log($"[MemoryManager] {message}");
}
private void LogWarning(string message)
{
Debug.LogWarning($"[MemoryManager] {message}");
}
private void LogError(string message)
{
Debug.LogError($"[MemoryManager] {message}");
}
#endregion
#region 辅助类
/// <summary>
/// 内存统计信息
/// </summary>
[System.Serializable]
public class MemoryStats
{
public int totalResourcesRegistered;
public int resourcesUnloaded;
public long totalMemoryAllocated;
public long totalMemoryFreed;
public int referenceOperations;
public int accessOperations;
public int autoCleanupsPerformed;
public int memoryWarnings;
public DateTime startTime = DateTime.Now;
public TimeSpan Uptime => DateTime.Now - startTime;
public void Reset()
{
totalResourcesRegistered = 0;
resourcesUnloaded = 0;
totalMemoryAllocated = 0;
totalMemoryFreed = 0;
referenceOperations = 0;
accessOperations = 0;
autoCleanupsPerformed = 0;
memoryWarnings = 0;
startTime = DateTime.Now;
}
public void PrintStats()
{
Debug.Log($"=== 内存统计 ===");
Debug.Log($"运行时间: {Uptime:hh\\:mm\\:ss}");
Debug.Log($"注册资源: {totalResourcesRegistered}");
Debug.Log($"卸载资源: {resourcesUnloaded}");
Debug.Log($"分配内存: {totalMemoryAllocated / 1024 / 1024:F1}MB");
Debug.Log($"释放内存: {totalMemoryFreed / 1024 / 1024:F1}MB");
Debug.Log($"净内存: {(totalMemoryAllocated - totalMemoryFreed) / 1024 / 1024:F1}MB");
Debug.Log($"引用操作: {referenceOperations}");
Debug.Log($"访问操作: {accessOperations}");
Debug.Log($"自动清理: {autoCleanupsPerformed}");
Debug.Log($"内存警告: {memoryWarnings}");
}
}
#endregion
#region Unity生命周期
void OnEnable()
{
Initialize();
}
void OnDisable()
{
StopAllCoroutines();
}
void OnApplicationQuit()
{
// 生成最终报告
string finalReport = ExportMemoryReport(true);
Debug.Log(finalReport);
// 强制清理所有资源
StartCoroutine(ForceCleanup(true));
}
void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus)
{
// 应用暂停时执行轻度清理
RequestMemoryCleanup(MemoryCleanupStrategy.Conservative);
}
}
void OnGUI()
{
if (enableProfiling && currentPressure >= MemoryPressureLevel.Normal)
{
DrawMemoryInfo();
}
}
/// <summary>
/// 在屏幕上绘制内存信息
/// </summary>
private void DrawMemoryInfo()
{
GUI.color = GetPressureColor(currentPressure);
GUI.backgroundColor = new Color(0, 0, 0, 0.7f);
GUILayout.BeginArea(new Rect(10, 10, 300, 150));
GUILayout.BeginVertical("Box");
long totalMB = Profiler.GetTotalAllocatedMemoryLong() / 1024 / 1024;
long reservedMB = Profiler.GetTotalReservedMemoryLong() / 1024 / 1024;
GUILayout.Label($"内存状态: {currentPressure}");
GUILayout.Label($"已用内存: {totalMB}MB");
GUILayout.Label($"保留内存: {reservedMB}MB");
GUILayout.Label($"资源数量: {resourceRegistry.Count}");
GUILayout.Label($"峰值内存: {peakMemoryUsage / 1024 / 1024}MB");
// 显示压力级别指示器
GUILayout.BeginHorizontal();
for (int i = 0; i < 4; i++)
{
GUI.color = i <= (int)currentPressure ? Color.red : Color.gray;
GUILayout.Box("", GUILayout.Width(20), GUILayout.Height(10));
}
GUILayout.EndHorizontal();
GUILayout.EndVertical();
GUILayout.EndArea();
GUI.color = Color.white;
}
private Color GetPressureColor(MemoryPressureLevel level)
{
switch (level)
{
case MemoryPressureLevel.Low: return Color.green;
case MemoryPressureLevel.Normal: return Color.yellow;
case MemoryPressureLevel.High: return new Color(1, 0.5f, 0); // 橙色
case MemoryPressureLevel.Critical: return Color.red;
default: return Color.white;
}
}
#endregion
}
5.2 卸载策略对比
| 卸载方法 | 参数 | 效果 | 适用场景 |
|---|---|---|---|
Unload(false) | false | 只卸载AssetBundle文件,保留已实例化的资源 | 资源正在使用,后续可能还要加载同一Bundle |
Unload(true) | true | 卸载AssetBundle和所有加载的资源 | 确定不再需要该Bundle的任何资源 |
Resources.UnloadUnusedAssets() | - | 卸载所有未引用的资源 | 场景切换、内存紧张时 |
Resources.UnloadAsset() | 具体资源 | 卸载指定资源 | 精确控制单个资源卸载 |
六、热更新实现方案
6.1 热更新流程
csharp
public class HotUpdateManager : MonoBehaviour
{
private string serverURL = "http://your-server.com/assetbundles/";
private string localPath;
private Hash128 remoteHash;
void Start()
{
localPath = Application.persistentDataPath + "/AssetBundles/";
StartCoroutine(CheckForUpdates());
}
IEnumerator CheckForUpdates()
{
// 1. 下载版本文件
string versionURL = serverURL + "version.json";
using (UnityWebRequest versionRequest = UnityWebRequest.Get(versionURL))
{
yield return versionRequest.SendWebRequest();
if (versionRequest.result == UnityWebRequest.Result.Success)
{
VersionInfo remoteVersion = JsonUtility.FromJson<VersionInfo>(
versionRequest.downloadHandler.text
);
// 2. 比较版本
VersionInfo localVersion = LoadLocalVersion();
if (remoteVersion.version > localVersion.version)
{
// 3. 下载更新
yield return DownloadUpdates(remoteVersion);
}
}
}
}
IEnumerator DownloadUpdates(VersionInfo remoteVersion)
{
// 4. 下载差异文件
foreach (BundleInfo bundle in remoteVersion.bundles)
{
string localFile = localPath + bundle.name;
string remoteFile = serverURL + bundle.name;
// 检查本地文件是否存在且哈希匹配
if (File.Exists(localFile))
{
Hash128 localHash = Hash128.Parse(
File.ReadAllText(localFile + ".hash")
);
if (localHash == bundle.hash)
continue; // 跳过已更新的文件
}
// 下载更新
using (UnityWebRequest request =
UnityWebRequestAssetBundle.GetAssetBundle(remoteFile, bundle.hash))
{
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
// 保存到本地
byte[] data = request.downloadHandler.data;
File.WriteAllBytes(localFile, data);
File.WriteAllText(localFile + ".hash", bundle.hash.ToString());
}
}
}
}
[System.Serializable]
public class VersionInfo
{
public int version;
public List<BundleInfo> bundles;
}
[System.Serializable]
public class BundleInfo
{
public string name;
public string hash;
public long size;
}
}
七、性能优化建议
7.1 加载优化
csharp
public class PerformanceOptimizer
{
// 1. 预加载常用资源
public IEnumerator PreloadEssentialBundles()
{
string[] essentialBundles = { "common", "ui", "config" };
foreach (string bundle in essentialBundles)
{
yield return AssetBundle.LoadFromFileAsync(
GetBundlePath(bundle)
);
}
}
// 2. 分批加载
public IEnumerator LoadInBatches(List<string> bundles, int batchSize = 3)
{
for (int i = 0; i < bundles.Count; i += batchSize)
{
List<IEnumerator> batchLoaders = new List<IEnumerator>();
for (int j = 0; j < batchSize && i + j < bundles.Count; j++)
{
batchLoaders.Add(LoadSingleBundle(bundles[i + j]));
}
// 并行加载一批
yield return StartCoroutine(ParallelCoroutines(batchLoaders));
// 给一帧休息时间
yield return null;
}
}
// 3. 使用对象池减少重复加载
public class AssetPool
{
private Dictionary<string, Queue<GameObject>> pool =
new Dictionary<string, Queue<GameObject>>();
public GameObject GetAsset(string bundleName, string assetName)
{
string key = $"{bundleName}/{assetName}";
if (pool.TryGetValue(key, out Queue<GameObject> queue) && queue.Count > 0)
{
return queue.Dequeue();
}
// 从AssetBundle加载
return LoadNewAsset(bundleName, assetName);
}
public void ReturnAsset(string bundleName, string assetName, GameObject obj)
{
string key = $"{bundleName}/{assetName}";
if (!pool.ContainsKey(key))
pool[key] = new Queue<GameObject>();
obj.SetActive(false);
pool[key].Enqueue(obj);
}
}
}
7.2 监控与调试
csharp
public class AssetBundleMonitor : MonoBehaviour
{
void OnGUI()
{
GUILayout.BeginArea(new Rect(10, 10, 300, 200));
// 显示已加载的Bundle
GUILayout.Label("=== 已加载 AssetBundle ===");
foreach (var bundle in AssetBundle.GetAllLoadedAssetBundles())
{
GUILayout.Label($"- {bundle.name}");
}
// 显示内存使用
GUILayout.Label($"\n总内存: {Profiler.GetTotalAllocatedMemoryLong() / 1024 / 1024}MB");
GUILayout.Label($"纹理内存: {Profiler.GetAllocatedMemoryForGraphicsDriver() / 1024 / 1024}MB");
GUILayout.EndArea();
}
// 记录加载时间
public class TimedLoader
{
private System.Diagnostics.Stopwatch stopwatch =
new System.Diagnostics.Stopwatch();
public IEnumerator LoadWithTiming(string bundleName)
{
stopwatch.Restart();
AssetBundleCreateRequest request =
AssetBundle.LoadFromFileAsync(GetBundlePath(bundleName));
yield return request;
stopwatch.Stop();
Debug.Log($"{bundleName} 加载耗时: {stopwatch.ElapsedMilliseconds}ms");
}
}
}
八、常见问题与解决方案
8.1 问题排查清单
csharp
public class ProblemSolver
{
/*
常见问题及解决方案:
1. Bundle加载失败
- 检查路径是否正确
- 检查文件名大小写
- 检查平台是否匹配
2. 依赖丢失
- 确保所有依赖Bundle已加载
- 检查Manifest是否正确
3. 内存泄漏
- 检查Unload调用
- 使用Profiler分析内存
- 确保引用计数正确
4. 加载卡顿
- 使用异步加载
- 分批加载大资源
- 预加载常用资源
5. 更新失败
- 检查网络权限
- 检查存储空间
- 验证文件完整性(CRC/MD5)
*/
// 完整性检查
public bool ValidateBundle(string path, string expectedHash)
{
if (!File.Exists(path))
return false;
using (var md5 = System.Security.Cryptography.MD5.Create())
{
using (var stream = File.OpenRead(path))
{
byte[] hash = md5.ComputeHash(stream);
string actualHash = BitConverter.ToString(hash).Replace("-", "").ToLower();
return actualHash == expectedHash.ToLower();
}
}
}
}
总结
最佳实践要点:
-
合理规划 Bundle:按功能模块、使用频率划分
-
异步加载:避免主线程卡顿
-
依赖管理:确保先加载依赖资源
-
内存管理:及时卸载不再使用的资源
-
版本控制:实现可靠的热更新机制
-
错误处理:网络异常、文件损坏等情况的处理
-
性能监控:添加加载时间、内存使用监控
推荐的工作流:
-
开发阶段:使用 Addressables 或直接 Resources
-
发布阶段:转换为 AssetBundle
-
更新阶段:通过热更新系统增量更新
-
运行阶段:按需加载 + 缓存管理
通过合理的 AssetBundle 管理,可以显著提升游戏性能,降低内存占用,并实现灵活的资源更新策略。
2244

被折叠的 条评论
为什么被折叠?



