Menu
CoddyTech

ObjectPool de Unity

Parte de la sección Arquitectura de juegos en Unity del Journey de Unity de Coddy. Lección 48 de 64.

Unity incluye un pool, ObjectPool<T> en UnityEngine.Pool. Le indicas qué hacer en cada paso con cuatro lambdas, y se encarga de los objetos de reserva y los recuentos. Esta chimenea expulsa 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 y ya hay maxSize objetos de reserva en el pool, este no la conserva: actionOnDestroy la destruye. defaultCapacity solo es 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 todavía posee, CountActive las que están en el juego y CountInactive las de reserva. Get activa 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 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 genera una InvalidOperationException: Trying to release an object that has already been released to the pool. La comprobación se ejecuta solo en el Editor, porque busca en los objetos de reserva 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 que se devuelva y vuelva a devolverse a sí mismo; por eso actionOnRelease lo desactiva.

challenge icon

Desafío

Intermedio

Lanza los fuegos artificiales usando el grupo de Unity.

  1. En Awake de Launcher, crea un ObjectPool<Spark>: crea con Instantiate(sparkPrefab), actívalo cuando se obtenga, desactívalo cuando se libere, destruye el objeto cuando se suelte y establece maxSize: 5.
  2. En Update, obtén cada chispa con pool.Get() en lugar de Instantiate.
  3. En Return, llama a pool.Release(spark) en lugar de Destroy.

Pulsa «Jugar» y lanza unas cuantas tandas. Después, pulsa «Comprobar».

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

Todas las lecciones de Arquitectura de juegos en Unity

Practica por tu cuenta: Playground de Unity