概述:http://www.javashuo.com/article/p-nggymcxb-bw.htmlhtml
本篇咱們實現unity裏的加載模塊,他的主要功能是,業務傳入資源名字和資源類型,加載模塊加載到對應的資源後返回給業務,業務不須要關心該資源是從本地加載仍是從AssetBundle里加載。緩存
加載模塊分兩部分1.各資源的加載器,例如ab包加載器、Asset加載器、網絡下載。2.各加載器的管理類,提供給業務的接口都在這裏服務器
須要支持的能力網絡
1.能切換不一樣加載模式 開發階段編輯器運行直接加載資源無需打ab包,測試或正式發佈階段經過ab包加載資源併發
2.緩存機制 能定時清理長時間未使用的資源內存框架
3.既有同步加載 也有異步加載異步
複雜點:async
1.根據業務傳入的資源名字,獲取到editor路徑、ab包名字。須要事先根據資源名字保存資源的路徑、ab包路徑配置。編輯器
2.ab包的引用計數維護:加載時ReferencedCount+1,卸載時ReferencedCount-1。ide
兩種引用:AB包之間的相互依賴,ab包加載時,依賴包引用計數加1,ab包卸載時,依賴包引用減1。2.資源引用,例如使用AssetBundle.LoadAsset加載資源時,該ab包引用計數加一,引用對象被刪除時,引用計數減1.
問題是如何確保被刪除的引用對象引用計數能正確減小。
有兩種方式:
1.純引用計數。ab包依賴和asset引用都使用引用計數。asset引用類型大概如下幾種
1-1.預製體,額外封裝一層,全部預製體的生成和銷燬都由一個管理類統一管理。
例如封裝一個ResourceItem類,全部預製體的生成和銷燬都必須走這個類的的Create和Dispose類,在ctor方法里加載ab包、實例化預製體,在Dispose方法裏Distory對象、卸載ab包(這裏的加、卸載只是引用計數加一、減1)。須要業務手動的釋放調用Dispose釋放對象。
-- 資源 -- 全部非UI的預製加載 local ResourceItem = class("ResourceItem", ObjectBase) -- 靜態建立ResourceItem接口 -- path Data目錄如下,預製的路徑 function ResourceItem.Create(target, filepath, parent, onLoaded, async) local abpath = PubFunc.GetAbNameOfPath(filepath) local name = PubFunc.GetNameFromPath(filepath) local item = ResourceItem.new(filepath, abpath, name, parent, onLoaded, async) target:AddSubItem(item) return item end function ResourceItem.CreateUIItem(target, abpath, name, parent, onLoaded, async) local filepath = abpath if string.find(abpath, name)==nil then filepath = abpath..name end local item = ResourceItem.new(filepath, abpath, name, parent, onLoaded, async) target:AddSubItem(item) return item end function ResourceItem:ctor(filepath, abpath, name, parent, onLoaded, async) ResourceItem.super.ctor(self)async = async and true or false self._filepath = filepath self._path = abpath -- 文件路徑 self._name = name self._parent = parent self._onLoaded = onLoaded -- 加載完回調 self.gameObject = nil --外部可直接獲取 self.transform = nil self._loadKey = me.modules.resource:CreateAsyn(abpath, name, handler(self, self.OnLoadComplete), async) end-- 清理 function ResourceItem:Dispose() if self.gameObject then -- 銷燬 me.modules.resource:Delete(self.gameObject) self.gameObject = nil self.transform = nil elseif self._loadKey then -- 取消加載 me.modules.resource:CancelLoad(self._loadKey) self._loadKey = nil end ResourceItem.super.Dispose(self) end-- 是否已加載 function ResourceItem:IsLoaded() return self.gameObject ~= nil end -- 加載完成回調 function ResourceItem:OnLoadComplete(go) self._loadKey = nil local trans = nil if go then trans = go.transform if self._parent then go:SetParent(self._parent) end trans:SetLocalPositionZero() trans:SetLocalScaleOne() else printError("加載RedourceItem失敗,path:", self._path) end self.gameObject = go self.transform = trans -- 回調給外部 if self._onLoaded then self._onLoaded(self) end end return ResourceItem
你也能夠在每一個實例化的GameObject上掛在一個腳本,並在該腳本的Destory方法裏卸載ab包的引用
1-2.場景類,這個比較簡單,場景管理類確定會記錄當前的場景信息,在加載新場景時,先卸載當前的ab包就能夠了。
1-3.sprite類,sprite是給image使用的,那麼咱們能夠擴展一下Image的類。例如業務傳入圖片的名字,ImageEx類根據名字到LoadModule加載對應的ab及sprite並記錄當前的sprite名字,當業務下次設置圖片或Image對象被Destory時,根據保存的sprite名字卸載ab包。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using Debugger = LuaInterface.Debugger; /// <summary> /// image擴展,提供經過圖片名字加載圖片、從ab包、url、高清資源下載、emoji加載圖片接口 /// TP形Image. /// </summary> public class ImageEx : Image { private string m_SpriteName = ""; public string SpriteName { get { return m_SpriteName; } set { m_SpriteName = value; } }protected override void OnDestroy() { // 銷燬的時候要卸載一下ab UnloadSprite(); StopCurrLoadingUrl(); } private void UnloadSprite() { if (!string.IsNullOrEmpty(SpriteName) && sprite != null) { SpriteModule.Instance.UnloadSpriteByName(SpriteName); SpriteName = null; this.sprite = null; } }public void SetSpriteName(string name) { if (sprite != null && SpriteName == name) { return; } UnloadSprite(); if(string.IsNullOrEmpty(name)) { this.sprite = null; return; } SpriteName = name; SpriteModule.Instance.LoadSpriteByName(name, onLoadedSprite); }private void onLoadedSprite(object obj) { Sprite sp = obj as Sprite; this.sprite = sp; if (sp == null) { Debugger.LogError("Load sprite null, name:{0}", m_SpriteUrl); } else { if(!string.IsNullOrEmpty(m_strHdResName)) { sprite.name = m_strHdResName; } } } }
1-4.shader類:全局就一個ab包,常駐內存就行了
1-5.音樂類:PlayMusic加載、StopMusic卸載就行了
// 背景音樂 // fromResources 是否從Resources文件夾下加載 public void PlayMusic(string name, bool fromResources = false) { if (string.IsNullOrEmpty(name)) return; if (fromResources) { if(MusicMute) { return; } AudioClip clip = Resources.Load<AudioClip>(name); if(clip != null) { m_musicSource.enabled = true; m_musicSource.clip = clip; m_musicSource.loop = true; m_musicSource.Play(); } } else { string strBundleName = "sound/music/" +name; LoadModule.Instance.LoadAssetFromBundle(strBundleName, name, typeof(AudioClip), (data) => { m_musicSource.enabled = true; m_musicSource.clip = data as AudioClip; m_musicSource.loop = true; m_musicSource.Play(); }); } } /// <summary> /// 中止音樂並清理 /// </summary> public void StopMusic() { AudioClip m_musicClip = m_musicSource.clip; if (m_musicClip) { m_musicSource.Stop(); m_musicSource.clip = null; string currentMusicName = m_musicClip.name; AssetBundleCache assetBundleCache = ABCachePool.Instance.GetABCacheByName(string.Format("sound_music_{0}.unity3d", currentMusicName)); if (assetBundleCache != null) { assetBundleCache.ReferencedCount = 1; LoadModule.Instance.UnloadAssetBundle(string.Format("sound/music/{0}", currentMusicName), true); } } }
2.引用計數+弱引用。ab包依賴使用引用計數,asset引用使用弱引用,業務在加載asset時須要傳入引用的對象(實例化就不用了,能夠把實例化出來的GameObject看成引用對象),經過判斷對象是否爲空來判斷引用關係。
強引用:咱們實例化一個對象,直接引用了這個對象就是強引用。在這個對象被強引用的時,GC沒法回收這個對象。只有當該對象全部的強引用都失去的時候,GC纔會回收該對象。
弱引用:弱引用能夠保持對對象的引用,同時容許GC在必要時釋放對象,回收內存。這邊必定要用弱引用,否則會影響對象的回收。
protected List<System.WeakReference> mReferenceOwnerList; /// <summary> /// 爲AB添加指定owner的引用 /// 全部owner都銷燬則ab引用計數歸零可回收 /// </summary> /// <param name="owner"></param> protected void retainOwner(UnityEngine.Object owner) { if (owner == null) { ResourceLogger.logErr(string.Format("引用對象不能爲空!沒法爲資源:{0}添加引用!", AssetBundleName)); return; } foreach (var referenceowner in mReferenceOwnerList) { if (owner.Equals(referenceowner)) { return; } } System.WeakReference wr = new System.WeakReference(owner); mReferenceOwnerList.Add(wr); } /// <summary> /// 獲取AB有效的引用對象計數 /// </summary> /// <returns></returns> protected int updateOwnerReference() { for (int i = 0; i < mReferenceOwnerList.Count; i++) { UnityEngine.Object o = (UnityEngine.Object)mReferenceOwnerList[i].Target; if (!o) { mReferenceOwnerList.RemoveAt(i); i--; } } return mReferenceOwnerList.Count; }
第一種方式須要業務手動Dispose無用的對象,固然這是個好習慣,須要框架嚴格注意引用對象的管理。第二種須要業務在引用時傳入引用的對象,須要額外的參數。
一個加載模塊大體結構以下:
加載模塊結構如上圖,load爲加載器,ResManager爲提供給業務調用的接口,LoadModule爲不使用ab包的加載接口,ABLoadModule爲使用ab包的加載接口,這兩個module對用戶封閉。
AssetLoader:在editor模式下加載資源。AssetBundleLoader :ab包加載器,負責從內存加載AssetBundle。BundleAssetLoader :負責從指定的ab包加載資源。AssetBundleCache:緩存的ab包。
1、加載器實現
上篇咱們有說到,unity有四種加載方式
1.AssetDatabase:在編輯器內加載卸載資源,並不能在遊戲發佈時使用,它只能在編輯器內使用。可是,它加載速度快,使用簡單。
2.Resources:由於使用Resources文件夾沒法熱更,本片篇就不實現此途徑了。
3.AssetBundle:參考http://www.javashuo.com/article/p-kxvvqcjh-e.html,支持熱更,可是每次資源變化都得從新打ab包(奇慢),因此適合發佈模式,但開發模式千萬別用。
4.UnityWebRequest:從網絡端下載
1.全部的加載器都繼承自一個接口:Loader,該類定義了當前的加載類型、初始化、回收的重置方法、加載方法、加載進度回調等
public class Loader { #region Define public enum LoaderType { STREAM, // 流(原則上能夠是任何文件,包括遠程服務器上的) ASSET, // Asset目錄下的資源 BUNDLE, // AssetBundle BUNDLEASSET, // AssetBundle中的資源 SCENE, // 場景 Texture, // 圖片 } public enum LoaderState { NONE, // LOADING, // 加載中 FINISHED, // 完成 } public delegate void ProgressHandle(Loader loader, float rate); public delegate void LoadedHandle(Loader loader, object data); #endregion protected Loader(LoaderType type) { m_type = type; } protected LoaderType m_type; // 加載器類型 protected LoaderState m_state; // 加載狀態 protected string m_path; // 路徑 protected bool m_async; // 是否異步 protected ProgressHandle m_onProgress; // 加載進度 protected LoadedCallback m_onloaded; // 加載完成回調通知 protected System.Diagnostics.Stopwatch m_watch = new System.Diagnostics.Stopwatch ();//加載時間統計 public LoaderType Type { get { return m_type; } } public string Path { get { return m_path; } } public bool IsFinish { get { return m_state == LoaderState.FINISHED; } } public bool IsAsync { get { return m_async; } }
//主要用於ab包的判斷,由於ab包須要等待依賴包的加載 public virtual bool IsPrepareToLoad() { return true; } //初始化參數 public virtual void Init(string path, LoadedCallback onloaded, bool async = true) { m_state = LoaderState.NONE; m_path = path; m_async = async; m_onloaded = onloaded; } public virtual void Reset() { m_path = ""; m_async = true; m_onloaded = null; m_state = LoaderState.NONE; m_onProgress = null; } public virtual void Load() { m_watch.Reset (); m_watch.Start (); m_state = LoaderState.LOADING; OnLoadProgress(0f); } public virtual void Stop() { Reset(); } public virtual void Update(float dt) { } protected virtual void OnLoadProgress(float rate) { if (m_onProgress != null) { m_onProgress(this, rate); } } protected virtual void OnLoadCompleted(object data) { m_state = LoaderState.FINISHED; try { if (m_onloaded!= null) { m_onloaded (data); } } catch(System.Exception e) { LuaInterface.Debugger.LogError(e.Message); } OnLoadProgress(1f); } }
2.editor模式下的加載,直接使用AssetDatabase加載。
public class AssetLoader : Loader { Object m_data = null; System.Type m_assetType = null; //資源類型 public AssetLoader() : base(Loader.LoaderType.ASSET) { } public void Init (string path, System.Type type, LoadedCallback onLoaded, bool async = true) { base.Init (path, onLoaded, async); m_assetType = type; } public override void Load() { base.Load(); #if UNITY_EDITOR if (m_assetType == null) { m_assetType = typeof(Object); } m_data = UnityEditor.AssetDatabase.LoadAssetAtPath(m_path, m_assetType); if (!m_async) { OnLoadCompleted(m_data); } #else if(!m_async) { OnLoadCompleted(null); } #endif } public override void Update(float dt) { if (m_state == LoaderState.LOADING) { OnLoadCompleted(m_data); m_data = null; } } }
3.ab包的緩存能夠參考我以前的文章:ab包的緩存
ab加載以下:當且僅當IsPrepareToLoad判斷經過(即全部依賴包都加載完成)才能調用load方法,開始ab包的加載。InitDependencies方法用於初始化當前ab包的依賴項
load加載分兩種,第一種是從擴展包加載,第二種是從本地加載。
ab包的加載無非就是同步和異步加載的區別,ab包的卸載也只須要調用Unload方法就行了。惟一須要注意的是,記載asset前必須保證ab的依賴包都加載完成了。
AssetBundleCache:緩存類,用於緩存ab包,提供從ab包加載asset的方法並緩存a包
public class AssetBundleCache { private string m_name; // AssetBundle name private int m_referencedCount; // 引用計數 private float m_unloadTime; // 釋放時間 private HashSet<string> m_setAllAssetNames = null;//ab包包含的全部asset的名字,用於判斷指定asset是否存在於ab中 private Dictionary<string, AssetBundleRequest> m_dicAsync = new Dictionary<string, AssetBundleRequest>();//正在異步加載的asset private Dictionary<string, Object> m_dicAssetCache = null;//已經加載完的asset public AssetBundleCache(string name, AssetBundle ab, int refCount) { m_name = name; Bundle = ab; ReferencedCount = refCount; } // AssetBundle public AssetBundle Bundle { get; private set; } // 是否常駐,通用資源的ab包不卸載 public bool Persistent { get; set; } public string BundleName { get { return m_name; } } // 引用計數 public int ReferencedCount { get { return m_referencedCount; } set { m_referencedCount = value; if (m_referencedCount <= 0) { m_unloadTime = Time.realtimeSinceStartup; } else { m_unloadTime = 0; } if (m_referencedCount < 0) { Debug.LogWarningFormat("AssetBundleCache reference count < 0, name:{0}, referencecount:{1}", m_name, m_referencedCount); } } } // 是否能夠刪除 public bool IsCanRemove { get { // 常駐資源 if (Persistent) return false; // 很是駐,而且引用計數爲0 if (!Persistent && ReferencedCount <= 0) { return true; } return false; } } // 緩存時間到 public bool IsTimeOut { get { return Time.realtimeSinceStartup - m_unloadTime >= Config.Instance.AssetCacheTime; } } // 資源是否正在異步加載中 public bool IsAssetLoading(string name) { return m_dicAsync.ContainsKey(name); } //判斷AB是否包含某個資源 public bool IsExistAsset(string strAssetName) { if (m_setAllAssetNames != null && m_setAllAssetNames.Contains(strAssetName)) { return true; } return false; } // 獲取緩存中的資源 public Object GetCacheAsset(string name) { if (m_dicAssetCache != null) { Object rst = null; if (m_dicAssetCache.TryGetValue(name, out rst)) { return rst; } } return null; } // 資源加載完 要緩存一下 public void OnLoadedAsset(string name, Object asset) { m_unloadTime = Time.realtimeSinceStartup; if (m_dicAsync.ContainsKey(name)) { m_dicAsync.Remove(name); } // 常駐ab加載進來的資源 用真實引用 不用弱引用 if (m_dicAssetCache == null) { m_dicAssetCache = new Dictionary<string, Object>(); } if (m_dicAssetCache.ContainsKey(name)) { Debug.LogWarningFormat("警報! 緩存已經存在了還從新加載:{0}", name); } m_dicAssetCache[name] = asset; return; } //異步加載資源,須要添加到m_dicAsync裏,防止重複加載 public AssetBundleRequest LoadAssetAsync(string name, System.Type type) { if (Bundle == null) { Debug.LogWarningFormat("AssetBundle:{0} is null, load asset async:{1},type:{2}, fail!!", m_name, name, type.ToString()); return null; } AssetBundleRequest opt; m_dicAsync.TryGetValue(name, out opt); if (opt == null) { opt = Bundle.LoadAssetAsync(name, type); m_dicAsync.Add(name, opt); } return opt; } //加載資源 public Object LoadAsset(string name, System.Type type) { if (Bundle == null) { Debug.LogWarningFormat("AssetBundle:{0} is null, load asset:{1},type:{2}, fail!!", m_name, name, type.ToString()); return null; } Object asset = Bundle.LoadAsset(name, type); if (asset == null) { Debug.LogWarningFormat("AssetBuncleCache.LoadAsset, asset not exist:{0}, {1}", m_name, name); } else { OnLoadedAsset(name, asset); } return asset; } //加載全部資源 public UnityEngine.Object[] LoadAllAssets(bool bCache = true) { UnityEngine.Object[] allObjs = Bundle.LoadAllAssets(); if (bCache) { for (int i = 0; i < allObjs.Length; i++) { Object obj = allObjs[i]; OnLoadedAsset(obj.name, obj); } } return allObjs; } //異步加載全部資源 只用做預加載使用 public AssetBundleRequest LoadAllAssetsAsync() { if (Bundle == null) { return null; } return Bundle.LoadAllAssetsAsync(); } public bool LoadAllAssetNames() { if (Bundle == null) { return false; } string[] arrNames = Bundle.GetAllAssetNames(); if (arrNames.Length == 0) { return false; } if (m_setAllAssetNames == null) { m_setAllAssetNames = new HashSet<string>(); } for (int i = 0; i < arrNames.Length; i++) { string strName = System.IO.Path.GetFileNameWithoutExtension(arrNames[i]); m_setAllAssetNames.Add(strName); } return true; } //卸載ab包 public void Unload() { if (m_dicAsync.Count > 0) { Debug.LogWarningFormat("[僅提醒]該Bundle還有資源在加載中,暫時不卸載:{0}", m_name); return; } if (Bundle != null) { Bundle.Unload(false); Bundle = null; } if (m_setAllAssetNames != null) { m_setAllAssetNames.Clear(); } if (m_dicAssetCache != null) { m_dicAssetCache.Clear(); } } }
ABCachePool:負責管理ab包的引用計數、緩存、獲取。
public class ABCachePool { #region Instance private static ABCachePool m_Instance; public static ABCachePool Instance { get { return m_Instance ?? (m_Instance = new ABCachePool()); } } #endregion Dictionary<string, AssetBundleCache> m_AssetBundleCaches = new Dictionary<string, AssetBundleCache>(); // 緩存隊列 HashSet<string> m_persistentABs = new HashSet<string>(); public Dictionary<string, AssetBundleCache> AssetBundleCaches { get { return m_AssetBundleCaches; } } public void ClearAllCache() { foreach (KeyValuePair<string, AssetBundleCache> keyval in m_AssetBundleCaches) { keyval.Value.Unload(); } m_AssetBundleCaches.Clear(); } public bool IsExistCache(string abName) { return m_AssetBundleCaches.ContainsKey(abName); } // 引用這個bundle public AssetBundleCache ReferenceCacheByName(string abName) { AssetBundleCache cache = null; m_AssetBundleCaches.TryGetValue(abName, out cache); if (cache != null) { ++cache.ReferencedCount; } return cache; } // 獲取ABCache 不增長引用 public AssetBundleCache GetABCacheByName(string abName) { AssetBundleCache cache = null; m_AssetBundleCaches.TryGetValue(abName, out cache); return cache; } public AssetBundleCache AddCache(string abName, AssetBundle bundle, int refCount) { if (m_AssetBundleCaches.ContainsKey(abName)) { Debug.LogWarningFormat("AssetBundleCache already contains key:{0}, it will be cover by new value.", abName); } AssetBundleCache cache = new AssetBundleCache(abName, bundle, refCount); m_AssetBundleCaches[abName] = cache; if (m_persistentABs.Contains(abName)) { cache.Persistent = true; } return cache; } // immediate 只有場景是馬上卸載 public AssetBundleCache UnReferenceCache(string abName, bool immediate = false) { AssetBundleCache cache = null; if (!m_AssetBundleCaches.TryGetValue(abName, out cache)) { return null; } if (cache.Persistent) { return null; } --cache.ReferencedCount; if (immediate && cache.IsCanRemove) { RemoveCache(abName); } return cache; } public void RemoveCache(string abName) { AssetBundleCache cache = m_AssetBundleCaches[abName]; cache.Unload(); m_AssetBundleCaches.Remove(abName); } private List<string> m_lstRm = new List<string>(); // 清除無引用的AssetBundle緩存 public void ClearNoneRefCache(bool mustTimeout) { foreach (KeyValuePair<string, AssetBundleCache> keyval in m_AssetBundleCaches) { AssetBundleCache item = keyval.Value; // 只清除引用計數爲0的 if (item.IsCanRemove && (!mustTimeout || item.IsTimeOut)) { m_lstRm.Add(keyval.Key); } } for (int i = 0; i < m_lstRm.Count; i++) { RemoveCache(m_lstRm[i]); } m_lstRm.Clear(); } /// <summary> /// 常駐ab包設置 /// </summary> /// <param name="arrAB"></param> public void SetPersistentABs(string[] arrAB) { m_persistentABs.Clear(); for (int i = 0; i < arrAB.Length; i++) { string strAB = arrAB[i];//FileHelper.GenBundlePath(arrAB[i]); m_persistentABs.Add(strAB); AssetBundleCache abCache; m_AssetBundleCaches.TryGetValue(strAB, out abCache); if (abCache != null) { abCache.Persistent = true; } } } }
BundleLoader:用於加載AssetBundle
public class BundleLoader : Loader { AssetBundleCreateRequest m_abRequest = null; private int m_iRefCount; // 當前等待此加載的引用計數 private List<string> m_lstDepAbNames = new List<string>(); //依賴的未加載Bundle名字列表 private LoadedCallback m_onABLoaded; private string m_strBundleName; // 相對路徑 public string BundleName { get { return m_strBundleName; } } System.Diagnostics.Stopwatch m_saveAbWatcher = new System.Diagnostics.Stopwatch(); public BundleLoader() : base(Loader.LoaderType.BUNDLE) { } public void AddLoadedCallback(LoadedCallback onloaded) { m_onABLoaded += onloaded; m_iRefCount += 1; } public void AddReferenced() { m_iRefCount += 1; } public override void Reset() { base.Reset(); m_iRefCount = 0; m_lstDepAbNames.Clear(); m_strBundleName = ""; m_abRequest = null; m_onABLoaded = null; m_lstDepAbNames.Clear(); } // 判斷是否全部依賴都已經加載 public override bool IsPrepareToLoad() { for (int i = m_lstDepAbNames.Count - 1; i >= 0; i--) { string strABName = m_lstDepAbNames[i]; if (ABCachePool.Instance.IsExistCache(strABName)) { m_lstDepAbNames.RemoveAt(i); } else { break; } } return m_lstDepAbNames.Count == 0; } public void Init(string path, string strName, string[] dependencies, LoadedCallback onloaded, bool async = true) { // Bundle 比較特殊 不使用父類的回調 base.Init(path, null, async); m_iRefCount = 1; m_strBundleName = strName; m_onABLoaded = onloaded; InitDependencies(dependencies); } private void InitDependencies(string[] dependencies) { if (dependencies == null || dependencies.Length == 0) return; for (int i = 0; i < dependencies.Length; i++) { string strName = dependencies[i]; if (!ABCachePool.Instance.IsExistCache(strName)) { m_lstDepAbNames.Add(strName); } } } public override void Load() { base.Load(); if (m_async) { string path = FileHelper.GetAPKPath(m_path);//根據ab包的名字索引ab包的存儲路勁 m_abRequest = AssetBundle.LoadFromFileAsync(path); } else { AssetBundle ab = null; try { // 同步使用AssetBundle.LoadFromFile加載,速度最快 if (ab == null) { string path = FileHelper.GetAPKPath(m_path);//根據ab包的名字索引ab包的存儲路勁 ab = AssetBundle.LoadFromFile(path); } } catch (System.Exception e) { Debug.LogError(e.Message); } finally { OnLoaded(ab); } } } public override void Update(float dt) { if (m_state == LoaderState.LOADING) { if (m_abRequest != null) { if (m_abRequest.isDone) { OnLoaded(m_abRequest.assetBundle); } else { DoProgress(m_abRequest.progress); } } } } void DoProgress(float rate) { } void OnLoaded(AssetBundle ab) { AssetBundleCache cache = ABCachePool.Instance.AddCache(m_strBundleName, ab, m_iRefCount); OnLoadCompleted(ab); if (m_onABLoaded != null) { m_onABLoaded(cache); } } }
2、緩存池,緩存加載器
public class LoaderPool { #region Instance private static LoaderPool m_Instance; public static LoaderPool Instance { get { return m_Instance ?? (m_Instance = new LoaderPool()); } } #endregion List<Loader> m_bundleLoaderPool = new List<Loader>(); // BundleLoader的緩存 List<Loader> m_assetLoaderPool = new List<Loader>(); // BundleAssetLoader的緩存 public T GetLoader<T>() where T : Loader, new() { System.Type type = typeof(T); T loader = null; if (type == typeof(BundleLoader)) { if (m_bundleLoaderPool.Count > 0) { loader = (T)m_bundleLoaderPool[0]; m_bundleLoaderPool.RemoveAt(0); return loader; } } else if (type == typeof(BundleAssetLoader)) { if (m_assetLoaderPool.Count > 0) { loader = (T)m_assetLoaderPool[0]; m_assetLoaderPool.RemoveAt(0); return loader; } } loader = new T(); return loader; } public void RecycleLoader(Loader loader) { if (loader.GetType() == typeof(BundleLoader)) { loader.Reset(); m_bundleLoaderPool.Add(loader); } else if (loader.GetType() == typeof(BundleAssetLoader)) { loader.Reset(); m_assetLoaderPool.Add(loader); } else { loader.Reset(); } } }
3、加載管理器LoadModule
1.ResManager :提供給業務的接口
public class ResManager { #region Instance private static ResManager m_Instance; public static ResManager Instance { get { return m_Instance ?? (m_Instance = new ResManager()); } } #endregion public int SyncCount = 6; // 同步加載併發數 private bool m_isUseAssetBundle; private ILoadModule m_loadModule; public void Init(bool isUseAssetBundle) { m_isUseAssetBundle = isUseAssetBundle; if (isUseAssetBundle) { m_loadModule = new ABLoadModule(); } else { m_loadModule = new LoadModule(); } m_loadModule.Init(SyncCount); } public void UnInit() { m_loadModule.UnInit(); } public void Update(float dt) { m_loadModule.Update(dt); } public void Clear() { ABCachePool.Instance.ClearNoneRefCache(false); Resources.UnloadUnusedAssets(); } public void LoadPrefab(string strPath, string name, LoadedCallback onLoaded, bool async = true) { m_loadModule.LoadPrefab(strPath, name, onLoaded, async); } public void LoadMusic(string name, LoadedCallback onLoaded, bool async = true) { m_loadModule.LoadMusic(name, onLoaded, async); } public void LoadFont(string name, LoadedCallback onLoaded, bool async = true, bool inBundle = false) { m_loadModule.LoadFont(name, onLoaded, async, inBundle); } public void LoadScene(string name, string scenePath, bool isAdditive, LoadedCallback onLoaded) { m_loadModule.LoadScene(name, scenePath, isAdditive, onLoaded); } }
2.兩種加載器的接口類ILoadModule
public interface ILoadModule { void Init(int syncCout); void UnInit(); void Update(float dt); void LoadPrefab(string strPath, string name, LoadedCallback onLoaded, bool async = true); void LoadMusic(string name, LoadedCallback onLoaded, bool async = true); void LoadFont(string name, LoadedCallback onLoaded, bool async = true, bool inBundle = false); void LoadScene(string name, string scenePath, bool isAdditive, LoadedCallback onLoaded); }
3.editor模式下的加載器
/// <summary> /// 不使用ab加載資源(Editor模式下) /// </summary> public class LoadModule : ILoadModule { public int SyncCount; // 同步加載併發數 public HashSet<string> m_loadedBundleNames = new HashSet<string>(); // 加載隊列 List<Loader> m_loadings = new List<Loader>();//正在加載 List<Loader> m_loaderQueue = new List<Loader>();//等待加載 float m_lastClear = 0; // 上一次清除時間 public void Init(int syncCout) { SyncCount = syncCout; } public void UnInit() { for (int i = 0; i < m_loadings.Count; i++) { m_loadings[i].Stop(); } m_loadings.Clear(); m_loaderQueue.Clear(); } private List<int> m_lstRmTemp = new List<int>(); public void Update(float dt) { for (int i = m_loadings.Count - 1; i >= 0; i--) { Loader loader = m_loadings[i]; loader.Update(dt); if (loader.IsFinish) { m_loadings.RemoveAt(i); LoaderPool.Instance.RecycleLoader(loader); } } int remain = Mathf.Min(SyncCount - m_loadings.Count, m_loaderQueue.Count); for (int i = 0; i < m_loaderQueue.Count; i++) { Loader loader = m_loaderQueue[i]; m_loadings.Add(loader); loader.Load(); loader.Update(dt); m_lstRmTemp.Add(i); if (m_lstRmTemp.Count >= remain) { break; } } if (m_lstRmTemp.Count > 0) { for (int i = m_lstRmTemp.Count - 1; i >= 0; i--) { m_loaderQueue.RemoveAt(m_lstRmTemp[i]); } m_lstRmTemp.Clear(); } } public void Clear() { Resources.UnloadUnusedAssets(); } public void LoadPrefab(string strPath, string name, LoadedCallback onLoaded, bool async = true) { LoadAssetByPath(strPath, name, typeof(GameObject), onLoaded, async); } public void LoadMusic(string name, LoadedCallback onLoaded, bool async = true) { string path = string.Format("music/{0}", name); LoadAssetByPath(path, name, typeof(AudioClip), onLoaded, async); } public void LoadFont(string name, LoadedCallback onLoaded, bool async = true, bool inBundle = false) { string path = string.Format("ui/font/{0}", name); LoadAssetByPath(path, name, typeof(Font), onLoaded, async); } #region LoadScene public void LoadScene(string name, string scenePath, bool isAdditive, LoadedCallback onLoaded) { var activeSceneName = SceneManager.GetActiveScene().name; // 若是當前場景是要加載的場景 直接返回 if (activeSceneName == name) { if (onLoaded != null) { onLoaded(null); } return; } if (isAdditive) { __LoadScene(name, scenePath, isAdditive, onLoaded); } else //大場景先加載idle過渡 { __LoadScene(name, scenePath, isAdditive, onLoaded); } } private void __LoadScene(string name, string scenePath, bool isAdditive, LoadedCallback onLoaded, bool async = true) { SceneLoader sLoader = LoaderPool.Instance.GetLoader<SceneLoader>(); sLoader.Init(name, scenePath, isAdditive, onLoaded, async); StartLoad(sLoader, true); } #endregion //從Bundle中加載資源 public void LoadAssetByPath(string path, string name, System.Type type, LoadedCallback onLoaded, bool async = true) { path = "Assets/Data/" + path; if (Directory.Exists(path)) { path += "/" + name; } string suffix = GetSuffixOfAsset(type); string fullPath = string.Format("{0}.{1}", path, suffix); LoadAsset(fullPath, onLoaded, type, false); } // 加載資源(Assets目錄下,帶後綴) public void LoadAsset(string path, LoadedCallback onLoaded, System.Type type = null, bool async = true) { if (!File.Exists(path)) { Debug.LogErrorFormat("Load Asset, Path:[{0}] not exist! ", path); if (onLoaded != null) { onLoaded(null); } return; } AssetLoader aLoader = LoaderPool.Instance.GetLoader<AssetLoader>(); aLoader.Init(path, type, onLoaded, async); StartLoad(aLoader, async); } void StartLoad(Loader loader, bool async, bool toHead = false) { // 異步加載或者加載還未準備好,則當作異步處理,外部控制是否加入隊列開頭 // 同步加載,而且已經具有加載條件,則直接調用Load進行加載 if (async || !loader.IsPrepareToLoad()) { if (toHead) { m_loaderQueue.Insert(0, loader); } else { m_loaderQueue.Add(loader); } } else { m_loadings.Add(loader); loader.Load(); } } // 卸載關卡場景 public void UnloadLevelScene(string sceneName, bool immediate) { SceneManager.UnloadSceneAsync(sceneName); } private void CallFunc_LoadedBack(LoadedCallback callback, object data) { if (callback != null) { callback(data); } } private string GetSuffixOfAsset(System.Type type) { if (type == typeof(Font)) { return "ttf"; } else if (type == typeof(AudioClip)) { return "ogg"; } else if (type == typeof(GameObject)) { return "prefab"; } else if (type == typeof(TextAsset)) { return "bytes"; } else if (type == typeof(Texture2D) || type == typeof(Sprite)) { return "png"; } return ""; } }
4.ab包加載模塊:和LoadModule 相比,1.ABLoadModule 須要在初始化前加載manifest文件。2.須要在加載資源前加載ab包以及a包的依賴包。3.須要提供卸載ab包的方法。
/// <summary> /// 使用ab加載資源(Editor模式下、線上平臺) /// </summary> public class ABLoadModule : ILoadModule { public int SyncCount; // 同步加載併發數 private AssetBundleManifest m_manifest = null; private HashSet<string> m_bundleNames = new HashSet<string>(); // 加載隊列 List<Loader> m_loadings = new List<Loader>();//正在加載 List<Loader> m_loaderQueue = new List<Loader>();//等待加載 Dictionary<string, AssetBundleLoader> m_dicAllLoader = new Dictionary<string, AssetBundleLoader>(); float m_lastClear = 0; // 上一次清除時間 public void Init(int syncCout) { SyncCount = syncCout; LoadManifest(); } public void UnInit() { for (int i = 0; i < m_loadings.Count; i++) { m_loadings[i].Stop(); } m_loadings.Clear(); m_loaderQueue.Clear(); ABCachePool.Instance.ClearAllCache(); } private List<int> m_lstRmTemp = new List<int>(); public void Update(float dt) { for (int i = m_loadings.Count - 1; i >= 0; i--) { Loader loader = m_loadings[i]; loader.Update(dt); if (loader.IsFinish) { if (loader.Type == Loader.LoaderType.BUNDLE) { AssetBundleLoader bLoader = loader as AssetBundleLoader; if (m_dicAllLoader.ContainsKey(bLoader.BundleName)) { m_dicAllLoader.Remove(bLoader.BundleName); } } m_loadings.RemoveAt(i); LoaderPool.Instance.RecycleLoader(loader); } } int remain = Mathf.Min(SyncCount - m_loadings.Count, m_loaderQueue.Count) ; for (int i = 0; i < m_loaderQueue.Count; i++) { Loader loader = m_loaderQueue[i]; if (loader.Type == Loader.LoaderType.BUNDLE) { AssetBundleLoader bLoader = loader as AssetBundleLoader; if (!bLoader.IsPrepareToLoad()) { continue; //若是有依賴未加載完直接跳過 } } else if (loader.Type == Loader.LoaderType.BUNDLEASSET) { BundleAssetLoader bLoader = loader as BundleAssetLoader; if (!bLoader.IsPrepareToLoad()) { continue; //Asset是否準備好加載 } } m_loadings.Add(loader); loader.Load(); loader.Update(dt); m_lstRmTemp.Add(i); if (m_lstRmTemp.Count >= remain) { break; } } if (m_lstRmTemp.Count > 0) { for (int i = m_lstRmTemp.Count - 1; i >= 0; i--) { m_loaderQueue.RemoveAt(m_lstRmTemp[i]); } m_lstRmTemp.Clear(); } UpdateAssetBundleCache(); } // 更新AssetBundle緩存(主要執行定時清理) public void UpdateAssetBundleCache() { // 每5秒回收一次 if (Time.realtimeSinceStartup - m_lastClear < 5) { return; } m_lastClear = Time.realtimeSinceStartup; /// 檢查無引用的AB節點 ABCachePool.Instance.ClearNoneRefCache(true); } public void Clear() { ABCachePool.Instance.ClearNoneRefCache(false); Resources.UnloadUnusedAssets(); } #region LoadManifest // 加載資源清單 public void LoadManifest() { if (m_manifest != null) { Object.DestroyImmediate(m_manifest, true); m_manifest = null; } m_bundleNames.Clear(); string manifestName = FileHelper.MANIFEST_NAME;//manifest文件名 string strFullPath = FileHelper.SearchFilePath(manifestName);//獲取manifest路徑 AssetBundleLoader bLoader = LoaderPool.Instance.GetLoader<AssetBundleLoader>(); bLoader.Init(strFullPath, manifestName, null, delegate (object data) { AssetBundleCache ab = data as AssetBundleCache; if (ab != null) { m_manifest = (AssetBundleManifest)ab.LoadAsset("AssetBundleManifest", typeof(AssetBundleManifest)); } ABCachePool.Instance.UnReferenceCache(manifestName, true); // 不走統一接口是由於manifest文件沒有後綴 if (m_manifest != null) { string[] bundles = m_manifest.GetAllAssetBundles(); for (int i = 0; i < bundles.Length; ++i) { m_bundleNames.Add(bundles[i]); } } }, false); bLoader.Load(); } #endregion public void LoadPrefab(string strPath, string name, LoadedCallback onLoaded, bool async = true) { LoadAssetFromBundle(strPath, name, typeof(GameObject), onLoaded, async); } public void LoadMusic(string name, LoadedCallback onLoaded, bool async = true) { string path = string.Format("music/{0}", name); LoadAssetFromBundle(path, name, typeof(AudioClip), onLoaded, async); } public void LoadFont(string name, LoadedCallback onLoaded, bool async = true, bool inBundle = false) { string path = string.Format("ui/font/{0}", name); LoadAssetFromBundle(path, name, typeof(Font), onLoaded, async); } #region LoadScene public void LoadScene(string name, string scenePath, bool isAdditive, LoadedCallback onLoaded) { var activeSceneName = SceneManager.GetActiveScene().name; // 若是當前場景是要加載的場景 直接返回 if (activeSceneName == name) { if (onLoaded != null) { onLoaded(null); } return; } if (isAdditive) { RealLoadScene(name, scenePath, isAdditive, onLoaded); } else //大場景先加載idle過渡 { RealLoadScene(name, scenePath, isAdditive, onLoaded); } } private void RealLoadScene(string name, string scenePath, bool isAdditive, LoadedCallback onLoaded) { string abPath = "scenes/" + name; LoadAssetBundle(abPath, delegate (object data) { if (data == null) { CallFunc_LoadedBack(onLoaded, null); return; } __LoadScene(name, scenePath, isAdditive, onLoaded); //場景的bundle在SceneLoader中自動卸載 }); } private void __LoadScene(string name, string scenePath, bool isAdditive, LoadedCallback onLoaded, bool async = true) { SceneLoader sLoader = LoaderPool.Instance.GetLoader<SceneLoader>(); sLoader.Init(name, scenePath, isAdditive, onLoaded, async); StartLoad(sLoader, true); } #endregion //從Bundle中加載資源 public void LoadAssetFromBundle(string path, string name, System.Type type, LoadedCallback onLoaded, bool async = true) { LoadAssetBundle(path, (data) => { AssetBundleCache abCache = data as AssetBundleCache; if (abCache == null) { Debug.LogWarningFormat("LoadAssetFromBundle, load ab fail:{0}", path); CallFunc_LoadedBack(onLoaded, null); return; } // 開啓任務去作加載 BundleAssetLoader assetLoader = LoaderPool.Instance.GetLoader<BundleAssetLoader>(); assetLoader.Init(abCache, name, type, onLoaded, async); StartLoad(assetLoader, async); }, async); } // 加載AssetBundle(先從persistentData讀,沒有找到則從streamingAssets讀,帶後綴) public void LoadAssetBundle(string path, LoadedCallback onLoaded, bool async = true) { string name = FileHelper.GenBundlePath(path); if (!HasAssetBundle(name)) { if (onLoaded != null) { onLoaded(null); } return; } // 加載依賴 LoadDependencies(name, async); // 檢查是否有緩存 有緩存說明依賴資源也是加載過了的 AssetBundleCache abCache = ABCachePool.Instance.ReferenceCacheByName(name); if (abCache != null) { if (onLoaded != null) { onLoaded(abCache); } return; } string fullpath = FileHelper.SearchFilePath(name); AssetBundleLoader bLoader = null; m_dicAllLoader.TryGetValue(name, out bLoader); if (bLoader == null) { string[] dependencies = m_manifest.GetDirectDependencies(name); bLoader = LoaderPool.Instance.GetLoader<AssetBundleLoader>(); bLoader.Init(fullpath, name, dependencies, onLoaded, async); m_dicAllLoader.Add(name, bLoader); StartLoad(bLoader, async); } else { if (onLoaded != null) { bLoader.AddLoadedCallback(onLoaded); } else { bLoader.AddReferenced(); } } } // 依賴 // 加載依賴 //asyncInFact 實際加載方式,若是依賴bundle是異步加載而且正在加載中,那麼整個bundle的加載方式變成異步加載 void LoadDependencies(string name, bool async) { if (m_manifest == null) { return; } string[] dependencies = m_manifest.GetDirectDependencies(name); for (int i = 0; i < dependencies.Length; ++i) { LoadAssetBundle(dependencies[i], null, async); } } void StartLoad(Loader loader, bool async, bool toHead = false) { // 異步加載或者加載還未準備好,則當作異步處理,外部控制是否加入隊列開頭 // 同步加載,而且已經具有加載條件,則直接調用Load進行加載 if (async || !loader.IsPrepareToLoad()) { if (toHead) { m_loaderQueue.Insert(0, loader); } else { m_loaderQueue.Add(loader); } } else { m_loadings.Add(loader); loader.Load(); } } public bool HasAssetBundle(string path) { path = path.Replace("/", "_").ToLower(); return m_bundleNames.Count == 0 || m_bundleNames.Contains(path) || string.Equals(path, FileHelper.MANIFEST_NAME); } // 卸載關卡場景 public void UnloadLevelScene(string sceneName, bool immediate) { SceneManager.UnloadSceneAsync(sceneName); UnloadSceneAssetBundle(sceneName, immediate); } // 卸載場景的AssetBundle public void UnloadSceneAssetBundle(string sceneName, bool immediate) { if (Config.Instance.UseAssetBundle) { string strABPath = "scenes/" + sceneName; UnloadAssetBundle(strABPath, immediate); } } // 卸載AssetBundle public void UnloadAssetBundle(string path, bool immediate = false) { string name = FileHelper.GenBundlePath(path); AssetBundleCache cache = ABCachePool.Instance.UnReferenceCache(name, immediate); if (cache != null) { UnloadDependencies(name, immediate); } } // 卸載依賴 void UnloadDependencies(string name, bool immediate) { if (m_manifest == null) { return; } string[] dependencies = m_manifest.GetDirectDependencies(name); for (int i = 0; i < dependencies.Length; ++i) { UnloadAssetBundle(dependencies[i], immediate); } } private void CallFunc_LoadedBack(LoadedCallback callback, object data) { if (callback != null) { callback(data); } } }