[Unity Study Notes] Object Pool


The object pool is a cliché. The advantage of using it is: when we need to repeatedly create or destroy some objects, for example, limiting the number of bullets to 10 rounds, when the 11th round is fired, the 10th round needs to disappear and the 11th one appears. Although it is possible to destroy bullet No. 10 and create bullet No. 11, it should not be implemented this way because creation and destruction consume performance. If you use object pool technology, you can avoid creation and destruction and hide and display instead. That is to say, display No. 11 and hide No. 10 bullets to achieve the same function. Through the object pool, if we want to fire 1,000 bullets, we don't have to implement 1,000 creation and destruction, only 11 creations and destructions (11 bullets are generated and displayed in sequence).

Design ideas

When designing the object pool, we will consider the use of design patterns. Just like in a chicken game, there are bullets of different calibers, so different guns also have different bullet capacities. Different firing rates will also cause different numbers of bullets that can exist in the object pool of different guns at the same time, which means different Guns have different object pools, and different object pools use different bullets and have different maximum bullet capacities. In the previous notes, I learned about the life cycle of Monobehavior. Each life cycle consumes performance in every frame. Therefore, we need to carefully consider the design pattern of the entire object pool:

How should object pooling be implemented? First of all, from the design requirements, N bullets are stored in an object pool (N should be variable), and the bullets in the object pool cannot be fixed. Secondly, if you switch guns in the game, since different guns each have their own object pool, the object pool used should also change. Finally, the use of guns in shooting games is generally throughout the entire process, so no matter the scene changes, the object pool should always be DDOL.


overall design

Based on the above ideas, we can determine a design pattern: first use an object pool manager. This object pool manager is DDOL, and it can manage the switching of object pools, which is equivalent to switching the corresponding object pool when cutting the gun. . For this function, we can use a dictionary to implement it, using key-value pairs to correspond to different object pools with different key values. The object pool manager can conveniently manage object pools (guns). For example, some guns will be discarded when they run out of bullets. We will unload the corresponding object pool and load the corresponding object pool when we pick up a new gun. Since it is DDOL, we can keep the gun from the previous scene to the next scene instead of destroying it and reloading it. Based on this property, the object pool should be globally unique in the game, which requires us to inherit a singleton mode for it.

Secondly, the design of the object pool. Generally speaking, we should generate the maximum number N of bullets for the object pool when Awake, rather than when needed, because the lag in generating bullets may destroy the player's gaming experience. We would rather put the 1 second generation time on the loading interface than allow players to have 0.1 seconds of lag while playing. Regarding the access of bullets in the object pool, the one fired first will definitely disappear first, so the queue is the most suitable data structure. We will dequeue it and then add it to the queue when the range is over. This ensures that bullets can be continuously generated when shooting continuously.

Finally, there are bullets. Since the same object pool class should be able to fire different bullets, we can save the bullets as prefabs. Some properties can be mounted on the bullet object itself. For example, when designing a game, gun attributes and bullet attributes are calculated separately. Guns can design base classes, then we can also design a bullet base class for other bullets to inherit.


Consider life cycle

After deciding on the design patterns of these systems, you should consider which scripts need to inherit the Monobehavior life cycle. First of all, bullets must not be inherited. If the object pool has a maximum of 100 bullets, and it has 10 object pools at the same time. If the bullet has a life cycle, it will have 1000 life cycles, otherwise it will only have 10 life cycles. Therefore, bullets cannot have a life cycle, so for the movement of the bullet, we should coordinate the functional design of the bullet into its object pool, and use Update or coroutine in the object pool to perform some processing that needs to be updated for this GameObject.

The second is the object pool. This object should inherit Mono behavior, which allows different object pools to handle their own different situations independently. A better design is to write a parent class of the object pool and let the subclass inherit and rewrite it. Some customizable methods.

Finally, there is the object pool manager. If we want it to DDOL, we need to inherit Mono; if it does not need to interact with the life cycle and just simply manages the object pool, it is not needed, and it can be inherited or not. Usually we design whether the singleton inherits Mono when designing the singleton pattern.


