Unity 对象池

Unity 对象池概念我们先讲讲对象池是什么 有什么用 在游戏的制作过程中 我们可能会碰到这样的情况 就像现在最火的吃鸡游戏一样 我们有一把枪 开枪的时候射出子弹 每个子弹即一个对象 正常情况 我们的处理方式可能会是 每开一枪 就 GameObject Instantiate 一个新的子弹 当子弹到达极限距离的时候再 GameObject Destroy 销毁它 假设有射出 1000 发子弹 我们就会执行 1000 次这样

概念

我们先讲讲对象池是什么,有什么用。在游戏的制作过程中,我们可能会碰到这样的情况,就像现在最火的吃鸡游戏一样,我们有一把枪,开枪的时候射出子弹。每个子弹即一个对象,正常情况,我们的处理方式可能会是,每开一枪,就GameObject.Instantiate()一个新的子弹,当子弹到达极限距离的时候再GameObject.Destroy()销毁它。假设有射出1000发子弹,我们就会执行1000次这样的操作,然而在Unity中Instantiate和Destroy操作,不仅影响性能还容易产生内存碎片,总之就是要尽量少做这种操作。这个时候就有对象池这个概念。

所谓对象池,就是针对需要经常生成消失的对象。我们在对象需要消失的时候不Destroy而是SetActive(false),然后放入池子中(Queue),当需要再次显示一个新的对象的时候,先去池子中看有没有隐藏的对象,有就取出SetActive(true),若池子里没有可用的则再Instantiate。

还是上面的例子,假设我们玩家能看见的子弹只有10发,那么在连续开枪1000次的时候,我们生成第一发子弹的时候,此时对象池为空,我们Instantiate一个子弹,第二到第十发子弹的时候同理,再Instantiate九个子弹,第十一发的时候,前十发子弹还是处于显示状态,池子中依旧为空,我们继续Instantiate一个子弹,当这个子弹射出的时候,我们的第一发子弹就应该无法看见了,我们即可SetActive(false),然后放入对象池中。当第12发子弹的时候,我们这个时候对象池里就有一个可用对象,即第一发子弹,我们取出并显示它,然后将它的起始坐标更新到枪口。第13发子弹之后就如此循环。这样,我们在1000发子弹的时候只需要执行11次Instantiate和Destroy操作,起到优化作用。

实现

注:实现的方式有很多,下面只是个人的一种方式,大家可以针对自己的需求任意修改。

了解了上面的概念之后,实现起来就很方便了。首先我们先生成一个对象池的class,里面的内容就很简单,首先有一个Queue用来存放池子中的对象,然后实现两个方法,一个取对象,一个销毁对象,取对象的时候,若池子中有可用对象则取出一个,若没有则Instantiate一个。销毁对象即将对象SetActive(false)并且放入池子中。代码如下

public class BaseGameObjectPool { ///  /// 队列,存放对象池中没有用到的对象,即可分配对象 ///  protected Queue m_queue; ///  /// 对象池中存放最大数量 ///  protected int m_maxCount; ///  /// 对象预设 ///  protected GameObject m_prefab; ///  /// 该对象池的transform ///  protected Transform m_trans; ///  /// 每个对象池的名称,当唯一id ///  protected string m_poolName; ///  /// 默认最大容量 ///  protected const int m_defaultMaxCount = 10; public BaseGameObjectPool() { m_maxCount = m_defaultMaxCount; m_queue = new Queue(); } public virtual void Init(string poolName, Transform trans) { m_poolName = poolName; m_trans = trans; } public GameObject prefab { set { m_prefab = value; } } public int maxCount { set { m_maxCount = value; } } ///  /// 生成一个对象 ///  /// 起始坐标 /// 对象存在的时间 /// 
    
