寫一個unity載入資源管理器
需求:
1.能切換不同載入模式
開發階段編輯器執行直接載入資源無需打ab包
測試或正式釋出階段通過ab包載入資源
2.快取機制 能定時清理長時間未使用的資源記憶體
3.既有同步載入 也有非同步載入
思路:
載入模式可以使用一個全域性列舉變數控制
資源管理器中 包含不同的載入器 每個載入器都有同步和非同步載入方法 要載入資源時呼叫資源管理器,管理器查詢資源是否在快取中,不在根據當前載入模式選擇對應的載入器載入資源,
畫張導圖
實現:
首先定義載入模式列舉
LoadMode.cs
public enum LoadMode{
Editor, //編輯器模式(該模式直接載入資源)
AssetBundle, //ab包模式(該模式所有資源使用ab方式載入)
}
定義資源管理器類,它是一個單例
AssetManager.cs
public class AssetManager : MonoSingleton<AssetManager>{ [Header("當前資源載入模式:")] public AssetLoadMode LoadMode = AssetLoadMode.Editor; [Header("定時清理快取間隔(秒):")] public float ClearCacheDuration; [Header("快取資料駐留時間(秒)")] public float CacheDataStayTime; private IAssetLoader editorLoader; private IAssetLoader abLoader; private float cacheTimeTemp; //緩衝區[key 為絕對路徑] private Dictionary<string, CacheDataInfo> cacheDataDic = new Dictionary<string, CacheDataInfo>(); public void InitMode(AssetLoadMode mode, float duration = 10f, float cacheStayTime = 9f) { Debug.LogFormat("[AssetManager]初始化 當前載入模式:{0} 定時清理緩衝間隔:{1}s", mode, duration); LoadMode = mode; ClearCacheDuration = duration; CacheDataStayTime = cacheStayTime; editorLoader = new EditorAssetLoader(); abLoader = new AssetBundleLoader(GameConfigs.StreamingAssetABRootPath, GameConfigs.StreamingAssetManifestPath); } //同步載入 public T LoadAsset<T>(string path) where T : Object { CacheDataInfo info = queryCache(path); if (info != null) { info.UpdateTick(); return info.CacheObj as T; } else { switch (LoadMode) { case AssetLoadMode.Editor: return editorLoader.LoadAsset<T>(path); case AssetLoadMode.AssetBundler: return abLoader.LoadAsset<T>(path); } return null; } } //非同步載入 public void LoadAssetAsync<T>(string path, UnityAction<T> onLoadComplate) where T : Object { CacheDataInfo info = queryCache(path); if (info != null) { info.UpdateTick(); if (onLoadComplate != null) { onLoadComplate(info.CacheObj as T); } } else { switch (LoadMode) { case AssetLoadMode.Editor: StartCoroutine(editorLoader.LoadAssetAsync<T>(path, onLoadComplate)); break; case AssetLoadMode.AssetBundler: StartCoroutine(abLoader.LoadAssetAsync<T>(path, onLoadComplate)); break; } } } //檢測緩衝區 private CacheDataInfo queryCache(string path) { if (cacheDataDic.ContainsKey(path)) { return cacheDataDic[path]; } return null; } //加入緩衝區 public void pushCache(string path, Object obj) { Debug.Log("[AssetManager]加入快取:" + path); lock (cacheDataDic) { if (cacheDataDic.ContainsKey(path)) { cacheDataDic[path].UpdateTick(); } else { CacheDataInfo info = new CacheDataInfo(path, obj); cacheDataDic.Add(path, info); info.UpdateTick(); } } } //清空緩衝區 public void RemoveCache() { cacheDataDic.Clear(); } //清理緩衝區 private void updateCache() { Debug.Log("[AssetManager]清理快取"); foreach (var iter in cacheDataDic.ToList()) { if (iter.Value.StartTick + CacheDataStayTime >= Time.realtimeSinceStartup) { Debug.Log("過期清理:" + iter.Value.CacheName); cacheDataDic.Remove(iter.Key); } } } private void Update() { if (ClearCacheDuration < 0) return; cacheTimeTemp += Time.deltaTime; if (cacheTimeTemp >= ClearCacheDuration) { updateCache(); cacheTimeTemp -= ClearCacheDuration; } } }
定義載入器介面
IAssetLoader.cs
public interface IAssetLoader{
//非同步載入
IEnumerator LoadAssetAsync<T>(string path, UnityAction<T> callback) where T : class;
//同步載入
T LoadAsset<T>(string path) where T : class;
}
編輯器模式下載入器實現類, 使用UnityEditor.AssetDatabase.LoadAssetAtPath函式載入資源
EditorAssetLoader.cs
public class EditorAssetLoader : IAssetLoader
{
public T LoadAsset<T>(string path) where T : class {
return load<T>(path);
}
public IEnumerator LoadAssetAsync<T>(string path, UnityAction<T> callback) where T : class {
if (callback != null) {
callback(load<T>(path));
}
yield return null;
}
T load<T>(string path) where T : class {
#if UNITY_EDITOR
string absolutepath = path;
//絕對路徑轉為相對Assets資料夾的相對路徑
path = PathUtils.GetRelativePath(path, Application.dataPath);
Debug.Log("[LoadAsset(Editor)]: " + path);
Object obj = UnityEditor.AssetDatabase.LoadAssetAtPath(path, typeof(T));
if (obj == null) {
Debug.LogError("Asset not found - path:" + path);
}
AssetManager.Instance.pushCache(absolutepath, obj);
return obj as T;
#endif
return null;
}
}
assetbundle模式下載入器實現類
AssetBundleLoader.cs
public class AssetBundleLoader : IAssetLoader
{
private string assetRootPath;
private string mainfastPath;
private static AssetBundleManifest manifest;
public AssetBundleLoader(string assetPath,string mainfast) {
assetRootPath = assetPath;
mainfastPath = mainfast;
}
public T LoadAsset<T>(string path) where T : class {
string absolutepath = path;
path = PathUtils.NormalizePath(path);
Debug.Log("[LoadAsset]: " + path);
//打的ab包都資源名稱和檔名都是小寫的
string assetBundleName = PathUtils.GetAssetBundleNameWithPath(path, assetRootPath);
//載入Manifest檔案
LoadManifest();
//獲取檔案依賴列表
string[] dependencies = manifest.GetAllDependencies(assetBundleName);
//載入依賴資源
List<AssetBundle> assetbundleList = new List<AssetBundle>();
foreach (string fileName in dependencies) {
string dependencyPath = assetRootPath + "/" + fileName;
Debug.Log("[AssetBundle]載入依賴資源: " + dependencyPath);
assetbundleList.Add(AssetBundle.LoadFromFile(dependencyPath));
}
//4載入目標資源
AssetBundle assetBundle = null;
Debug.Log("[AssetBundle]載入目標資源: " + path);
assetBundle = AssetBundle.LoadFromFile(path);
assetbundleList.Insert(0, assetBundle);
Object obj = assetBundle.LoadAsset(Path.GetFileNameWithoutExtension(path), typeof(T));
//釋放依賴資源
UnloadAssetbundle(assetbundleList);
//加入快取
AssetManager.Instance.pushCache(absolutepath, obj);
return obj as T;
}
public IEnumerator LoadAssetAsync<T>(string path, UnityAction<T> callback) where T : class {
string absolutepath = path;
path = PathUtils.NormalizePath(path);
Debug.Log("[LoadAssetAsync]: " + path);
//打的ab包都資源名稱和檔名都是小寫的
string assetBundleName = PathUtils.GetAssetBundleNameWithPath(path, assetRootPath);
//載入Manifest檔案
LoadManifest();
//獲取檔案依賴列表
string[] dependencies = manifest.GetAllDependencies(assetBundleName);
//載入依賴資源
AssetBundleCreateRequest createRequest;
List<AssetBundle> assetbundleList = new List<AssetBundle>();
foreach (string fileName in dependencies) {
string dependencyPath = assetRootPath + "/" + fileName;
Debug.Log("[AssetBundle]載入依賴資源: " + dependencyPath);
createRequest = AssetBundle.LoadFromFileAsync(dependencyPath);
yield return createRequest;
if (createRequest.isDone) {
assetbundleList.Add(createRequest.assetBundle);
} else {
Debug.LogError("[AssetBundle]載入依賴資源出錯");
}
}
//載入目標資源
AssetBundle assetBundle = null;
Debug.Log("[AssetBundle]載入目標資源: " + path);
createRequest = AssetBundle.LoadFromFileAsync(path);
yield return createRequest;
if (createRequest.isDone) {
assetBundle = createRequest.assetBundle;
//釋放目標資源
assetbundleList.Insert(0, assetBundle);
}
AssetBundleRequest abr = assetBundle.LoadAssetAsync(Path.GetFileNameWithoutExtension(path), typeof(T));
yield return abr;
Object obj = abr.asset;
//加入快取
AssetManager.Instance.pushCache(absolutepath, obj);
callback(obj as T);
//釋放依賴資源
UnloadAssetbundle(assetbundleList);
}
// 載入 manifest
private void LoadManifest() {
if (manifest == null) {
string path = mainfastPath;
Debug.Log("[AssetBundle]載入manifest:" + path);
AssetBundle manifestAB = AssetBundle.LoadFromFile(path);
manifest = manifestAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
manifestAB.Unload(false);
}
}
private void UnloadAssetbundle(List<AssetBundle> list) {
for (int i = 0; i < list.Count; i++) {
list[i].Unload(false);
}
list.Clear();
}
}
在使用前還需要準備做幾件事情
1.使用工具把專案資源打成ab包(請自行百度谷歌,有很多文章介紹,專案程式碼裡也寫有一個簡單的打包工具。◕ᴗ◕。)
2.指定專案資源存放的資料夾路徑,指定ab包路徑,這裡寫一個全域性配置類GameConfig.cs
public static class GameConfigs
{
//資源管理器 載入模式
public static FoxGame.Asset.AssetLoadMode LoadAssetMode = FoxGame.Asset.AssetLoadMode.AssetBundler;
#if UNITY_ANDROID
static string curPlatformName = "android";
#elif UNITY_IPHONE
static string curPlatformName = "iphone";
#else
static string curPlatformName = "win";
#endif
//當前平臺名
public static string CurPlatformName { get { return curPlatformName; } }
//(該資料夾只能讀,打包時被一起寫入包內,第一次運行遊戲把該資料夾資料拷貝到本地ab包路徑下)
public static string StreamingAssetABRootPath = Application.streamingAssetsPath + "/" + curPlatformName;
//streamingasset目錄下的manifest檔案路徑
public static string StreamingAssetManifestPath = Application.streamingAssetsPath + "/" + curPlatformName + "/" + curPlatformName;
//遊戲資原始檔路徑
public static string GameResPath = Application.dataPath + "/GameRes";
//打包資源的輸出資料夾(匯出到streamingaseet資料夾下)
public static string GameResExportPath = Application.streamingAssetsPath + "/" + curPlatformName;
#region game res path
private static string assetRoot {
get {
if (LoadAssetMode == FoxGame.Asset.AssetLoadMode.Editor) {
return GameResPath;
} else {
return StreamingAssetABRootPath;
}
}
}
//ui預製體路徑
public static string GetUIPath(string prefabName) {
string str = "/Prefabs/UI/" + prefabName;
if (LoadAssetMode != FoxGame.Asset.AssetLoadMode.Editor) {
str = str.ToLower();
} else {
str = str + ".prefab";
}
return assetRoot + str;
}
//圖集路徑
public static string GetSpriteAtlasPath(string name) {
string str = "/Atlas/" + name;
if (LoadAssetMode != FoxGame.Asset.AssetLoadMode.Editor) {
str = str.ToLower();
} else {
str = str + ".spriteatlas";
}
return assetRoot + str;
}
// todo: 擴充套件...
#endregion
}
測試呼叫類Launcher.cs
public class Launcher : MonoBehaviour{
public Text Content;
public Image Img_1;
public Button Btn_1;
public Image Img_2;
public Button Btn_2;
// Use this for initialization
void Start () {
AssetManager.Instance.InitMode(GameConfigs.LoadAssetMode);
Content.text = "資源管理器載入模式:" + GameConfigs.LoadAssetMode;
Btn_1.onClick.AddListener(onClickedBtn1);
Btn_2.onClick.AddListener(onClickedBtn2);
}
void onClickedBtn1() {
SpriteAtlas atlas = AssetManager.Instance.LoadAsset<SpriteAtlas>(GameConfigs.GetSpriteAtlasPath("ui_atlas"));
Img_1.sprite = atlas.GetSprite(string.Format("icon_{0}", Random.Range(0, atlas.spriteCount - 1)));
}
void onClickedBtn2() {
AssetManager.Instance.LoadAssetAsync<SpriteAtlas>(GameConfigs.GetSpriteAtlasPath("ui_atlas"), (SpriteAtlas atlas) => {
Img_2.sprite = atlas.GetSprite(string.Format("icon_{0}", Random.Range(0, atlas.spriteCount - 1)));
});
}
}
win端效果(ios和Android等我空了測( ̄▽ ̄)~*):
說明:
1.模式可以按需求自定義擴充套件.
2.MonoSingleton類是一個繼承了MonoBehaviour的單例類,谷歌百度也有很多。◕ᴗ◕。
3.ab包所有檔名都是小寫!小寫!小寫!注意了各位.
4.PathUtils類是一個路徑操作的工具類
5.管理器快取的思路:
快取資源用CacheDataInfo類包裝,該類包含資源本身和存入快取的時間tick,當有新資源放入快取或者快取佇列的資源被重用時, 更新該tick為當前時間;
管理器定時的遍歷快取佇列 檢查快取是否有過期的資料.
6.專案中快取結構要複雜許多,可能某些資源需要常駐記憶體,某些資源特定場景才會載入,根據需求而定.
另附參考:
編輯器載入資源函式 AssetDatabase.LoadAssetAtPath https://docs.unity3d.com/ScriptReference/AssetDatabase.LoadAssetAtPath.html
ab類 AssetBundle https://docs.unity3d.com/ScriptReference/AssetBundle.html
unity單例 https://blog.csdn.net/ycl295644/article/details/49487361
專案工程(unity2017.4):https://pan.baidu.com/s/1ZRAk2uV1eXtj0DRA33PBHw
提取碼:2bex
不對的地方歡迎拍磚