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.
Desafío
IntermedioFuegos 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 unObjectPool<Spark>en Awake. Este crea una chispa al instanciarsparkPrefab, 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
Returndevuelve 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);
}
}
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
1Componentes, no jerarquías
Componentes pequeñosUn script base para enemigosComponentes abstractosComponentes que trabajan juntosRepaso: torreta modular4Datos serializados
Clases serializablesListas en el InspectorEnumeraciones en el InspectorRepaso: tabla de botín7Máquinas de estados
Estados como clasesUna máquina de estadosTransicionesCerebros de enemigosRepaso: estados de puertas10Proyecto: Superviviente de la arena
El héroeTipos de enemigos2Interfaces en Unity
IDamageableInterfaces y TryGetComponentGetComponentsRepaso: tecla de interacción5ScriptableObjects
Recursos de datosEstadísticas de armasConfiguración compartidaOleadas como datosRepaso: base de datos de objetos3Eventos
Eventos de C# en UnitySuscribirse en OnEnableCampos de UnityEventLos eventos controlan la interfazRepaso: ventana emergente de logro6Guardar con JSON
JsonUtilityGuardar la partidaCargar y valores predeterminadosRepaso: ranuras de guardado9Pool de objetos
Por qué usar un pool de objetosUn pool con una colaObjectPool de UnityResumen: pool de balas