参考:
1.Unity —– 对象池GameObjectPool
2.Unity3D内存管理——对象池(Object Pool)
游戏开发中需要经常实例化和访问IO来动态加载和调用资源,频繁的访问和调用IO组件是一个比较消耗资源的操作,因此,为了提升游戏的体验效果,一般会使用对象池来预先存储需要大量实例化的对象和资源,当真正需要使用对象和资源的时候,不是调用IO组件去实例化资源,而是从对象池中将预先实例化好的对象取出来使用,用完之后,当需要将对象销毁的时候,并不是调用Destroy()方法将对象直接销毁,而是将对象的状态设置Seactive(false),并把对象重新放入池中,再次使用的时候,对对象进行初始化(或者可以在放入对象池的时候)。对象池虽然很好用,但是由于对象池会预先实例化一些对象存储在其中,会占据一定的存储空间,因此,也需要注意该什么时候建立、使用、释放对象池。说了这么多,具体看代码分析。
首先创建一个对象池,在对象池中预先生成需要实例化的对象,为了对象池能被全局访问,将对象池定义为一个单例模式:
private static MyObjectPool _instance;//创建一个单例模式
public static MyObjectPool Instance
{
get { return _instance; }
}
void Start()
{
_instance = this;
}
声明两个字典类型的集合:
//用于存储需要实例化的对象,以及实例化后对象的集合
Dictionary<GameObject, List<GameObject>> pooledObjects =
new Dictionary<GameObject,List<GameObject>();
Dictionary<GameObject, GameObject> spawnedObjects =
new Dictionary<GameObject, GameObject>();
声明一个数据配置类,用于保存需要实例化的对象以及需要实例化的个数:
//不继承自Monobehavior的类,需要将类型序列化
//PoolConfig类用来配置需要实例化的对象的预制和需要生成的数量
[Serializable]
public class PoolConfig
{
public int count;//实例化对象的数量
public GameObject prefab;//需要实例化的对象的预制
}
public PoolConfig[] poolConfigs;//可在Inspector中配置实例化的对象和数量
创建对象池,并向对象池中预先存储需要实例化的对象,在合适的时候调用CreatePool方法,可以放在场景的入口,或者通过某个函数触发(因为创建对象池会占用一定的存储空间,所以选择合适的建池时机是非常有必要的),这里做测试,我将对象池的创建放大了Start函数中。
void Start()
{
_instance = this;
//根据配置文件,初始化池子,并创建对象,根据PoolConfig中的配置创建
for (int i = 0; i < this.poolconfigs.Length; i++)
{
CreatePool(this.poolconfigs[i].prefab,this.poolconfigs[i].count);
}
}
创建和生成对象池,并向对象池中添加需要实例化的对象
public static void CreatePool(GameObject prefab, int initialPoolSize)
{
//是否预制为空,且池中不包含任何对象
if (prefab != null && !Instance.pooledObjects.ContainsKey(prefab))
{
var list = new List<GameObject>();
Instance.pooledObjects.Add(prefab, list);
if (initialPoolSize > 0)
{
bool active = prefab.activeSelf;
Transform parent = Instance.transform;
while (list.Count < initialPoolSize)
{
var obj = GameObject.Instantiate(prefab);
obj.transform.parent = parent;
list.Add(obj);
}
prefab.SetActive(false);
}
}
}
对象池已经创建好了,池子中也存放了一些需要实例化的对象了,接下来,我们就需要从池子中取对象了。
//从池子中取对象,并初始化这个对象的一些属性,比如:坐标、缩放、以及父对象等等
public static GameObject Spawn(GameObject prefab, Transform parnet, Vector3 position, Quaternion rotation)
{
List<GameObject> list;
Transform trans;
GameObject obj;
//判断池子中是否存在该对象,如果存在,则取出,不存在,则创建一个新的对象
if (Instance.pooledObjects.TryGetValue(prefab, out list))
{
obj = null;
if (list.Count > 0)
{
while (obj == null && list.Count > 0)
{
obj = list[0];
list.RemoveAt(0);
}
if (obj != null)
{
trans = obj.transform;
trans.parent = parnet;
trans.localPosition = position;
trans.localRotation = rotation;
obj.SetActive(true);
Instance.spawnedObjects.Add(obj, prefab);
return obj;
}
}
obj = GameObject.Instantiate(prefab);
trans = obj.transform;
trans.parent = parnet;
trans.localPosition = position;
trans.localRotation = rotation;
obj.SetActive(true);
Instance.spawnedObjects.Add(obj, prefab);
return obj;
}
else
{
obj = GameObject.Instantiate(prefab);
trans = obj.GetComponent<Transform>();
trans.parent = parnet;
trans.localPosition = position;
trans.localRotation = rotation;
return obj;
}
当我们不在需要使用刚才取出来的对象时,我们并不是调用Destroy()函数将对象销毁,其实调用Destroy()函数之后,系统并不会马上将内存返还给程序,大量的Destroy()会造成应用卡顿。所以,我们调用对象池,将对象回收,再次存放到对象池中,并将对象的状态设置为:SetActive(false);
//回收对象的时候,我们可能需要做一些数据初始化的操作
//我们想函数参数中创建一个委托,使得对象的初始化工作,可以在对象本身执行
public static void Recyle(GameObject obj, Action RecyleAction)
{
GameObject prefab;
if (Instance.spawnedObjects.TryGetValue(obj, out prefab))
{
// 将对象从添加到池中,并从生成对象中移除
Instance.pooledObjects[prefab].Add(obj);
Instance.spawnedObjects.Remove(obj);
obj.transform.parent = Instance.transform;
//如果委托的定义不为空,则调用委托执行对象的初始化
if (RecyleAction != null)
{
RecyleAction();
}
}
else
{
Debug.Log("没有找到,要把对象销毁");
Destroy(obj);
}
}
到这里一个对象池需要具备的基本功能差不多就完成了,下面,分析一下使用对象池创建、回收对象的方法。
一、调用对象池实例化对象
//脚本用于从对象池中取对象,将脚本挂载到需要实例化对象的地方
MyObjectPool.Spawn(this.cube, this.transform, Vector3.zero, Quaternion.identity);
二、当资源需要释放时,调用回收函数
//声明一个委托,在对象呗回收时调用,主要执行一些对象初始化的工作
private Action recyleAction;
void OnEnable ()
{
StartCoroutine("RecyleObject");
this.recyleAction = Recyle;
IEnumerator RecyleObject()
{
yield return new WaitForSeconds(2);
//当对象被实例化两秒之后回收对象,具体可以按照项目需求来自己定制
MyObjectPool.Recyle(this.gameObject,this.Recyle);
}
void Recyle()
{
this.GetComponent<Rigidbody>().velocity=Vector3.one;
this.GetComponent<Rigidbody>().angularVelocity=Vector3.zero;
this.gameObject.SetActive(false);
recyleAction=null;
Debug.Log("开始回收了");
}
到这里,对象池的创建、使用、回收就差不多了,希望能帮到你。