对象池实现
PoolAttribute
PoolAttribute类负责对需要Object对象池管理的对象进行Pool属性标记,无参数故无内容,只作用于类,不允许重复定义。
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class PoolAttribute : Attribute
{}
GameObject对象池
GameObjectPoolData类负责对PoolManager中的gameObjectPoolDic字典中具体存储的一个GameObject对象池数据进行管理,包含对象池父节点fatherObj和对象队列容器PoolQueue 。设计PushObj、GetObj方法和构造方法GameObjectPoolData实现对GameObject对象池的初始化、对象放入、对象取出,需要注意的是,PoolManager中的push和get相关操作是面向外界操作逻辑的,负责从所有对象池中找到特定的那一个对象池,具体的数据存储交由GameObjectPoolData类中的push和get方法完成。
//class GameObjectPoolData
// 对象池中 父节点
public GameObject fatherObj;
// 对象容器
public Queue<GameObject> poolQueue;
public GameObjectPoolData(GameObject obj, GameObject poolRootObj)
{
// 创建父节点 并设置到对象池根节点下方
fatherObj = new GameObject(obj.name);
fatherObj.transform.SetParent(poolRootObj.transform);
poolQueue = new Queue<GameObject>();
// 把首次创建时候 需要放入的对象 放进容器
PushObj(obj);
}
对于新创建的一类GameObjectPoolData,首先要创建其父节点(与物体同名)挂载到PoolRoot下,提供好对象容器并将对象放入对象池,所以实际有三层(对象池根节点-某一类对象池父节点-实际对象),先做好第一层第二层的层级设置,数据操作由PoolManager完成。
/// <summary>
/// 将对象放进对象池
/// </summary>
public void PushObj(GameObject obj)
{
// 对象进容器
poolQueue.Enqueue(obj);
// 设置父物体
obj.transform.SetParent(fatherObj.transform);
// 设置隐藏
obj.SetActive(false);
}
/// <summary>
/// 从对象池中获取对象
/// </summary>
/// <returns></returns>
public GameObject GetObj(Transform parent = null)
{
GameObject obj = poolQueue.Dequeue();
// 显示对象
obj.SetActive(true);
// 设置父物体
obj.transform.SetParent(parent);
if (parent == null)
{
// 回归默认场景
UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(obj, UnityEngine.SceneManagement.SceneManager.GetActiveScene());
}
return obj;
}
}
在第三层放入对象时,首先为对象设置第二层的父节点并放入容器,设置隐藏(放回对象池代表着回收不用),取出时显示对象并解开层级关系(parent代表取出的对象放到哪个对象下面,默认为null,代表对象放到最外层,此时需要从DontDestroyOnload场景回到原来的Scene)。
这里需要说明的是,第三层放入对象时,每个对象池的种类已经确定,Queue不需要做种类区分,区分普通的对象池应该是第一、二层PoolManager的工作,第三层实际是往一个确定类型的对象池内放入/取出一个或多个同类型的对象。
Object对象池
ObjectPoolData负责对PoolManager中的objectPoolDic字典中具体存储的一个Object对象池数据进行管理,类似的,其也有相应的push、get方法和构造函数。
public ObjectPoolData(object obj)
{
PushObj(obj);
}
// 对象容器
public Queue<object> poolQueue = new Queue<object>();
/// <summary>
/// 将对象放进对象池
/// </summary>
public void PushObj(object obj)
{
poolQueue.Enqueue(obj);
}
/// <summary>
/// 从对象池中获取对象
/// </summary>
/// <returns></returns>
public object GetObj()
{
return poolQueue.Dequeue();
}
Object对象池管理C#脚本对象,面板不可见,也不需要考虑Hierarchy层级关系(压根没有PoolRoot),构造函数直接将对象放入池子内即可,放入取出逻辑也不需要考虑HIerarchy层级(Object对象池实际就是字典-队列-对象三层,而GameObject对象池除此之外还要同步维护好Hierarchy中PoolRoot-FatherRoot-具体对象三层关系)。
PoolManager
PoolManager继承自ManagerBase管理器基类,挂载在GameRoot下,在游戏运行时调用Init方法完成单例实例化,负责对以上两种对象池进行管理,两个字典gameObjectPoolDic和objectPoolDic对对象池索引进行存储,poolRootObj作为所有对象池的父节点。
//class PoolManager
// 根节点
[SerializeField]
private GameObject poolRootObj;
/// <summary>
/// GameObject对象容器
/// </summary>
public Dictionary<string, GameObjectPoolData> gameObjectPoolDic = new Dictionary<string, GameObjectPoolData>();
/// <summary>
/// 普通类 对象容器
/// </summary>
public Dictionary<string, ObjectPoolData> objectPoolDic = new Dictionary<string, ObjectPoolData>();
public override void Init()
{
base.Init();
}
GameObject相关操作
PoolManager通过GetGameObject方法获取对象池中对象,传入GameObject作为查询目标提取Name得到对象池String类型Key值,在GameObject对象池字典中查找是否存在对应的对象池,存在调用下一层GetObj方法返回GameObject,如果不存在说明对象池中没有这一个对象池,实例化一个对象返回,也可额外传入泛型参数T来获取游戏对象上的某个组件(对于组件查不到就是查不到,实例化无意义,返回Null)。
/// <summary>
/// 获取GameObject
/// </summary>
/// <typeparam name="T">你最终组件</typeparam>
public T GetGameObject<T>(GameObject prefab, Transform parent = null) where T : UnityEngine.Object
{
GameObject obj = GetGameObject(prefab, parent);
if (obj != null)
{
return obj.GetComponent<T>();
}
return null;
}
/// <summary>
/// 获取GameObject
/// </summary>
public GameObject GetGameObject(GameObject prefab, Transform parent = null)
{
GameObject obj = null;
string name = prefab.name;
// 检查有没有这一层
if (CheckGameObjectCache(prefab))
{
obj = gameObjectPoolDic[name].GetObj(parent);
}
// 没有的话给你实例化一个
else
{
// 确保实例化后的游戏物体和预制体名称一致
obj = GameObject.Instantiate(prefab, parent);
obj.name = name;
}
return obj;
}
设计CheckGameObjectCache方法在对象放入出过程中进行键值校对,确保对象池中有对应的一层数据记录且对象容器不为空。
/// <summary>
/// 检查有没有某一层对象池数据
/// </summary>
private bool CheckGameObjectCache(GameObject prefab)
{
string name = prefab.name;
return gameObjectPoolDic.ContainsKey(name) && gameObjectPoolDic[name].poolQueue.Count > 0;
}
设计PushGameObject方法来将GameObject放回对象池,放入的检查逻辑较简单,直接在字典查Key,有就放进去,没有添加到字典中,并调用GameObjectData构造函数创建新数据,且在创建过程中会自动把本次的对象放到容器中。
/// <summary>
/// GameObject放进对象池
/// </summary>
public void PushGameObject(GameObject obj)
{
string name = obj.name;
// 现在有没有这一层
if (gameObjectPoolDic.ContainsKey(name))
{
gameObjectPoolDic[name].PushObj(obj);
}
else
{
gameObjectPoolDic.Add(name, new GameObjectPoolData(obj, poolRootObj));
}
}
设计CheckCacheAndLoadGameObject方法对二级目录进行分割来快速获取预制体的名字,再在字典中进行查找,适用于UI资源。
/// <summary>
/// 检查缓存 如果成功 则加载游戏物体 不成功返回Null
/// </summary>
/// <returns></returns>
public GameObject CheckCacheAndLoadGameObject(string path, Transform parent = null)
{
// 通过路径获取最终预制体的名称 "UI/LoginWindow"
string[] pathSplit = path.Split('/');
string prefabName = pathSplit[pathSplit.Length - 1];
// 对象池有数据
if (gameObjectPoolDic.ContainsKey(prefabName) && gameObjectPoolDic[prefabName].poolQueue.Count > 0)
{
return gameObjectPoolDic[prefabName].GetObj(parent);
}
else
{
return null;
}
}
Object对象相关操作
在普通对象相关操作中,类似的,设计GetObject、PushObject、CheckObjectCache方法实现对普通脚本对象进行收纳、检查。
/// <summary>
/// 获取普通对象
/// </summary>
public T GetObject<T>() where T : class, new()
{
T obj;
if (CheckObjectCache<T>())
{
string name = typeof(T).FullName;
obj = (T)objectPoolDic[name].GetObj();
return obj;
}
else
{
return new T();
}
}
/// <summary>
/// GameObject放进对象池
/// </summary>
/// <param name="obj"></param>
public void PushObject(object obj)
{
string name = obj.GetType().FullName;
// 现在有没有这一层
if (objectPoolDic.ContainsKey(name))
{
objectPoolDic[name].PushObj(obj);
}
else
{
objectPoolDic.Add(name, new ObjectPoolData(obj));
}
}
private bool CheckObjectCache<T>()
{
string name = typeof(T).FullName;
return objectPoolDic.ContainsKey(name) && objectPoolDic[name].poolQueue.Count > 0;
}
清空对象池
清空整个对象池中所有内容(GameObjectPool,ObjectPool),GameObject删除PoolRoot下所有子物体即可并清空字典,Object清空字典即可。
/// <summary>
/// 删除全部
/// </summary>
/// <param name="clearGameObject">是否删除游戏物体</param>
/// <param name="clearCObject">是否删除普通C#对象</param>
public void Clear(bool clearGameObject = true, bool clearCObject = true)
{
if (clearGameObject)
{
for (int i = 0; i < poolRootObj.transform.childCount; i++)
{
Destroy(poolRootObj.transform.GetChild(i).gameObject);
}
gameObjectPoolDic.Clear();
}
if (clearCObject)
{
objectPoolDic.Clear();
}
}
清空GameObjectPool或ObjectPool。
public void ClearAllObject()
{
Clear(false, true);
}
public void ClearAllGameObject()
{
Clear(true, false);
}
清空某一类GameObject对象池,根据Name查找子节点,删除并移除字典记录即可。
public void ClearGameObject(string prefabName)
{
GameObject go = poolRootObj.transform.Find(prefabName).gameObject;
if (go != null)
{
Destroy(go);
gameObjectPoolDic.Remove(prefabName);
}
}
public void ClearGameObject(GameObject prefab)
{
ClearGameObject(prefab.name);
}
清空某一类Object对象池,传泛型或者Type移除字典记录即可。
public void ClearObject<T>()
{
objectPoolDic.Remove(typeof(T).FullName);
}
public void ClearObject(Type type)
{
objectPoolDic.Remove(type.FullName);
}
PS:上文所说的三层是从数据角度描述的,从代码实现角度是两层,PoolManager持有PoolRoot和每个池子的父节点是第一层,具体的池子数据是第二层。