some code

Singleton pattern

public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    
    
    private static T _instance;
    public static T Instance
    {
    
    
        get
        {
    
    
            if (_instance == null)
            {
    
    
                _instance = FindObjectOfType<T>();
            }
            if (_instance == null)
            {
    
    
                GameObject obj = new GameObject();
                obj.name = typeof(T).Name;
                obj.AddComponent<T>();
            }
            return _instance;
        }
    }
}

Object pool manager that inherits from singleton

public class ObjPoolManager : Singleton<ObjPoolManager>
{
    
    
    [SerializeField]
    public Dictionary<string, ObjPool> m_poolDic;
    /// <summary>
    /// 读取挂载的子物体中的对象池并加入字典
    /// </summary>
    /// <param name="obj">用于统一管理对象池的父物体</param>
    public void initPool(GameObject obj)
    {
    
    
        m_poolDic = new Dictionary<string, ObjPool>();
        for (int i = 0; i < obj.transform.childCount; i++)
        {
    
    
            var thisChild = obj.transform.GetChild(i);
            m_poolDic.Add(thisChild.name, thisChild.GetComponent<ObjPool>());
        }
    }
    ///其他管理对象池的代码
}

Object pool code

public class ObjPool : MonoBehaviour
{
    
    
    [SerializeField]
    GameObject obj;
    [SerializeField]
    GameObject QiangKou;
    [SerializeField]
    int Maxnum = 0;


    void Start()
    {
    
    
        Initiate();
    }
    protected Queue m_queue;
    void Initiate()
    {
    
    
        m_queue = new Queue(Maxnum);
        for (int i = 0; i < Maxnum; i++)
        {
    
    
            var newobj = Instantiate(obj);
            newobj.transform.position = Vector3.zero;
            newobj.name = "bullet" + i.ToString();
            newobj.transform.SetParent(this.transform);
            m_queue.Enqueue(newobj);
            newobj.SetActive(false);
        }
    }

    void Shot(GameObject bullet)
    {
    
    
        Debug.Log("发射子弹:" + bullet.name);
        StartCoroutine(Fire(bullet));
    }

    IEnumerator Fire(GameObject bullet)
    {
    
    
        Debug.Log("发射:" + bullet.name);
        while (bullet.transform.position.x < 1500)
        {
    
    
            bullet.transform.position = new Vector3(bullet.transform.position.x + 10f, bullet.transform.position.y, bullet.transform.position.z);
            yield return new WaitForSeconds(0.02f);
        }
        InPool(bullet);
    }
    void OutPool()
    {
    
    
        GameObject bullet = (GameObject)m_queue.Dequeue();
        bullet.transform.position = QiangKou.transform.position;
        bullet.SetActive(true);
        Shot(bullet);
    }
    void InPool(GameObject bullet)
    {
    
    
        bullet.SetActive(false);
        m_queue.Enqueue(bullet);
    }
    public void OnShotClick()
    {
    
    
        Debug.Log("准备发射");
        OutPool();
    }
}

Other classes that call the object pool manager singleton

public class GunsManager : MonoBehaviour
{
    
    
    void Start()
    {
    
    
        ObjPoolManager.Instance.initPool(this.gameObject);
    }
}

Insert image description here
Single object pool effect display, in which the white block represents the muzzle and the black block represents the bullet.
Insert image description here
Multi-object pool effect display, the three-color buttons represent three different guns. Click the button to fire the bullet.

If you want to improve the system, you need to extend the object pool manager class, and then call the object pool manager class through other classes.

In the above example, I just attached the object pool class to each button, and then fired the bullet based on the button triggering event. Then the same emission code needs to be repeated three times. A better way would be to uniformly use a button to execute the emitted object pool event, which can be directly defined on the object pool manager, and then select the object pool for the operation based on the index value.

For example, the gun cutting operation can also be done by modifying the dictionary of the object pool manager. The object pool can be managed more flexibly through the object pool manager.

Guess you like

Origin blog.csdn.net/milu_ELK/article/details/132042290