1. 程式人生 > >寫一個unity載入資源管理器

寫一個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

 

不對的地方歡迎拍磚