频繁创建和销毁对象会造成性能的开销。
创建对象的时候,系统会为这个对象开辟一片新的空间。销毁对象的时候,这个对象会变成内存垃圾,当内存垃圾达到一定程度,就会触发垃圾回收机制,清理内存垃圾,由于此时在清理垃圾,所以程序有可能就会变卡。
为了改善这个问题,我们就可以使用对象池。使用了它之后,程序的性能就能得到提升不那么容易变卡。
对象池的原理:
1、当要创建对象的时候,不直接创建,而是先从对象池里面找,如果对象池里有这种类型的对象,就从对象池中取出来用。如果对象池里面没有这种类型的对象,才创建该对象。
2、当要销毁对象的时候,不直接销毁,而是把这个对象放进对象池里面存着,以便下一次创建对象时使用。
一个预制体对应一个对象池,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;/// <summary>
/// 对象池
/// </summary>
public class ObjectPool : MonoBehaviour
{public GameObject prefabs; //这个对象池存储的游戏对象的预制体public int capcity = 100; //对象池的容量//从这个对象池中取出来正在使用的游戏对象public List<GameObject> usedGameObjectList = new List<GameObject>();//存在这个对象池中没有被使用的游戏对象public List<GameObject> unusedGameObjectList = new List<GameObject>();//这个池子总共拥有的游戏对象的个数public int TotalGameObjectCount { get => usedGameObjectList.Count + unusedGameObjectList.Count; }/// <summary>/// 从对象池中获取一个对象,并返回一个对象/// </summary>public GameObject Spawn(Vector3 position,Quaternion rotation,Transform parent=null){GameObject go;//池子中有if (unusedGameObjectList.Count > 0){go = unusedGameObjectList[0];unusedGameObjectList.RemoveAt(0);usedGameObjectList.Add(go);go.transform.localPosition = position;go.transform.localRotation = rotation;go.transform.SetParent(parent, false);go.SetActive(true);}else //对象池中没有,实例化{go = Instantiate(prefabs, position, rotation, parent);usedGameObjectList.Add(go);}//如果该游戏对象身上继承MonoBehaviour的脚本中写了名叫OnSpwan的方法,则会执行它们一次// 就算没有接收者也不报错go.SendMessage("OnSpawn", SendMessageOptions.DontRequireReceiver);return go;}//回收进对象池中public void DesPawn(GameObject go){if (go == null) return;//遍历这个对象池中所有正在使用的游戏对象for (int i = 0; i < usedGameObjectList.Count; i++){if (usedGameObjectList[i] == go){//如果这个对象池的容量不为负数,且容纳的游戏对象已经满了,则把0号游戏对象删除掉//确保之后新的游戏对象能放入池子中if (capcity >= 0 && usedGameObjectList.Count >= capcity){if (unusedGameObjectList.Count>0){DesPawn(unusedGameObjectList[0]);unusedGameObjectList.RemoveAt(0);}}//把游戏对象回收到对象池中unusedGameObjectList.Add(go);usedGameObjectList.RemoveAt(i);//如果该游戏对象身上继承MonoBehaviour的脚本写了名叫OnDespawn的方法,则在回收的时候会执行一次//这个方法用来在回收前将游戏对象还原go.SendMessage("OnDespawn", SendMessageOptions.DontRequireReceiver);go.SetActive(false);go.transform.SetParent(transform, false); //取消子物体return;}}}/// <summary>/// 把通过这个对象池生成的所有对象全部隐藏并放入对象池中/// </summary>public void DesPawnAll(){int count = usedGameObjectList.Count;for (int i = 1; i <= count; i++){DesPawn(usedGameObjectList[0]);}usedGameObjectList.Clear();}/// <summary>/// 预加载的方法,预先在池子里放几个对象/// </summary>public void PreLoad(int amount = 1){if (prefabs == null) return;if (amount <= 0) return;for (int i = 1; i <= amount ; i++){GameObject go = Instantiate(prefabs, Vector3.zero, Quaternion.identity);go.SetActive(false);go.transform.SetParent(transform, false);unusedGameObjectList.Add(go);go.name = prefabs.name; //更改一下生成的物体的名字}}}
一个预制体一个池子,然后一个大的池子管理所有的小对象池
using System.Collections;
using System.Collections.Generic;
using UnityEngine;/// <summary>
/// 对象池管理,可以通过这个管理器从对象池生成游戏对象,也可以回收游戏对象进对象池
/// </summary>
public class ObjectPoolsManager : SingletonPatternBase<ObjectPoolsManager>
{GameObject poolsParent; //所有对象池的父物体string poolsParentName = "ObjectPools"; //大对象池的名字//当前所有小对象池的列表public List<ObjectPool> objectPoolsList = new List<ObjectPool>();public Dictionary<GameObject, ObjectPool> objectDictionary = new Dictionary<GameObject, ObjectPool>();/// <summary>/// 从对象池生成游戏对象/// 如果对象池有,从对象池中取出来用/// 如果对象池没有,实例化该对象/// </summary>public GameObject Spawn(GameObject prefab,Vector3 position,Quaternion rotation,Transform parent=null){if (prefab == null) return null;//如果场景中没有对象池的父物体,则生成一个空物体作为所有对象池的父物体CreatePoolsParentIfNull();//先通过预制体查找它所属的小对象池,如果找到了,则返回这个小对象池//如果找不到,则创建一个小对象池,用来存放这种预制体ObjectPool objectPool = FindPoolByPrefabOrCreatePool(prefab);GameObject go = objectPool.Spawn(position, rotation, parent);objectDictionary.Add(go, objectPool);return go;}/// <summary>/// 回收的方法/// </summary>public void DesPawn(GameObject go,float delayTime=0){if (go == null) return;MonoManager.Instance.StartCoroutine(DespawnCoroutine(go, delayTime));}IEnumerator DespawnCoroutine(GameObject go, float delayTime = 0){//等待指定的秒数yield return new WaitForSeconds(delayTime);if (objectDictionary.TryGetValue(go, out ObjectPool pool)){objectDictionary.Remove(go);pool.DesPawn(go);}else{//获取这个游戏对象所属的对象池pool = FindPoolByUsedGameObject(go);if (pool != null){pool.DesPawn(go);}}}/// <summary>/// 通过正在使用的物体获取这个游戏对象所属的对象池/// </summary>ObjectPool FindPoolByUsedGameObject(GameObject go){if (go == null) return null;for (int i = 0; i < objectPoolsList.Count; i++){ObjectPool pool = objectPoolsList[i];for (int j = 0; j < pool.usedGameObjectList.Count; j++){if (pool.usedGameObjectList[j]==go){return pool;}}}return null;}/// <summary>/// 如果场景中没有对象池的父物体,则生成一个空物体作为所有对象池的父物体/// </summary>void CreatePoolsParentIfNull(){if (poolsParent == null){objectPoolsList.Clear();objectDictionary.Clear();poolsParent = new GameObject(poolsParentName);}}/// <summary>/// 先通过预制体查找它所属的小对象池,如果找到了,则返回这个小对象池/// 如果找不到,则创建一个小对象池,用来存放这种预制体/// </summary>/// <returns></returns>ObjectPool FindPoolByPrefabOrCreatePool(GameObject prefab){//确保大对象池存在CreatePoolsParentIfNull();//查找并返回该预制体对象的对象池ObjectPool objectPool = FindPoolByPrefab(prefab);if (objectPool == null){objectPool = new GameObject($"ObjectPool{prefab.name}").AddComponent<ObjectPool>();objectPool.prefabs = prefab;objectPool.transform.SetParent(poolsParent.transform);objectPoolsList.Add(objectPool);}return objectPool;}/// <summary>/// 查找并返回该预制体对象的对象池/// </summary>ObjectPool FindPoolByPrefab(GameObject prefab){if (prefab == null) return null;for (int i = 0; i < objectPoolsList.Count; i++){if (objectPoolsList[i].prefabs == prefab){return objectPoolsList[i];}}return null;}
}
物体身上最好写一个这样的方法将物体状态初始化,清楚一些脏数据
void OnDespawn()
{
transform.position = Vector3.zero;
GetComponent<Rigidbody>().velocity = Vector3.zero;
}