Menu
CoddyTech

ObjectPool в Unity

Часть раздела Архитектура игр в Unity путешествия по Unity на Coddy. Урок 48 из 64.

В Unity есть пул — ObjectPool<T> из UnityEngine.Pool. С помощью четырёх лямбда-выражений задайте, что пул должен делать на каждом этапе, а он будет хранить запасные объекты и вести подсчёт. Эта дымовая труба выпускает клубы дыма каждые 0.2 секунды, и каждый клуб возвращается через секунду:

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 вызывается, когда Get обнаруживает, что пул пуст; actionOnGet вызывается при каждом Get, а actionOnRelease — при каждом Release. Если клуб дыма возвращается, когда в пуле уже хранится maxSize запасных объектов, пул не сохраняет его: actionOnDestroy уничтожает его. defaultCapacity — это только начальный размер списка пула, поэтому заранее ничего не создаётся. CountAll считает клубы дыма, созданные пулом и всё ещё находящиеся в его распоряжении, CountActive — те, что находятся в игре, а CountInactive — запасные. Get активирует клуб дыма до того, как дымовая труба задаёт его положение, и здесь это не проблема, поскольку у Puff нет OnEnable, который считывает его положение. Клуб дыма хранит пул как IObjectPool<Puff> — интерфейс, который реализуют все пулы Unity, — а Launch сбрасывает его возраст при каждом использовании.

Если collectionCheck включён, повторный возврат клуба дыма, который уже находится в пуле, вызывает исключение InvalidOperationException: Trying to release an object that has already been released to the pool. Проверка выполняется только в редакторе, поскольку при каждом Release она ищет объект среди запасных; в сборке клуб дыма окажется в пуле дважды, и два вызова Get вернут один и тот же объект. Частая причина — объект продолжает работать после возврата в пул и возвращает себя туда снова; поэтому actionOnRelease отключает его.

challenge icon

Задание

Средне

Запускай фейерверки из пула Unity.

  1. В Awake класса Launcher создай ObjectPool<Spark>: создавай объекты с помощью Instantiate(sparkPrefab), включай их при получении, выключай при возвращении, уничтожай объект при отпускании, укажи maxSize: 5.
  2. В Update получай каждую искру с помощью pool.Get() вместо Instantiate.
  3. В Return вызывай pool.Release(spark) вместо Destroy.

Нажми «Играть» и запусти несколько залпов. Затем нажми «Проверить».

Попробуйте сами

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

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

    void Awake()
    {
        // Создать пул: создавать, получать, возвращать и уничтожать искры, оставляя не более 5 запасных искр
    }

    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);
            }
        }
    }

    // Искра, срок жизни которой истёк
    public void Return(Spark spark)
    {
        Destroy(spark.gameObject);
    }

    // Наружу и вверх, равномерно распределяя вокруг пусковой установки
    Vector3 Direction(int i)
    {
        return Quaternion.Euler(0f, i * 360f / sparks, 0f) * new Vector3(0f, 1f, 3f);
    }
}

Все уроки раздела Архитектура игр в Unity

Потренируйтесь самостоятельно: Песочница Unity