      生成的对象 
     public virtual GameObject Get(Vector3 position, float lifetime) { if(lifetime < 0) { //lifetime<0时,返回null return null; } GameObject returnObj; if(m_queue.Count > 0) { //池中有待分配对象 returnObj = (GameObject)m_queue.Dequeue(); } else { //池中没有可分配对象了,新生成一个 returnObj = GameObject.Instantiate(m_prefab) as GameObject; returnObj.transform.SetParent(m_trans); returnObj.SetActive(false); } //使用PrefabInfo脚本保存returnObj的一些信息 GameObjectPoolInfo info = returnObj.GetComponent 
    
      (); if(info == null) { info = returnObj.AddComponent 
     
       (); } info.poolName = m_poolName; if(lifetime > 0) { info.lifetime = lifetime; } returnObj.transform.position = position; returnObj.SetActive(true); return returnObj; } /// 
       /// “删除对象”放入对象池 ///  /// 
      对象 public virtual void Remove(GameObject obj) { //待分配对象已经在对象池中 if(m_queue.Contains(obj)) { return; } if(m_queue.Count > m_maxCount) { //当前池中object数量已满,直接销毁 GameObject.Destroy(obj); } else { //放入对象池,入队 m_queue.Enqueue(obj); obj.SetActive(false); } } ///  /// 销毁该对象池 ///  public virtual void Destroy() { m_queue.Clear(); } }  
      
    

同时,这也是个基类,针对一些特殊的对象,可能会有些独特的操作,我们可以单独生成一个类继承于它,进行修改。举个例子,下面这个对象池的预设取自AssetBundle:

public class CubePool : BaseGameObjectPool { PrefabAssetBundleItem m_cubeAsset; public CubePool() : base() { } public override void Init(string poolName, Transform trans) { base.Init(poolName, trans); m_cubeAsset = new PrefabAssetBundleItem("", "Cube"); m_cubeAsset.Load(); m_prefab = m_cubeAsset.prefab; } public override GameObject Get(Vector3 position, float lifetime) { lifetime = 3; return base.Get(position, lifetime); } public override void Destroy() { base.Destroy(); m_cubeAsset.Destroy(); } } 

上面的代码大家会发现,在生成一个新对象的时候,我们给对象添加了一个GameObjectPoolInfo的自定义组件。前面也提到了,对象池主要针对显示之后一段时间就会消失的对象。所以这个自定义组件的作用就是设置了一个显示时间,当时间到了之后将该对象加入对象池。

public class GameObjectPoolInfo : MonoBehaviour { ///  /// 对象显示的持续时间,若=0,则不隐藏 ///  [HideInInspector] public float lifetime = 0; ///  /// 所属对象池的唯一id ///  [HideInInspector] public string poolName; WaitForSeconds m_waitTime; void Awake() { if(lifetime > 0) { m_waitTime = new WaitForSeconds(lifetime); } } void OnEnable() { if(lifetime > 0) { StartCoroutine(CountDown(lifetime)); } } IEnumerator CountDown(float lifetime) { yield return m_waitTime; //将对象加入对象池 GameObjectPoolManager.instance.RemoveGameObject(poolName, gameObject); } }

同时我们还需要生成一个对象池的管理类,因为我们可能会有很多的对象池,比如AK的子弹是一类对象池,M4的子弹是另一类。我们需要将这些对象池都存在一个字典当中,方便后续处理

//SingleClass就是单例,大家可以自己来实现 public class GameObjectPoolManager : SingleClass 
   
     { /// 
     /// 存放所有的对象池 ///  Dictionary 
    
      m_poolDic; /// 
      /// 对象池在场景中的父控件 /// 本例中将对象池的对象都放在了一个单独的gameobject下,大家可以按照自己的需求来乱放 ///  Transform m_parentTrans; public GameObjectPoolManager() { m_poolDic = new Dictionary 
     
       (); //生成一个新的GameObject存放所有的对象池对象 GameObject go = new GameObject("GameObjectPoolManager"); m_parentTrans = go.transform; } /// 
       /// 创建一个新的对象池 ///  /// 
      
        对象池类型 
       /// 
      对象池名称,唯一id /// 
       
         对象池对象 
        public T CreatGameObjectPool 
       
         (string poolName) where T : BaseGameObjectPool, new() { if(m_poolDic.ContainsKey(poolName)) { return (T)m_poolDic[poolName]; } GameObject obj = new GameObject(poolName); obj.transform.SetParent(m_parentTrans); T pool = new T(); pool.Init(poolName, obj.transform); m_poolDic.Add(poolName, pool); return pool; } /// 
         /// 从对象池中取出新的对象 ///  /// 
        对象池名称 /// 对象新坐标 /// 对象显示时间 /// 
           
             新对象 
            public GameObject GetGameObject(string poolName, Vector3 position, float lifeTime) { if(m_poolDic.ContainsKey(poolName)) { return m_poolDic[poolName].Get(position, lifeTime); } return null; } ///  /// 将对象存入对象池中 ///  /// 对象池名称 /// 对象 public void RemoveGameObject(string poolName, GameObject go) { if(m_poolDic.ContainsKey(poolName)) { m_poolDic[poolName].Remove(go); } } ///  /// 销毁所有对象池操作 ///  public void Destroy() { m_poolDic.Clear(); GameObject.Destroy(m_parentTrans); } }  
         
      
     
   

接着我们就可以使用它了,例子里我点击按钮随机生成对象

public class GameObjectPoolDemo : MonoBehaviour { [SerializeField] Button m_addSphereBtn; [SerializeField] Button m_addCubeBtn; [SerializeField] GameObject m_spherePrefab; BaseGameObjectPool m_spherePool; CubePool m_cubePool; void Start () { m_spherePool = GameObjectPoolManager.instance.CreatGameObjectPool 
   
     ("SpherePool"); m_spherePool.prefab = m_spherePrefab; m_cubePool = GameObjectPoolManager.instance.CreatGameObjectPool 
    
      ("CubePool"); m_addSphereBtn.onClick.AddListener(() => { float x = Random.Range(-15, 15); float y = Random.Range(-10, 10); m_spherePool.Get(new Vector3(x, y, 0), 1); //GameObjectPoolManager.instance.GetGameObject("SpherePool", new Vector3(x, y, 0), 1); }); m_addCubeBtn.onClick.AddListener(() => { float x = Random.Range(-15, 15); float y = Random.Range(-10, 10); GameObjectPoolManager.instance.GetGameObject("CubePool", new Vector3(x, y, 0), 1); }); } } 
     
   

效果如下,可以看见虽然屏幕中看着像一直生成新的小球方块,但是真正生成的对象其实就几个。

Unity 对象池



版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/214528.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午4:02
下一篇 2026年3月18日 下午4:03


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号