Menu
CoddyTech

ObjectPool de Unity

Parte de la sección Arquitectura de juegos en Unity del Journey de Unity de Coddy. Lección 39 de 50.

Unity incluye un pool, ObjectPool<T> en UnityEngine.Pool. Le indicas qué hacer en cada paso con cuatro lambdas, y se encarga de mantener los objetos sobrantes y las cantidades. Esta chimenea echa humo cada 0.2 segundos, y cada bocanada vuelve al pool después de un 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 se ejecuta cuando Get encuentra el pool vacío; actionOnGet se ejecuta en cada Get y actionOnRelease en cada Release. Cuando se devuelve una bocanada mientras ya hay objetos sobrantes en espera hasta maxSize, el pool no la conserva: actionOnDestroy la destruye. defaultCapacity es solo el tamaño inicial de la lista del pool, así que no se crea nada de antemano. CountAll cuenta las bocanadas que el pool ha creado y aún posee, CountActive las que están en el juego y CountInactive las sobrantes. Get ha activado la bocanada antes de que la chimenea la coloque, lo cual está bien en este caso porque Puff no tiene un OnEnable que lea su posición. La bocanada conserva el pool como un IObjectPool<Puff>, la interfaz que implementan todos los pools de Unity, y Launch reinicia su edad en cada uso.

Con collectionCheck activado, devolver una bocanada que ya está en el pool produce una InvalidOperationException: Trying to release an object that has already been released to the pool. La comprobación solo se ejecuta en el Editor, porque busca entre los objetos sobrantes en cada Release; en una compilación, la bocanada quedaría dos veces en el pool y dos llamadas a Get entregarían el mismo objeto. Una causa común es que un objeto siga ejecutándose después de ser devuelto y vuelva a devolverse, por eso actionOnRelease lo desactiva.

challenge icon

Desafío

Intermedio

Fuegos artificiales. Cada vez que pulsas Espacio, salen 8 chispas del lanzador, y cada Spark llama a Return(this) del lanzador cuando termina su 1 segundo de vida (Spark está listo). Hoy, el lanzador crea y destruye cada chispa. Usa el pool de Unity:

  • En Launcher.cs, crea un ObjectPool<Spark> en Awake. Este crea una chispa al instanciar sparkPrefab, la activa cuando se obtiene y la desactiva cuando vuelve, y destruye el objeto de la chispa cuando la libera.
  • El pool conserva como máximo 5 chispas de reserva, así que después de una ráfaga de 8, 5 esperan y 3 se destruyen.
  • Al pulsar Espacio se obtienen las 8 chispas del pool en vez de instanciarlas, y Return devuelve la chispa al pool en vez de destruirla.

Pulsa Play, dispara unas cuantas ráfagas y luego pulsa Check.

Pruébalo tú mismo

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

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

    void Awake()
    {
        // Crea el pool: crea, obtiene, libera y destruye, con un máximo de 5 chispas 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);
            }
        }
    }

    // Una chispa cuya vida ha terminado
    public void Return(Spark spark)
    {
        Destroy(spark.gameObject);
    }

    // Hacia fuera y hacia arriba, distribuidas uniformemente alrededor del lanzador
    Vector3 Direction(int i)
    {
        return Quaternion.Euler(0f, i * 360f / sparks, 0f) * new Vector3(0f, 1f, 3f);
    }
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Arquitectura de juegos en Unity