Unity's new version of Object Pooling (Object Pooling) application

 reference:

Introduction to Object Pooling - Unity Learnhttps://learn.unity.com/tutorial/introduction-to-object-pooling#5ff8d015edbc2a002063971cUnity - Scripting API: ObjectPool (unity3d.com)https://docs.unity3d.com/ScriptReference/Pool.ObjectPool_1.html

This article is still exploring and trying, and does not represent the optimal solution

What is pooling

Object pooling is a programming pattern used to optimize the process of object creation and destruction to improve application performance. Object pools pre-create and store objects in memory for later reuse. In other words, if a gun in the game scene is limited to only 50 bullets fired at the same time, then the gun will reuse the 50 bullets that have been generated, and there will be no steps of generation and destruction. This reduces the number of times objects are created and destroyed at runtime, thereby reducing system overhead.

In addition, object pools are usually used in scenarios that require frequent creation and destruction of objects, such as thread pools, database connection pools, network connection pools, etc. In these scenarios, using object pools can reduce system overhead and improve application response speed and throughput.

Usually, the object pool maintains an object queue, and the creation and destruction of objects are controlled by the object pool. When an object needs to be used, the application obtains the object from the object pool, and returns it to the object pool after use, so that it can be reused next time. If no object is available in the object pool, a new object is created and added to the object pool.

Unity pooling implementation

The new version of Unity has officially encapsulated its own pooling method, which can be called and used directly!

We use bullet generation to study the optimization effect of pooling. We need two game objects, one is a firearm (used to hang on the bullet launch script BulletPooling), and the other is the bullet Prefab in Assets (used to hang on to the bullet script PoolBullet), We set the rigid body of the bullet as follows, which can not only ensure that the bullet will not penetrate the object, but also increase the burden on the computer for stress testing:

The following is the script PoolBullet of the bullet:

using System.Collections;
using UnityEngine;
namespace ObjectPoolSpace
{
    public class PoolBullet : MonoBehaviour
    {
        public int destroyTime = 3;
        public BulletPooling bulletPoolManager;// can also just use the 'bulletPool' in the BulletPooling.cs
        private void OnCollisionEnter(Collision other)
        {
            // if (other.gameObject.CompareTag("Player")) //do somthing 
            StartCoroutine(KillBullet(gameObject, new WaitForSeconds(destroyTime)));
        }
        public IEnumerator KillBullet(GameObject bullet, WaitForSeconds hide_DestroyTime)
        {
            yield return hide_DestroyTime;

            if (bulletPoolManager.isPooling)
            { //check if the bullet is still active
                if (bullet.activeSelf) bulletPoolManager.bulletPool.Release(bullet);
            }
            else Destroy(bullet);
        }
    }
}

And the script BulletPooling hanging on the gun:

using System.Collections;
using UnityEngine;
using UnityEngine.Pool;//base on Stack
/// <summary>
///https://docs.unity3d.com/ScriptReference/Pool.ObjectPool_1.html
/// </summary>
namespace ObjectPoolSpace
{
    public class BulletPooling : MonoBehaviour
    {
        public bool isPooling = true;
        [SerializeField] Transform firePoint;
        [SerializeField] int bulletSpeed = 10;
        [SerializeField] GameObject bulletPrefab;
        [Tooltip("The default number of objects to have in the pool, when the space is not enough, the pool will auto expand(stack).")]
        [SerializeField] int defaultPoolSize = 100;
        [Tooltip("The maximum number of objects to have in the pool. 0 = no maximum.")]
        [SerializeField] int maxPoolSize = 1000;
        public ObjectPool<GameObject> bulletPool;
        [Header("Below For Debug Use:")]
        [SerializeField] private int activeCount, inactiveCount, totalCount;

        // Start is called before the first frame update
        void Start()
        {
            bulletPool = new ObjectPool<GameObject>(OnCreatPoolItem, OnGetItemFromPool, OnReleaseItemFromPool, OnDestroyItemFromPool, true, defaultPoolSize, maxPoolSize);
        }
        private void Update()
        {
            activeCount = this.bulletPool.CountActive;
            inactiveCount = this.bulletPool.CountInactive;
            totalCount = this.bulletPool.CountAll;

            Shoot();
        }

