Unity 使用內建的物件池


建立時間: 2023年7月13日 16:38
更新時間: 2023年9月14日 08:29

說明

原本我有寫一篇 Unity 遊戲物件池 極簡版 的文章,後來發現原來 Unity 內建有物件池的功能,我研究了很久,終於將舊版的物件池移植成 Unity 內建的物件池,本篇將分享 Unity 內建的物件池範例。

關於物件池

關於物件池介紹的部分,請參考 Unity 遊戲物件池 極簡版

球池

一樣,我需要先建立一個球池。

BallPool.cs

using UnityEngine;
using UnityEngine.Pool;

/// <summary>
/// 球池
/// </summary>
public class BallPool : MonoBehaviour
{
    /// <summary>
    /// 球預製
    /// </summary>
    [SerializeField]
    private GameObject ballPrefab;

    /// <summary>
    /// 球池
    /// </summary>
    private ObjectPool<Ball> pool;

    /// <summary>
    /// 預設容量
    /// </summary>
    [SerializeField]
    private int defaultCapacity = 10;

    /// <summary>
    /// 球池
    /// </summary>
    public ObjectPool<Ball> Pool => pool;

    private void Awake()
    {
        pool = new ObjectPool<Ball>(
            CreatePooledItem,
            OnTakeFromPool,
            OnReturnedToPool,
            OnDestroyPoolObject,
            true,
            defaultCapacity
        );
    }

    /// <summary>
    /// 實體化球體
    /// </summary>
    /// <returns></returns>
    private Ball CreatePooledItem()
    {
        GameObject ballGameObject = Instantiate(ballPrefab, transform);
        Ball ball = ballGameObject.GetComponent<Ball>();

        return ball;
    }

    /// <summary>
    /// If the pool capacity is reached then any items returned will be destroyed.
    /// We can control what the destroy behavior does, here we destroy the GameObject.
    /// </summary>
    /// <param name="ball"></param>
    private void OnDestroyPoolObject(Ball ball)
    {
        Destroy(ball.gameObject);
    }

    /// <summary>
    /// Called when an item is returned to the pool using Release
    /// </summary>
    /// <param name="ball"></param>
    private void OnReturnedToPool(Ball ball)
    {
        ball.gameObject.SetActive(false);
    }

    /// <summary>
    /// Called when an item is taken from the pool using Get
    /// </summary>
    /// <param name="ball"></param>
    private void OnTakeFromPool(Ball ball)
    {
        ball.gameObject.SetActive(true);
    }

    /// <summary>
    /// 取得球,與 Pool.Get() 不同的是,可以設定參數
    /// </summary>
    /// <param name="position">位置</param>
    /// <returns></returns>
    public Ball GetBall(Vector3 position)
    {
        Ball ball = pool.Get();
        ball.transform.position = position;

        return ball;
    }
}

我宣告一個 private ObjectPool<Ball> pool 物件池,內涵 Ball 物件,接著我在 Awake() 實體化 pool 成員 pool = new ObjectPool<Ball>()

關於 new ObjectPool<Ball>() 裡面的的參數,請參考 ObjectPool<T0>

大致參數說明如下:

  1. 第1個參數是建立物件的方法
  2. 第2個參數是取得物件的方法
  3. 第3個參數是回收物件的方法
  4. 第4個參數是當池滿要摧毀物件的方法
  5. 第5個參數是如果我們嘗試釋放池中已有的項目,集合檢查將引發錯誤。
  6. 第6個參數是預設容量
  7. 第7個參數是容量上限

我還自己寫了一個 GetBall(Vector3 position) 方法,你可以看到裡面有寫 pool.Get() 這就是取得池中物件的方法,但我另外寫的 GetBall() 方法可以額外設定位置。

建立球池遊戲物件

同之前的一樣,建立一個空遊戲物件,新增 BallPool 組件,並設定好屬性。

註:遊戲物件名稱 NormalBallPool 與底下 Ball 腳本 GameObject.Find("BallPool") 不一致。
Ball Pool Inspector

球物件參考球池

同之前一樣,球腳本中,有一個靜態的球池,它參考的就是遊戲物件的球池,意思是球池是共享的。

Ball.cs

using UnityEngine;

/// <summary>
/// 球
/// </summary>
public class Ball : MonoBehaviour
{
    /// <summary>
    /// 球池
    /// </summary>
    private static BallPool ballPool;

    /// <summary>
    /// 球池
    /// </summary>
    public static BallPool BallPool
    {
        get
        {
            if (ballPool == null)
            {
                ballPool = GameObject.Find("BallPool").GetComponent<BallPool>();
            }

            return ballPool;
        }
    }

    /// <summary>
    /// 釋放一顆球
    /// </summary>
    public void Release()
    {
        ballPool.Pool.Release(this);
    }
}

我另位寫了一個 Release() 方法,方便釋放球用。

取得一顆球

要取得一顆球可以這樣呼叫:

Ball ball = Ball.BallPool.Pool.Get();
Ball ball = Ball.BallPool.GetBall(transform.position);

就看是否需要預設位置,當然取得 ball 之後,也是可以在設定位置。

回收一顆球

回收使用的是球池釋放的方法,你可以這樣回收

Ball ball = Ball.BallPool.Pool.Get();

Ball.BallPool.Pool.Release(ball);
ball.Release();

回收具體做些什麼,可以參考 BallPool 類別的 OnReturnedToPool()

注意!你不能這樣回收。

Ball ball = Ball.BallPool.Pool.Get();
ball.gameObject.SetActive(false);

因為 Ball.BallPool.Pool.Release(ball); 實際上可能還有執行其他程式,並非只有調用 OnReturnedToPool(),我會這麼猜測是因為,我曾發生過將所有的球都調用 ball.gameObject.SetActive(false);Ball.BallPool.Pool.CountActive > 0 卻是 true,我改用 Release() 就恢復正常了,所以保險起見請使用 Release()

參考

觀看次數: 1043
objectpoolunity物件物件池
按讚追蹤 Enjoy 軟體 Facebook 粉絲專頁
每週分享資訊技術

一杯咖啡的力量,勝過千言萬語的感謝。

支持我一杯咖啡,讓我繼續創作優質內容,與您分享更多知識與樂趣!