Menu
CoddyTech

ObjectPool của Unity

Thuộc phần Kiến trúc trò chơi trong Unity trong hành trình Unity của Coddy. Bài học 48 trên 64.

Unity tích hợp sẵn một pool, ObjectPool<T> trong UnityEngine.Pool. Bạn cho pool biết cần làm gì ở mỗi bước bằng bốn hàm lambda, còn pool sẽ giữ các đối tượng dự phòng và bộ đếm. Ống khói này phun khói mỗi 0.2 giây, và mỗi làn khói sẽ quay lại sau một giây:

using UnityEngine;
using UnityEngine.Pool;

public class Chimney : MonoBehaviour
{
    public Puff puffPrefab;
    private ObjectPool<Puff> pool;
    private float timer;

    void Awake()
    {
        pool = new ObjectPool<Puff>(
            createFunc: () => Instantiate(puffPrefab),
            actionOnGet: puff => puff.gameObject.SetActive(true),
            actionOnRelease: puff => puff.gameObject.SetActive(false),
            actionOnDestroy: puff => Destroy(puff.gameObject),
            collectionCheck: true,
            defaultCapacity: 10,
            maxSize: 20);
    }

    void Update()
    {
        timer += Time.deltaTime;
        if (timer >= 0.2f)
        {
            timer = 0f;
            Puff puff = pool.Get();
            puff.transform.position = transform.position + Vector3.up;
            puff.Launch(pool);
        }
    }
}
// Puff.cs
private IObjectPool<Puff> pool;
private float age;

public void Launch(IObjectPool<Puff> owner)
{
    pool = owner;
    age = 0f;
}

void Update()
{
    transform.Translate(0f, Time.deltaTime, 0f);
    age += Time.deltaTime;
    if (age >= 1f) pool.Release(this);
}

createFunc chạy khi Get thấy pool trống; actionOnGet chạy mỗi lần gọi Get và actionOnRelease chạy mỗi lần gọi Release. Khi trả lại một làn khói trong lúc pool đã có maxSize đối tượng dự phòng, pool sẽ không giữ nó: actionOnDestroy sẽ hủy đối tượng đó. defaultCapacity chỉ là kích thước ban đầu của danh sách trong pool, nên pool không tạo sẵn đối tượng nào. CountAll đếm số làn khói mà pool đã tạo và vẫn sở hữu, CountActive đếm những làn khói đang ở trong trò chơi, còn CountInactive đếm các đối tượng dự phòng. Get bật làn khói trước khi ống khói đặt vị trí cho nó. Điều này không gây vấn đề ở đây vì Puff không có OnEnable đọc vị trí của nó. Làn khói lưu pool dưới dạng IObjectPool<Puff>, giao diện mà mọi pool của Unity đều triển khai, còn Launch đặt lại tuổi của nó mỗi lần sử dụng.

Khi bật collectionCheck, việc trả lại một làn khói đã nằm trong pool sẽ gây ra InvalidOperationException: Trying to release an object that has already been released to the pool. Việc kiểm tra chỉ chạy trong Editor vì nó tìm trong các đối tượng dự phòng mỗi lần gọi Release; trong bản build, làn khói sẽ nằm trong pool hai lần và hai lần gọi Get sẽ trả về cùng một đối tượng. Một nguyên nhân thường gặp là đối tượng vẫn tiếp tục chạy sau khi được trả lại rồi lại tự trả về pool lần nữa. Vì vậy, actionOnRelease sẽ tắt đối tượng.

challenge icon

Thử thách

Trung bình

Phóng pháo hoa từ pool của Unity.

  1. Trong Awake của Launcher, tạo một ObjectPool<Spark>: tạo bằng Instantiate(sparkPrefab), bật khi lấy ra, tắt khi trả lại, hủy đối tượng khi loại bỏ, maxSize: 5.
  2. Trong Update, lấy từng tia lửa bằng pool.Get() thay vì Instantiate.
  3. Trong Return, gọi pool.Release(spark) thay vì Destroy.

Nhấn Chơi và bắn vài loạt pháo. Sau đó nhấn Kiểm tra.

Tự làm thử

using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Pool;

public class Launcher : MonoBehaviour
{
    public Spark sparkPrefab;
    public int sparks = 8;

    void Awake()
    {
        // Tạo pool: tạo, lấy, trả về và hủy, với tối đa 5 tia lửa dự phòng
    }

    void Update()
    {
        if (Keyboard.current.spaceKey.wasPressedThisFrame)
        {
            for (int i = 0; i < sparks; i++)
            {
                Spark spark = Instantiate(sparkPrefab);
                spark.transform.position = transform.position + Vector3.up;
                spark.Launch(Direction(i), this);
            }
        }
    }

    // Một tia lửa đã hết thời gian tồn tại
    public void Return(Spark spark)
    {
        Destroy(spark.gameObject);
    }

    // Bay ra ngoài và lên trên, tỏa đều quanh bộ phóng
    Vector3 Direction(int i)
    {
        return Quaternion.Euler(0f, i * 360f / sparks, 0f) * new Vector3(0f, 1f, 3f);
    }
}

Tất cả bài học trong Kiến trúc trò chơi trong Unity

Tự luyện tập: Playground Unity