        GameObject tempbullet;
        void Shoot()
        {
            //if use pooling, then use the Get() method to get a bullet from the pool, otherwise create a new one by Instantiate()
            tempbullet = isPooling ? bulletPool.Get() : //the Get() method will return an object from the pool, or create a new one if the pool is empty.
             Instantiate(bulletPrefab, firePoint.position, Quaternion.identity, this.transform);
            if (!tempbullet) return;
            tempbullet.GetComponent<Rigidbody>().velocity = firePoint.forward * bulletSpeed + new Vector3(UnityEngine.Random.Range(-1f, 1f), UnityEngine.Random.Range(-1f, 1f), UnityEngine.Random.Range(-1f, 1f));
            tempbullet.GetComponent<PoolBullet>().bulletPoolManager = this;
            StartCoroutine(KillBullet(tempbullet, new WaitForSeconds(8)));//kill the bullet whatever after 5 seconds
        }
        /// <summary>
        /// This method will be called when the bullet is hit something, or the time is up.
        /// </summary>
        public IEnumerator KillBullet(GameObject bullet, WaitForSeconds hide_DestroyTime)
        {
            yield return hide_DestroyTime;

            if (isPooling)
            { //check if the bullet is still active
                if (bullet && bullet.activeSelf) bulletPool.Release(bullet);
            }
            else Destroy(bullet);
        }

        /***Below are the callback methods for the ObjectPool***/

        /// <summary> 
        /// 在对象池中创建对象时调用; This method will be called when the the Get() method is first time called and there still have space in the pool.
        /// </summary>
        private GameObject OnCreatPoolItem()
        {
            var bullet = Instantiate(bulletPrefab, firePoint.position, Quaternion.identity, this.transform);
            bullet.SetActive(true);
            //Debug.Log("OnCreatPoolItem");
            return bullet;
        }
        /// <summary>
        /// 当对象池超过容量时,或者对象被销毁时调用,一般不会发生,除非调用了Clean 或者 Dispose 方法
        ///This method will be called when the pool is full, ot Clean() or Dispose() method is called.
        /// </summary>
        private void OnDestroyItemFromPool(GameObject obj)
        {
            Destroy(obj);
            //Debug.Log("OnDestroyItemFromPool");
        }
        /// <summary>
        /// 从对象池中获取对象时调用; This method will be called when the the Get() method is called.
        /// </summary>
        private void OnGetItemFromPool(GameObject bullet)
        {
            if (bullet) bullet.SetActive(true);
            //Debug.Log("OnGetItemFromPool");
        }
        /// <summary>
        /// 当对象放回对象池时调用; This method will be called when the the Release() method is called.
        /// </summary>
        private void OnReleaseItemFromPool(GameObject bullet)
        {
            bullet.SetActive(false);
            //Reset the bullet's position
            bullet.transform.position = firePoint.position;
            bullet.transform.rotation = Quaternion.identity;
            // bullet.GetComponent<Rigidbody>().velocity = Vector3.zero;
            //Debug.Log("OnReleaseItemFromPool");
        }
    }
}

The core of the whole program is:

 bulletPool = new ObjectPool<GameObject>(OnCreatPoolItem,
 OnGetItemFromPool,
 OnReleaseItemFromPool,
 OnDestroyItemFromPool,
 true,
 defaultPoolSize,
 maxPoolSize);

And correctly set when to recycle the bullet (using the Release() method), the above script will be able to launch the bullet, the bullet will be automatically destroyed/recycled after detecting the collision for a certain period of time, and the bullet will be automatically destroyed/recycled after the bullet has not collided for a long time. In the experimental scene, 8 turrets will violently output shells. After the number of bullets stabilizes, each turret will have about 300 shells flying and colliding at the same time. A total of more than 2,400 objects are rendered and collided in motion.


performance analysis

Unity's Destroy() and Instantiate() consume a lot of resources and computing power, so when many objects are continuously generated and deleted, it will greatly increase the computer burden and reduce the number of game frames.

Finally, we can see through comparison that pooling can really significantly improve performance. Although I am still not satisfied (there may be some problems with the code), it is indeed optimized a lot!

Pooled bullets alternate between hiding and activating, not involving adding and destroying

As can be seen from the following performance test, the left picture does not use pooling , but uses traditional instantiation-and then deletes about 45 frames. The picture on the right uses pooling- resource reuse frame rate to more than 65 frames, and after more tests, pooling always has an advantage of 20 or 30 frames! !

no pooling
use pooling

Guess you like

Origin blog.csdn.net/weixin_46146935/article/details/130512691