文章目录
- 什么是对象池?对象池有什么用?
- 对象池的原理
- 对象池的实现
- 1、从对象池获取对象
- 2、回收对象
- 3、回收所有对象
- 4、预先往这个对象池中加载指定数量的游戏对象
- 5、最终代码
- 封装对象池管理器
- 1、对象池管理器代码
- 2、测试调用
- 3、生成和回收游戏对象时自动调用的方法
- 完结
什么是对象池?对象池有什么用?
频繁创建和销毁对象会造成性能的开销。
创建对象的时候,系统会为这个对象开辟一片新的空间。销毁对象的时候,这个对象会变成内存垃圾,当内存垃圾达到一定程度,就会触发垃圾回收机制(及GC),清理内存垃圾,由于此时在清理垃圾,所以程序有可能就会变卡。
为了改善这个问题,我们就可以使用对象池。使用了它之后,程序的性能就能得到提升不那么容易变卡。
对象池的原理
1、当要创建对象的时候,不直接创建,而是先从对象池里面找,如果对象池里有这种类型的对象,就从对象池中取出来用。如果对象池里面没有这种类型的对象,才创建该对象。
2、当要销毁对象的时候,不直接销毁,而是把这个对象放进对象池里面存着,以便下一次创建对象时使用。
对象池的实现
1、从对象池获取对象
从对象池获取一个对象。如果对象池有,从对象池中取出来用。如果对象池没有,则实例化该对象。
/// <summary>
/// 对象池
/// </summary>
public class ObjectPool : MonoBehaviour
{// 这个对象池存储的游戏对象的预制体public GameObject prefab;// 对象池最多能容纳多少个游戏对象。负数表示可以容纳无数个。public int capacity = -1;// 从这个对象池中取出并正在使用的游戏对象。public List<GameObject> usedGameObjectList = new List<GameObject>();// 存在这个对象池中没有被使用的游戏对象。public List<GameObject> unUsedGameObjectList = new List<GameObject>();// 这个对象池中正在使用和没有被使用的游戏对象的总数public int TotalGameObjectCount => usedGameObjectList.Count + unUsedGameObjectList.Count;/// <summary>/// 从对象池获取一个对象。/// 如果对象池有,从对象池中取出来用。/// 如果对象池没有,则实例化该对象。/// </summary>/// <param name="position">位置</param>/// <param name="rotation">旋转</param>/// <param name="parent">父级</param>/// <returns>返回这个对象</returns>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(prefab, position, rotation, parent);usedGameObjectList.Add(go);}// 如果该游戏对象身上继承MonoBehaviour的脚本中写了名叫OnSpawn的方法,则会执行它们一次。go.SendMessage("OnSpawn", SendMessageOptions.DontRequireReceiver);return go;}}
2、回收对象
隐藏指定的游戏对象,并把它回收进对象池中。
/// <summary>
/// 回收对象
/// </summary>
/// <param name="go">要回收的对象</param>
public void Despawn(GameObject go)
{if (go == null) return;// 遍历这个对象池中所有正在使用的游戏对象for (int i = 0; i < usedGameObjectList.Count; i++){if (usedGameObjectList[i] == go){// 如果这个对象池的容量不为负数,且容纳的游戏对象已经满,则把0号的游戏对象剔除掉if (capacity >= 0 && usedGameObjectList.Count >= capacity){if (unUsedGameObjectList.Count > 0){Destroy(unUsedGameObjectList[0]);unUsedGameObjectList.RemoveAt(0);}}// 把游戏对象回收到对象池中unUsedGameObjectList.Add(go);usedGameObjectList.RemoveAt(i);go.SetActive(false);go.transform.SetParent(transform, false);// 如果该游戏对象身上继承MonoBehaviour的脚本写了名叫OnDespawn的方法,则在回收的时候,会执行一次。go.SendMessage("OnDespawn", SendMessageOptions.DontRequireReceiver);return;}}
}
3、回收所有对象
把通过这个对象池生成的所有游戏对象全部隐藏并放入对象池中
/// <summary>
/// 回收所有对象,把通过这个对象池生成的所有游戏对象全部隐藏并放入对象池中
/// </summary>
public void DespawnAll()
{int count = usedGameObjectList.Count;for (int i = 0; i < count; i++){Despawn(usedGameObjectList[0]);}
}
4、预先往这个对象池中加载指定数量的游戏对象
/// <summary>
/// 预先往这个对象池中加载指定数量的游戏对象
/// </summary>
/// <param name="amount">预先生成游戏对象数量</param>
public void Preload(int amount = 1)
{if (prefab == null) return;if (amount <= 0) return;for (int i = 0; i < amount; i++){GameObject go = Instantiate(prefab, Vector3.zero, Quaternion.identity);go.SetActive(false);go.transform.SetParent(transform, false);unUsedGameObjectList.Add(go);go.name = prefab.name;}
}
5、最终代码
using System.Collections.Generic;
using UnityEngine;/// <summary>
/// 对象池
/// </summary>
public class ObjectPool : MonoBehaviour
{//这个对象池存储的游戏对象的预制体public GameObject prefab;//对象池最多能容纳多少个游戏对象。负数表示可以容纳无数个。public int capacity = -1;//从这个对象池中取出并正在使用的游戏对象public List<GameObject> usedGameObjectsList = new List<GameObject>();//存在这个对象池中并没有被使用的游戏对象public List<GameObject> unUsedGameObjectsList = new List<GameObject>();//这个对象池中正在使用和没有被使用的游戏对象的总数public int TotalGameObjectsCount { get => usedGameObjectsList.Count + unUsedGameObjectsList.Count; }/// <summary>/// <para>从对象池获取一个对象</para>/// <para>如果对象池中有,则从对象池中取出来用。</para>/// <para>如果对象池中没有,则实例化该对象。</para>/// </summary>/// <param name="position">生成游戏对象的位置</param>/// <param name="rotation">生成游戏对象的旋转</param>/// <param name="parent">生成的游戏对象的父物体</param>/// <returns>返回这个对象</returns>public GameObject Spawn(Vector3 position, Quaternion rotation, Transform parent = null){GameObject go;if (unUsedGameObjectsList.Count > 0){go = unUsedGameObjectsList[0];unUsedGameObjectsList.RemoveAt(0);usedGameObjectsList.Add(go);go.transform.localPosition = position;go.transform.localRotation = rotation;go.transform.SetParent(parent, false);go.SetActive(true);}else{go = Instantiate(prefab, position, rotation, parent);usedGameObjectsList.Add(go);}//如果该游戏对象身上继承Monobehavior的脚本中写了名叫OnSpawn的方法,则会执行它们。go.SendMessage("OnSpawn", SendMessageOptions.DontRequireReceiver);return go;}/// <summary>/// <para>隐藏指定的游戏对象,并把它回收进对象池中。</para>/// </summary>/// <param name="go">要放入对象池的游戏对象</param>public void Despawn(GameObject go){if (go == null) return;//遍历这个对象池中所有正在使用的游戏对象for (int i = 0; i < usedGameObjectsList.Count; i++){if (usedGameObjectsList[i] == go){//如果这个对象池的容量不为负数,且容纳的游戏对象已经满了,则把0号的游戏对象删掉,确保之后新的游戏对象能放入到池子中。if (capacity >= 0 && unUsedGameObjectsList.Count >= capacity){if (unUsedGameObjectsList.Count > 0){Destroy(unUsedGameObjectsList[0]);unUsedGameObjectsList.RemoveAt(0);}}//把游戏对象放入到对象池中unUsedGameObjectsList.Add(go);usedGameObjectsList.RemoveAt(i);//如果该游戏对象身上继承Monobehavior的脚本中写了名叫OnDespawn的方法,则会执行它们。go.SendMessage("OnDespawn", SendMessageOptions.DontRequireReceiver);go.SetActive(false);go.transform.SetParent(transform, false);return;}}}/// <summary>/// 把通过这个对象池生成的所有游戏对象,全部隐藏并放入对象池中/// </summary>public void DespawnAll(){int count = usedGameObjectsList.Count;for (int i = 1; i <= count; i++){Despawn(usedGameObjectsList[0]);}//清空列表usedGameObjectsList.Clear();}/// <summary>/// <para>在这个对象池中预加载指定数量的游戏对象。</para>/// </summary>/// <param name="amount">要预加载的数量</param>public void Preload(int amount = 1){if (prefab == null) return;if (amount <= 0) return;for (int i = 1; i <= amount; i++){GameObject go = Instantiate(prefab, Vector3.zero, Quaternion.identity);go.SetActive(false);go.transform.SetParent(transform, false);unUsedGameObjectsList.Add(go);go.name = prefab.name;}}
}
封装对象池管理器
1、对象池管理器代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;/// <summary>
/// 对象池管理器
/// </summary>
public class ObjectPoolsManager : Singleton<ObjectPoolsManager>
{//所有对象池的父物体GameObject poolsParent;//所有对象池共同父物体的名字readonly string poolsParentName = "ObjectPools";//当前所有对象池的列表public List<ObjectPool> objectPoolsList = new List<ObjectPool>();//键:由对象池生成的并且正在使用的游戏对象//值:这个游戏对象所属的对象池public Dictionary<GameObject, ObjectPool> objectsDictionary = new Dictionary<GameObject, ObjectPool>();/// <summary>/// <para>从对象池获取一个对象</para>/// <para>如果对象池中有,则从对象池中取出来用。</para>/// <para>如果对象池中没有,则实例化该对象。</para>/// </summary>/// <param name="prefab">要生成的游戏对象的预制体</param>/// <param name="position">生成游戏对象的位置。如果没有设置父物体,则是世界坐标。如果设置了父物体,则是局部坐标。</param>/// <param name="rotation">生成游戏对象的旋转</param>/// <param name="parent">>生成游戏对象的父物体</param>/// <returns>返回游戏对象</returns>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);//把生成的游戏对象与它所属的对象池记录到字典objectsDictionary.Add(go, objectPool);return go;}/// <summary>/// <para>隐藏指定的游戏对象,并把它回收进对象池中。</para>/// </summary>/// <param name="go">要放入对象池的游戏对象</param>/// <param name="delayTime">延迟多少秒执行</param>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){//等待指定秒数if (delayTime > 0)yield return CoroutineTool.WaitForSeconds(delayTime);//先从象池生成的正在使用的游戏对象的字典中找指定的游戏对象if (objectsDictionary.TryGetValue(go, out ObjectPool pool)){objectsDictionary.Remove(go);//把这个游戏对象放入找到的对象池pool.Despawn(go);}else{//获取这个游戏对象所属的对象池pool = FindPoolByUsedGameObject(go);if (pool != null)pool.Despawn(go);}}/// <summary>/// 把所有通过对象池生成的对象全部隐藏,并回收进对象池中。/// </summary>public void DespawnAll(){for (int i = 0; i < objectPoolsList.Count; i++){objectPoolsList[i].DespawnAll();}objectsDictionary.Clear();}/// <summary>/// <para>在对象池中预加载指定数量的游戏对象。</para>/// </summary>/// <param name="prefab">游戏对象的预制体</param>/// <param name="amount">要预加载的数量</param>public void Preload(GameObject prefab, int amount = 1){if (prefab == null) return;if (amount <= 0) return;//通过预制体来查找它所属的对象池ObjectPool pool = FindPoolByPrefabOrCreatePool(prefab);//预加载指定数量的游戏对象pool.Preload(amount);}/// <summary>/// <para>返回指定的预制体所属的对象池的容量。</para>/// <para>如果该对象池不存在,则会创建它,然后返回-1。</para>/// </summary>/// <param name="prefab">预制体</param>public int GetCapacity(GameObject prefab){ObjectPool pool = FindPoolByPrefabOrCreatePool(prefab);return pool.capacity;}/// <summary>/// <para>设置指定的预制体所属的对象池的容量。</para>/// <para>如果该对象池不存在,则会创建它,然后再设置它的容量。</para>/// </summary>/// <param name="prefab">预制体</param>/// <param name="capacity">要设置的容量。如果设置为负数,则表示这个对象池可以容纳无数个游戏对象。</param>public void SetCapacity(GameObject prefab, int capacity = -1){ObjectPool pool = FindPoolByPrefabOrCreatePool(prefab);pool.capacity = capacity;}/// <summary>///生成对象池的父物体/// </summary>void CreatePoolsParentIfNull(){if (poolsParent == null){//清空列表和字典,避免上一个场景的数据的影响。objectPoolsList.Clear();objectsDictionary.Clear();//生成一个空物体poolsParent = new GameObject(poolsParentName);}}/// <summary>/// <para>查找指定的预制体所属的对象池</para>/// </summary>/// <param name="prefab">预制体</param>ObjectPool FindPoolByPrefab(GameObject prefab){if (prefab == null) return null;for (int i = 0; i < objectPoolsList.Count; i++){if (objectPoolsList[i].prefab == prefab)return objectPoolsList[i];}return null;}/// <summary>/// <para>查找指定的游戏对象所属的对象池</para>/// </summary>/// <param name="go">要查找的游戏对象</param>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.usedGameObjectsList.Count; j++){if (pool.usedGameObjectsList[j] == go)return pool;}}return null;}/// <summary>/// <para>通过预制体来查找它所属的对象池</para>/// </summary>/// <param name="prefab">预制体</param>ObjectPool FindPoolByPrefabOrCreatePool(GameObject prefab){//生成对象池的父物体CreatePoolsParentIfNull();//查找并返回该预制体所属的对象池ObjectPool objectPool = FindPoolByPrefab(prefab);//如果该对象池不存在,则创建一个。if (objectPool == null){//创建一个对象池objectPool = new GameObject($"ObjectPool-{prefab.name}").AddComponent<ObjectPool>();//设置这个对象池所管理的预制体objectPool.prefab = prefab;//把生成的对象池放到父物体中,方便管理。objectPool.transform.SetParent(poolsParent.transform);//记录这个对象池objectPoolsList.Add(objectPool);}return objectPool;}
}
2、测试调用
public class PoolTest : MonoBehaviour
{private void OnGUI(){if (GUI.Button(new Rect(0, 0, 150, 70), "生成游戏对象")){GameObject prefab = ResourcesManager.Instance.Load<GameObject>("Prefabs/Cube");GameObject go = ObjectPoolsManager.Instance.Spawn(prefab, transform.position, Quaternion.identity, transform);go.GetComponent<Rigidbody>().AddForce(Vector3.forward * 1000f);//加力//5秒后回收ObjectPoolsManager.Instance.Despawn(go, 5f);}}
}
效果
3、生成和回收游戏对象时自动调用的方法
在预制体添加脚本,书写生成和回收游戏对象时自动调用的方法,可以在上面书写对象初始化方法,重置对象参数
public class Cube : MonoBehaviour {//生成游戏对象时自动调用void OnSpawn(){Debug.Log("生成游戏对象");}//回收游戏对象时自动调用void OnDespawn(){Debug.Log("回收游戏对象");//重置参数transform.position = Vector3.zero;GetComponent<Rigidbody>().velocity = Vector3.zero;}
}
效果
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~