书接上回,上次介绍了对象池在游戏开发中的概念,
这篇文章我们来看一看在一款游戏demo中如何运用对象池技术和资源加载器结合来应对反复生成的游戏对象
我们先编写一个简单的Resources资源加载器
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class ResMgr : BaseMgr<ResMgr>
{
/// <summary>
/// 同步加载资源
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns></returns>
public T Load<T>(string name) where T : Object
{
T obj = Resources.Load<T>(name);
if (obj == null)
{
Debug.LogError("不存在资源:" + typeof(T) + ":" + name);
}
//如果对象类型是GameObject 直接实例化后再传出去 直接使用即可
if (obj is GameObject)
return GameObject.Instantiate(obj);
else
return obj;
}
/// <summary>
/// 异步加载资源
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <param name="callback"></param>
public void LoadAsync<T>(MonoBehaviour mono, string name,System.Action<T> callback) where T:Object
{
mono.StartCoroutine(ReallyLoadAsync(name, callback));
}
private IEnumerator ReallyLoadAsync<T>(string name,System.Action<T> callback) where T : Object
{
ResourceRequest obj = Resources.LoadAsync<T>(name);
yield return obj;
if (obj.asset is GameObject)
callback(GameObject.Instantiate((T)obj.asset));
else
callback(obj.asset as T);
}
}
有了资源加载器,现在我们就可以完成对象池了
首先,我们先创造一个类,用来储存和处理对象池中抽屉数据的数据类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class PoolData
{
//抽屉中 对象挂载的父节点
public GameObject fatherObj;
//对象的容器
public List<GameObject> poolList;
public PoolData(string typeName,GameObject obj, GameObject poolObj)
{
//给我们的抽屉 创建一个父对象 并且把他作为我们pool(衣柜)对象的子物体
fatherObj = new GameObject(typeName);
fatherObj.transform.SetParent(poolObj.transform);
Debug.Log("PoolData构造函数:"+fatherObj.name);
poolList = new List<GameObject>() {};
PushObj(obj);
}
/// <summary>
/// 往抽屉里面 压都东西
/// </summary>
/// <param name="obj"></param>
public void PushObj(GameObject obj)
{
if (obj == null) return;
//失活 让其隐藏
obj.SetActive(false);
//存起来
poolList.Add(obj);
//设置父对象
obj.transform.SetParent(fatherObj.transform);
}
/// <summary>
/// 从抽屉里面 取第一个东西
/// </summary>
/// <returns></returns>
public GameObject GetObj()
{
GameObject obj = null;
//取出第一个
obj = poolList[0];
if (obj == null)
{
Debug.LogWarning("不存在这个对象:"+poolList[0]);
return null;
}
poolList.RemoveAt(0);
//断开了父子关系
//obj.transform.parent = null;
obj.transform.SetParent(null);
//激活 让其显示
obj.SetActive(true);
return obj;
}
/// <summary>
/// 根据实际Obj直接取出对应的缓冲池对象
/// </summary>
/// <param name="targetObj">目标缓冲池对象</param>
/// <returns></returns>
public GameObject GetObjByGameObject(GameObject targetObj)
{
foreach (GameObject obj in poolList)
{
if (obj.Equals(targetObj))
{
poolList.Remove(obj);
//断开了父子关系
obj.transform.parent = null;
//激活 让其显示
obj.SetActive(true);
return obj;
}
}
return null;
}
}
这个类主要实现存储数据,将对象压入单个抽屉和读取的功能,然后我们来制作一个PoolManager来实现创建和管理对象池,代码附上:
/// <summary>
/// 缓存池模块
/// 1.Dictionary List
/// 2.GameObject 和 Resources 两个公共类中的 API
/// </summary>
public class PoolMgr : BaseMgr<PoolMgr>
{
//缓存池容器 (衣柜)
public Dictionary<string, PoolData> poolDic = new Dictionary<string, PoolData>();
private GameObject poolObj;
/// <summary>
/// 当没有缓冲池对象的时候,异步加载对象
/// </summary>
/// <param name="poolName">本缓存池类型名</param>
/// <param name="path">如果没有对应缓冲池,用Resources获取的地址或者对象名</param>
/// <param name="callBack">对缓冲池类型对象执行的函数</param>
public void AsyncGetObj(MonoBehaviour mono,string poolName,string path, UnityAction<GameObject> callBack)
{
//有抽屉 并且抽屉里有对应路径对象
if (poolDic.ContainsKey(poolName) && poolDic[poolName].poolList.Count>0)
{
foreach(var na in poolDic.Keys)
{
Debug.Log("poolDic包含:" + na);
}
callBack(poolDic[poolName].GetObj());
}
else
{
//通过异步加载资源 创建对象给外部用
ResMgr.GetInstance().LoadAsync<GameObject>(mono,path, (o) =>
{
//将资源名设置为路径名,方便下次再次获取
o.name = path;
callBack(o);
});
}
}
/// <summary>
/// 当没有缓冲池对象的时候,同步加载缓冲池对象
/// </summary>
/// <param name="poolName"></param>
/// <param name="path"></param>
/// <returns></returns>
public GameObject GetObj(string poolName,string path)
{
//有抽屉 并且抽屉里有对应路径对象
if (poolDic.ContainsKey(poolName) && poolDic[poolName].poolList.Count > 0 )
{
return poolDic[poolName].GetObj();
}
else
{
GameObject obj = null;
obj = ResMgr.GetInstance().Load<GameObject>(path);
obj.name = path;
return obj;
}
}
/// <summary>
/// 根据实际Obj直接取出对应的缓冲池对象
/// </summary>
/// <param name="poolName">缓冲池类型名</param>
/// <param name="targetObj">目标对象</param>
public GameObject GetObjByGameObject(string poolName,GameObject targetObj)
{
if (poolDic.ContainsKey(poolName) && poolDic[poolName].poolList.Count > 0)
{
return poolDic[poolName].GetObjByGameObject(targetObj);
}
Debug.Log("你试图取出不存在的缓冲池对象!");
return null;
}
/// <summary>
/// 存入缓冲池
/// </summary>
/// <param name="poolName">本缓存池类型名</param>
/// <param name="obj">资源对象</param>
public void PushObj(string poolName, GameObject obj)
{
if (poolObj == null)
poolObj = new GameObject("Pool");
//里面有抽屉
if (poolDic.ContainsKey(poolName))
{
poolDic[poolName].PushObj(obj);
}
//里面没有抽屉
else
{
poolDic.Add(poolName, new PoolData(poolName,obj, poolObj));
}
}
/// <summary>
/// 清空缓存池的方法
/// 主要用在 场景切换时
/// </summary>
public void Clear()
{
poolDic.Clear();
poolObj = null;
}
}
PoolManager的功能就是创建一个对象池并对其中的抽屉数据进行管理 .
附上效果图:
总结:第一个对象池实践,还有很多细节没有实现得很好,没有实现UI和3D对象的对象池分离管理,还有很多不足的地方,望指正,欢迎学习交流.
路漫漫其修远兮,吾将上下而求索