Menu
CoddyTech

O ObjectPool da Unity

Parte da seção Arquitetura de jogos no Unity do Journey de Unity da Coddy. Lição 39 de 50.

O Unity já inclui um pool, ObjectPool<T> em UnityEngine.Pool. Você diz a ele o que fazer em cada etapa com quatro lambdas, e ele mantém os objetos extras e as contagens. Esta chaminé solta fumaça a cada 0.2 segundos, e cada baforada volta após um segundo:

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 é executada quando Get encontra o pool vazio; actionOnGet é executada a cada Get e actionOnRelease a cada Release. Quando uma baforada é liberada enquanto já há objetos extras aguardando em maxSize, o pool não a mantém: actionOnDestroy a destrói. defaultCapacity é apenas o tamanho inicial da lista do pool, então nada é criado antecipadamente. CountAll conta as baforadas que o pool criou e ainda possui, CountActive aquelas que estão no jogo e CountInactive os objetos extras. Get ativou a baforada antes de a chaminé posicioná-la, o que funciona aqui porque Puff não tem um OnEnable que leia sua posição. A baforada mantém o pool como um IObjectPool<Puff>, a interface implementada por todos os pools do Unity, e Launch redefine sua idade a cada uso.

Com collectionCheck ativado, liberar uma baforada que já está no pool lança uma InvalidOperationException: Trying to release an object that has already been released to the pool. A verificação é executada apenas no Editor, porque pesquisa os objetos extras a cada Release; em uma build, a baforada ficaria duas vezes no pool e dois Gets entregariam o mesmo objeto. Uma causa comum é um objeto que continua sendo executado depois de ser liberado e se libera novamente, por isso actionOnRelease o desativa.

challenge icon

Desafio

Médio

Fogos de artifício. Cada pressionamento da barra de espaço lança 8 faíscas para fora do lançador, e cada Spark chama Return(this) do lançador quando seu 1 segundo de vida termina (Spark está pronto). Hoje, o lançador instancia e destrói cada faísca. Use o pool do Unity:

  • Em Launcher.cs, crie um ObjectPool<Spark> em Awake. Ele cria uma faísca instanciando sparkPrefab, ativa a faísca quando ela é retirada e a desativa quando volta, e destrói o objeto da faísca quando a libera.
  • O pool mantém no máximo 5 faíscas sobressalentes, então, após um disparo de 8, 5 ficam aguardando e 3 são destruídas.
  • A barra de espaço retira suas 8 faíscas do pool em vez de instanciá-las, e Return libera a faísca para o pool em vez de destruí-la.

Pressione Play, dispare alguns conjuntos e depois Check.

Experimente você mesmo

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

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

    void Awake()
    {
        // Crie o pool: crie, obtenha, libere e destrua, com no máximo 5 faíscas de reserva
    }

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

    // Uma faísca cuja vida chegou ao fim
    public void Return(Spark spark)
    {
        Destroy(spark.gameObject);
    }

    // Para fora e para cima, distribuídas uniformemente ao redor do lançador
    Vector3 Direction(int i)
    {
        return Quaternion.Euler(0f, i * 360f / sparks, 0f) * new Vector3(0f, 1f, 3f);
    }
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Arquitetura de jogos no Unity