前面几篇文章介绍了Addressable的资源加载以及缓存池的设计。彼时是将项目中的设计理念记录下来,设计时Addressable还尚未给出同步的设计方法。虽然Addressable用法简单,但是因为异步的原因导致项目中使用了大量回调委托,一度使项目的维护修改变的及其困难,好在Unity很快给出了同步方法的设计方案,因为在重新整理代码时,将这部分逻辑采用同步的方法进行重构。
Unity的资源加载统分两类资源加载,一类为复制类型资源加载(Gameobject类型),另外一类时引用类型资源加载(如音贴图、文本等)。因此设计时在基类脚本中处理基础的Addressabele加载,考虑到大资源文件的加载,因此保留同步加载的方法。
在基类加载中,主要保存了加载资源地址和句柄,调用Addressable的加载API利用WaitForCompletion() 方法将异步加载的方法同步阻塞,实现同步加载。
using UnityEngine.AddressableAssets;
using UnityEngine.AddressableAssets.Initialization;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine;
using System.Collections.Generic;
class BaseLoader
{
protected string name = string.Empty;
private AsyncOperationHandle handle;
private bool isLoad = false;
public BaseLoader(string name)
{
this.name = name;
this.isLoad = false;
}
///
/// 加载资源
///
/// 资源名称
/// 加载完成之后存放的父节点
/// 加载完成之后的回调
public virtual void Load<T>(CallBack<T> onComplete)where T:UnityEngine.Object
{
if (this.isLoad)
{
if (handle.IsDone)
{
if (onComplete != null)
{
onComplete(handle.Result as T);
}
}
else
{
handle.Completed += (result) =>
{
if (result.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
{
var obj = result.Result as T;
if (onComplete != null)
{
onComplete(obj);
}
}
else
{
if (onComplete != null)
{
onComplete(null);
}
Helper.LogError("Load name = " + name + " tpye = " + typeof(T).ToString() + " failed! ");
}
};
}
}
else
{
this.isLoad = true;
this.handle = Addressables.LoadAssetAsync<T>(name);
handle.Completed += (result) =>
{
if (result.Status == UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationStatus.Succeeded)
{
var obj = result.Result as T;
if (onComplete != null)
{
onComplete(obj);
}
}
else
{
if (onComplete != null)
{
onComplete(null);
}
Helper.LogError("Load name = " +name+" tpye = " + typeof(T).ToString() + " failed! ");
}
};
}
}
///
/// 同步方法加载资源
///
///
///
public virtual T Load<T>() where T : UnityEngine.Object
{
this.isLoad = true;
this.handle = Addressables.LoadAssetAsync<T>(name);
T obj = this.handle.WaitForCompletion() as T;
this.isLoad = false;
return obj;
}
///
/// 同时加载多个资源
///
///
public virtual List<T> Loads<T>() where T : UnityEngine.Object
{
this.isLoad = true;
this.handle = Addressables.LoadAssetsAsync<T>(name,(obj)=> { });
List<T> objs=this.handle.WaitForCompletion() as List<T>;
return objs;
}
public virtual void Release()
{
if (this.isLoad)
{
this.isLoad = false;
Addressables.Release(handle);
}
}
}
引用类资源加载相对简单,只需要做好加载计数,每次加载引用计数+1,每次释放引用计数-1,如果引用计数小于或等于0则释放句柄。虽然Addressable为我们做了引用计数,但是实际使用时发现如果多处使用了资源未做引用计数时会导致错误贴图丢失等错误。
第一版的资源加载设计的稍微有点复杂,结合缓存池的设计思路,将复制类资源的加载和缓存池技术结合起来简化代码逻辑
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.AddressableAssets.Initialization;
///
/// 全局单例资源管理器
/// 所有的资源加载最终都走此类入口
///
public class AssetManager: Singleton<AssetManager>
{
#region Prefab 预制加载管理
///
/// 缓存对象根节点
///
public UnityEngine.Transform PoolRoot;
private Dictionary<string, GameObjectLoader> pools = new Dictionary<string, GameObjectLoader>();
///
/// 缓存查找表
///
private Dictionary<GameObject, GameObjectLoader> lookup = new Dictionary<GameObject, GameObjectLoader>();
public AssetManager()
{
UnityEngine.Transform poolNode = new GameObject("[Asset Pool]").transform;
poolNode.transform.localPosition = Vector3.zero;
poolNode.transform.localScale = Vector3.one;
poolNode.transform.localRotation = Quaternion.identity;
GameObject.DontDestroyOnLoad(poolNode);
//启动定时器,定时清理缓存池里的缓存
}
///
/// 同步实例化GameObject
///
///
///
public GameObject Instantiate(string name)
{
GameObjectLoader loader;
if (this.pools.TryGetValue(name, out loader))
{
var obj = loader.Instantiate();
this.lookup.Add(obj, loader);
return obj;
}
else
{
loader = new GameObjectLoader(name);
var obj= loader.Instantiate();
//添加缓存池
this.pools.Add(name, loader);
//添加缓存查找表
this.lookup.Add(obj, loader);
return obj;
}
}
///
///获取模板信息
///
///
///
public GameObject GetTemplete(string name)
{
GameObjectLoader loader;
if (this.pools.TryGetValue(name, out loader))
{
return loader.prefab;
}
return null;
}
///
/// 将资源释放回缓存池
///
///
public void FreeGameObject(GameObject obj)
{
GameObjectLoader loader;
if (this.lookup.TryGetValue(obj, out loader))
{
loader.Free(obj);
//释放后从缓存查找表中移除
this.lookup.Remove(obj);
}
}
public void RemovePools(string name)
{
this.pools.Remove(name);
}
///由定时器调用,定时清理缓存,一般设计为10分钟清理一次
public void ReleaseAll()
{
foreach (var item in this.pools.Values )
{
item.Release();
}
}
#endregion
#region 引用类型资源加载管理
///
/// 资源类型查找缓存表
///
private Dictionary<string, UnityObjectLoader> assets = new Dictionary<string, UnityObjectLoader>();
public T LoadAsset<T>(string name) where T : UnityEngine.Object
{
UnityObjectLoader loader;
if (this.assets.TryGetValue(name, out loader))
{
var obj = loader.LoadAsset<T>();
return obj;
}
else
{
loader = new UnityObjectLoader(name);
var obj = loader.LoadAsset<T>();
//添加缓存池
this.assets.Add(name, loader);
return obj;
}
}
///
/// 释放资源,引用计数自减1,减少为0后释放Addressable
///
///
public void FreeAsset(string name)
{
UnityObjectLoader loader;
if (this.assets.TryGetValue(name, out loader))
{
loader.Free();
}
}
#endregion
}
经常我们会在一些界面上反复实例化/销毁一个物体(例如Tips提示,多的时候10多条,少的时候1~2条,需要我们动态的创建与销毁),使用GameObjectLoader(GameObject obj)带参数的构造函数,因此可以在需要的使用单独使用,可以做为某个特定对象的缓存池。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
///
/// 预制类资源加载器
///
class GameObjectLoader:BaseLoader
{
///
/// 资源缓存列表
///
private Stack<GameObject> caches=new Stack<GameObject>();
///
/// 正在使用的列表
///
private HashSet<GameObject> references = new HashSet<GameObject>();
public GameObject prefab;
public GameObjectLoader(string name):base(name)
{
this.prefab = null;
}
public GameObjectLoader(GameObject obj) : base(obj.name)
{
this.prefab = obj;
}
///
/// 同步方法,确保已经加载好了
///
///
///
public GameObject Instantiate(Transform parent)
{
GameObject obj = null;
if (caches.Count > 0)
{
obj= caches.Pop();
}
else
{
obj = GameObject.Instantiate(this.prefab) as GameObject;
obj.name = this.name;
}
this.references.Add(obj);
return obj;
}
///
/// 同步方法实例化对象
///
///
public GameObject Instantiate()
{
if (caches.Count > 0)
{
var obj = caches.Pop();
this.references.Add(obj);
return obj;
}
else
{
if (this.prefab != null)
{
var obj = GameObject.Instantiate(this.prefab) as GameObject;
obj.name = this.name;
this.references.Add(obj);
return obj;
}
else
{
this.prefab = base.Load<GameObject>();
var obj = GameObject.Instantiate(this.prefab) as GameObject;
obj.name = this.name;
base.Release();
this.references.Add(obj);
return obj;
}
}
}
public void Free(GameObject obj)
{
this.caches.Push(obj);
this.references.Remove(obj);
obj.transform.SetParent(AssetManager.Instance.PoolRoot);
}
public override void Release()
{
foreach (var obj in this.caches)
{
GameObject.Destroy(obj.gameObject);
}
if (this.references.Count <= 0)
{
base.Release();
AssetManager.Instance.RemovePools(this.name);
}
}
}