在Unity开发过程中,经常需要对各类资源进行内存维护,eg:图片,音效,视频, Prefab 等。
下面给大家提供一个简单的AssetBundle管理方案:
using Game.Utils;
using LitJson;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using TMPro;
using Unity.VisualScripting;
using UnityEngine;
using static UnityEditor.Progress;
namespace Game.Load
{
public class BundleCatalog
{
public Dictionary<string, string> mPathMapping = new Dictionary<string, string>();
public Dictionary<string, string[]> mDepensMapping = new Dictionary<string, string[]>();
public Dictionary<string, string> bundleName2MD5 = new Dictionary<string, string>();
}
public class Reference
{
public int count;
public float time;
public void AddCount() { count++; }
public void RemoveCount() { count--; }
public void ResetTime() { time = 0; }
}
public class BundleLoader
{
public AssetBundleCreateRequest abRequest;
public string path;
public bool isDone = false;
public int cacheCount;
public List<string> cacheDepens;
public List<Action<AssetBundle>> actions;
public bool GetRequestIsDone()
{
if (isDone) return true;
if (abRequest == null) return false;
return abRequest.isDone;
}
public bool GetBundleIsDone()
{
if (!GetRequestIsDone()) return false;
return cacheCount == cacheDepens.Count;
}
}
public class BundleOperation
{
public BundleCatalog mBundleCalalog;
// 记录已加载的AssetBundle引用计数
private Dictionary<string, Reference> assetBundleReferenceCounts = new Dictionary<string, Reference>();
// 记录已加载的AssetBundle
private Dictionary<string, AssetBundle> loadedAssetBundles = new Dictionary<string, AssetBundle>();
// 正在加载中的AssetBundle
private Dictionary<string, BundleLoader> AssetBundlesLoader = new Dictionary<string, BundleLoader>();
// 缓存时间
private int mCacheTime = 3;
public BundleOperation()
{
mBundleCalalog = JsonMapper.ToObject<BundleCatalog>(
FileTools.GetFile(
AssetUtils.GetRealPath(
GameConst.GetAssetBundlePath(GameConst.Catalog)
)
)
);
}
public string[] FindDepend(string assetBundlePath, List<string> mark = null)
{
if (mark == null)
mark = new List<string>();
assetBundlePath = GetPathByCatalog(assetBundlePath);
if (mark.Contains(assetBundlePath))
return default;
else
mark.Add(assetBundlePath);
if (mBundleCalalog.mDepensMapping.ContainsKey(assetBundlePath))
{
foreach (var depend in mBundleCalalog.mDepensMapping[assetBundlePath])
{
FindDepend(depend, mark);
}
}
return mark.ToArray();
}
public void LoadBundleAsync(string assetBundlePath, Action<AssetBundle> action = null)
{
LoadBundleAsync(assetBundlePath, action, new List<string>());
}
private void LoadBundleAsync(string assetBundlePath, Action<AssetBundle> action = null, List<string> mark = null)
{
if (mark == null)
mark = new List<string>();
assetBundlePath = GetPathByCatalog(assetBundlePath);
if (mark.Contains(assetBundlePath))
return;
else
mark.Add(assetBundlePath);
if (loadedAssetBundles.ContainsKey(assetBundlePath))
{
action?.Invoke(loadedAssetBundles[assetBundlePath]);
return;
}
if (AssetBundlesLoader.ContainsKey(assetBundlePath))
{
AssetBundlesLoader[assetBundlePath].actions.Add(action);
return;
}
if (mBundleCalalog.mDepensMapping.ContainsKey(assetBundlePath))
{
foreach (var depend in mBundleCalalog.mDepensMapping[assetBundlePath])
{
LoadBundleAsync(depend, null, mark);
}
}
AssetBundleCreateRequest ab = AssetBundle.LoadFromFileAsync(AssetUtils.GetRealPath(GameConst.GetAssetBundlePath(assetBundlePath)));
AssetBundlesLoader.Add(assetBundlePath, new BundleLoader()
{
cacheDepens = mBundleCalalog.mDepensMapping[assetBundlePath].ToList(),
isDone = false,
path = assetBundlePath,
abRequest = ab,
actions = new List<Action<AssetBundle>> { action },
cacheCount = 0
});
}
public AssetBundle LoadBundleSync(string assetBundlePath)
{
return LoadBundleSync(assetBundlePath, new List<string>());
}
private AssetBundle LoadBundleSync(string assetBundlePath, List<string> mark = null)
{
if (mark == null)
mark = new List<string>();
assetBundlePath = GetPathByCatalog(assetBundlePath);
if (mark.Contains(assetBundlePath))
return default;
else
mark.Add(assetBundlePath);
if (loadedAssetBundles.ContainsKey(assetBundlePath))
{
return loadedAssetBundles[assetBundlePath];
}
if (mBundleCalalog.mDepensMapping.ContainsKey(assetBundlePath))
{
foreach (var depend in mBundleCalalog.mDepensMapping[assetBundlePath])
{
LoadBundleSync(depend, mark);
}
}
AssetBundle ab = AssetBundle.LoadFromFile(
AssetUtils.GetRealPath(
GameConst.GetAssetBundlePath(assetBundlePath)
)
);
loadedAssetBundles.Add(assetBundlePath, ab);
return loadedAssetBundles[assetBundlePath];
}
public string GetPathByCatalog(string assetBundlePath)
{
foreach (var item in mBundleCalalog.mPathMapping)
{
if (item.Key.Contains(assetBundlePath))
{
return item.Value;
}
}
return assetBundlePath;
}
public string GetPathByCatalogKey(string assetBundlePath)
{
foreach (var item in mBundleCalalog.mPathMapping)
{
if (item.Key.Contains(assetBundlePath))
{
return item.Key;
}
}
return assetBundlePath;
}
private void UnloadAssetBundle(string assetBundlePath)
{
assetBundlePath = GetPathByCatalog(assetBundlePath);
if (loadedAssetBundles.ContainsKey(assetBundlePath))
{
loadedAssetBundles[assetBundlePath].Unload(true);
loadedAssetBundles.Remove(assetBundlePath);
}
}
public void IncreaseAssetBundleReferenceCount(string assetBundlePath)
{
IncreaseAssetBundleReferenceCount(assetBundlePath, new List<string>());
}
private void IncreaseAssetBundleReferenceCount(string assetBundlePath, List<string> mark = null)
{
if (mark == null)
mark = new List<string>();
assetBundlePath = GetPathByCatalog(assetBundlePath);
if (mark.Contains(assetBundlePath))
return;
else
mark.Add(assetBundlePath);
if (mBundleCalalog.mDepensMapping.ContainsKey(assetBundlePath))
{
foreach (var depend in mBundleCalalog.mDepensMapping[assetBundlePath])
{
IncreaseAssetBundleReferenceCount(depend, mark);
}
}
if (!assetBundleReferenceCounts.ContainsKey(assetBundlePath))
{
assetBundleReferenceCounts.Add(assetBundlePath, new Reference() { count = 0, time = 0 });
}
assetBundleReferenceCounts[assetBundlePath].AddCount();
}
public void DecreaseAssetBundleReferenceCount(string assetBundlePath)
{
DecreaseAssetBundleReferenceCount(assetBundlePath, new List<string>());
}
private void DecreaseAssetBundleReferenceCount(string assetBundlePath, List<string> mark = null)
{
if (mark == null)
mark = new List<string>();
assetBundlePath = GetPathByCatalog(assetBundlePath);
if (mark.Contains(assetBundlePath))
return;
else
mark.Add(assetBundlePath);
if (mBundleCalalog.mDepensMapping.ContainsKey(assetBundlePath))
{
foreach (var depend in mBundleCalalog.mDepensMapping[assetBundlePath])
{
DecreaseAssetBundleReferenceCount(depend, mark);
}
}
if (assetBundleReferenceCounts.ContainsKey(assetBundlePath))
{
assetBundleReferenceCounts[assetBundlePath].RemoveCount();
if (assetBundleReferenceCounts[assetBundlePath].count < 0)
{
Log.Error("引用计数小于0:", assetBundlePath);
}
}
else
{
Log.Error("引用不存在:", assetBundlePath);
}
}
public string GetReferenceCount(string assetBundlePath)
{
assetBundlePath = GetPathByCatalog(assetBundlePath);
if (assetBundleReferenceCounts.ContainsKey(assetBundlePath))
{
return "引用:" + assetBundleReferenceCounts[assetBundlePath].count.ToString();
}
return "引用:不存在";
}
public void OnTick()
{
UpdateReference();
UpdateLoader();
}
public void UpdateLoader()
{
foreach (var item in AssetBundlesLoader)
{
if (item.Value.GetRequestIsDone() && !loadedAssetBundles.ContainsKey(item.Key))
{
foreach (var item2 in AssetBundlesLoader)
{
if (item2.Value.cacheDepens.Contains(item.Key))
{
item2.Value.cacheCount++;
}
}
}
}
List<string> succKey = new List<string>();
foreach (var item in AssetBundlesLoader)
{
if (item.Value.GetBundleIsDone())
{
succKey.Add(item.Key);
}
}
foreach (var item in succKey)
{
loadedAssetBundles.Add(item, AssetBundlesLoader[item].abRequest.assetBundle);
foreach (var action in AssetBundlesLoader[item].actions)
{
action?.Invoke(AssetBundlesLoader[item].abRequest.assetBundle);
}
AssetBundlesLoader.Remove(item);
}
}
public void UpdateReference()
{
List<string> overtime = new List<string>();
foreach (var item in assetBundleReferenceCounts)
{
if (item.Value.count <= 0)
{
item.Value.time += Time.deltaTime;
if (item.Value.time > mCacheTime)
{
overtime.Add(item.Key);
}
}
else
{
item.Value.ResetTime();
}
}
foreach (var item in overtime)
{
assetBundleReferenceCounts.Remove(item);
UnloadAssetBundle(item);
}
}
}
}
简单的说:
在外部调用 LoadBundleSync/LoadBundleAsync 后进行IncreaseAssetBundleReferenceCount
在Destroy时进行DecreaseAssetBundleReferenceCount
OnTick 刷新检测异步加载 以及释放缓存