概述:https://www.cnblogs.com/wang-jin-fu/p/10975660.html
本篇我们实现unity里的加载模块,他的主要功能是,业务传入资源名字和资源类型,加载模块加载到对应的资源后返回给业务,业务不需要关心该资源是从本地加载还是从AssetBundle里加载。
加载模块分两部分1.各资源的加载器,例如ab包加载器、Asset加载器、网络下载。2.各加载器的管理类,提供给业务的接口都在这里
需要支持的能力
1.能切换不同加载模式 开发阶段编辑器运行直接加载资源无需打ab包,测试或正式发布阶段通过ab包加载资源
2.缓存机制 能定时清理长时间未使用的资源内存
3.既有同步加载 也有异步加载
复杂点:
1.根据业务传入的资源名字,获取到editor路径、ab包名字。需要事先根据资源名字保存资源的路径、ab包路径配置。
2.ab包的引用计数维护:加载时ReferencedCount+1,卸载时ReferencedCount-1。
两种引用: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、减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包。
一、加载器实现
上篇我们有说到,unity有四种加载方式
1.AssetDatabase:在编辑器内加载卸载资源,并不能在游戏发布时使用,它只能在编辑器内使用。但是,它加载速度快,使用简单。
2.Resources:因为使用Resources文件夹无法热更,本片篇就不实现此途径了。
3.AssetBundle:参考https://www.cnblogs.com/wang-jin-fu/p/11171626.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); } } }
二、缓存池,缓存加载器
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(); } } }
三、加载管理器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); } } }