概述:
本篇我们实现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:参考,支持热更,但是每次资源变化都得重新打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);
}
}
}