Unity 对象池(Object Pooling)理解与简单应用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/l773575310/article/details/71601460

Unity 对象池理解与简单应用

参考Unity官方教程 : Object Pooling
Unity官方教程: Space Shooter
Github项目(使用对象池):Space Shooter
Github项目(封装对象池):GentleTank

  对象池用于减少内存开销,其原理就是把可能用到到的对象,先存在一个地方(池),要用的时候就调出来,不用就放回去。而不是要用的时候创建,不用的时候销毁。
  举个例子:
  我有个飞机,射击子弹,按传统的方法就是,创建子弹,子弹击中目标或出界等销毁子弹。就是不断的创建与销毁,要知道创建和销毁是要消耗许多内存以及时间的。如果把子弹存在一个地方(池),需要子弹时,就从里面拿出来,不需要的时候放回去。
对象池

  如果还有多个飞机,可以公用一个子弹库。
  这个方法虽然一直保持着存储子弹空间的一个最大值,但相比不断创建与销毁的代价,是非常值得考虑的。
  而实际开发中,可以按照需要适当放大或缩小池大小。
  
   关于线程池还有连接池其原理类似对象池,不做介绍。


对象池简单应用

  拿上面说的飞机射子弹的例子,上代码。

子弹对象池类

这里写图片描述

using System.Collections.Generic;
using UnityEngine;

public class BulletsPool : MonoBehaviour
{
    public static BulletsPool bulletsPoolInstance;      //子弹池单例
    public GameObject bulletObj;                        //子弹perfabs
    public int pooledAmount = 5;                        //子弹池初始大小
    public bool lockPoolSize = false;                   //是否锁定子弹池大小

    private List<GameObject> pooledObjects;             //子弹池链表

    private int currentIndex = 0;                       //当前指向链表位置索引

    void Awake()
    {
        bulletsPoolInstance = this;                     //把本对象作为实例。
    }

    void Start()
    {
        pooledObjects = new List<GameObject>();         //初始化链表
        for (int i = 0; i < pooledAmount; ++i)
        {
            GameObject obj = Instantiate(bulletObj);    //创建子弹对象
            obj.SetActive(false);                       //设置子弹无效
            pooledObjects.Add(obj);                     //把子弹添加到链表(对象池)中
        }
    }

    public GameObject GetPooledObject()                 //获取对象池中可以使用的子弹。
    {
        for (int i = 0; i < pooledObjects.Count; ++i)   //把对象池遍历一遍
        {
            //这里简单优化了一下,每一次遍历都是从上一次被使用的子弹的下一个,而不是每次遍历从0开始。
            //例如上一次获取了第4个子弹,currentIndex就为5,这里从索引5开始遍历,这是一种贪心算法。
            int temI = (currentIndex + i) % pooledObjects.Count;
            if (!pooledObjects[temI].activeInHierarchy) //判断该子弹是否在场景中激活。
            {
                currentIndex = (temI + 1) % pooledObjects.Count;
                return pooledObjects[temI];             //找到没有被激活的子弹并返回
            }
        }


        //如果遍历完一遍子弹库发现没有可以用的,执行下面
        if(!lockPoolSize)                               //如果没有锁定对象池大小,创建子弹并添加到对象池中。
        {
            GameObject obj = Instantiate(bulletObj);
            pooledObjects.Add(obj);
            return obj;
        }

        //如果遍历完没有而且锁定了对象池大小,返回空。
        return null;
    }

}

自动发射子弹类

using UnityEngine;

public class AutoFire : MonoBehaviour
{
    //传统创建子弹方法需要的子弹perfabs
    //public GameObject shotObj;

    public GameObject shotSpawn;                //子弹发射的初始化位置

    public float fireRate = 0.2f;               //每次发射子弹事件间隔

    private float nextFire;                     //下一次发射子弹的时间

    void Update()
    {
        if (Time.time > nextFire)               //可以发射子弹时间
        {
            nextFire = Time.time + fireRate;

            //传统创建子弹方法
            //Instantiate(shotObj, shotSpawn.transform.position, shotSpawn.transform.rotation);

            //获取对象池中的子弹
            GameObject bullet = BulletsPool.bulletsPoolInstance.GetPooledObject();
            if(bullet != null)                  //不为空时执行
            {
                bullet.SetActive(true);         //激活子弹并初始化子弹的位置
                bullet.transform.position = shotSpawn.transform.position;
            }
        }
    }
}

子弹失效,回收子弹类

  
  判断是否出界,这个类是放在场景的一个长方体里,飞机子弹都在这长方体内,所以时刻都是与这个长方体碰撞中的,当子弹出界,及子弹碰撞结束。这是Unity官方教程(Space shooter)里有的。

using UnityEngine;

public class DestroyByBoundary : MonoBehaviour
{
    void OnTriggerExit(Collider other)
    {
        //Destroy(other.gameObject);                //传统方法,直接删除子弹

        other.gameObject.SetActive(false);          //对象池方法,把子弹失效就好了
    }
}

刚开始,如图已经发了三发Bolt1,对象池中还有两发active是false的:
对象池1

当稳定后,子弹池稳定在7发。
对象池2


进阶

见 Github:GentleTank/Assets/GameControl/Object%20Pool/Scripts/ObjectPool.cs


将对象池封装成ScriptObject。

  将对象池重新封装,做成一个ScriptObject,就可把对象池抽象出来,就可以应用于任何物体。

这里写图片描述

  在上图右下角Project面板上创建一个对象池文件。

这里写图片描述

  其面板如下。

这里写图片描述

注意:使用前需要调用CreateObjectPool(),来初始化创建对象池。

阅读更多

没有更多推荐了,返回首页