Object pooling en Unity: cómo reciclar bloques sin saturar memoria

Clase 28 de 53Curso de C# para Videojuegos

Resumen

Optimiza la generación procedural en Unity con un enfoque robusto: usa object pooling y un level manager para añadir bloques sin saturar la RAM. Aquí verás cómo controlar la aleatoriedad con Random.Range, alinear puntos de entrada y salida, y mantener el rendimiento sin bajadas de frames.

¿Por qué usar object pooling y un level manager?

La tentación de “añadir y añadir bloques” acaba en overflow de memoria, bajadas de frames y mala experiencia. La solución es reciclar objetos ya instanciados: el object pooling mantiene un conjunto de elementos listos para reutilizar, evitando el coste de instanciar en tiempo real.

  • Instanciar en exceso consume RAM y provoca lag.
  • Reciclar objetos reduce el coste computacional.
  • El level manager centraliza crear, mover, posicionar y destruir bloques.

¿Qué problema evita el object pooling?

Evita que el juego crezca en memoria sin control. En vez de crear cada bala o bloque desde cero, se reutiliza desde una “recámara” de objetos precargados.

¿Cómo se reciclan objetos como balas?

Se mantienen listas de objetos ya instanciados (10, 20, 50, 100). Cuando uno termina su ciclo (colisiona o sale de pantalla), vuelve a la recámara para usarse de nuevo.

¿Cómo se añade un nuevo bloque con aleatoriedad controlada?

El método para “añadir bloque” parte de una idea clave: el primer bloque no es aleatorio, es el de la posición 0 para no frustrar al jugador. A partir del segundo, se usa Random.Range con distribución uniforme, donde todos los bloques disponibles son equiprobables.

  • Genera un índice aleatorio con Random.Range entre 0 y el tamaño de la lista.
  • Prepara una spawn position con Vector3.zero y calcúlala según el caso.
  • Para el primer bloque: usa el índice 0 y colócalo en level start position.
  • Para los siguientes: coloca el bloque en el exit point del bloque anterior.

Código base para el índice aleatorio y la posición inicial:

int randomIdx = Random.Range(0, allTheLevelBlocks.Count);
Vector3 spawnPosition = Vector3.zero;

Primer bloque fijo en el inicio del nivel:

if (currentLevelBlocks.Count == 0)
{
    var block = Instantiate(allTheLevelBlocks[0]);
    spawnPosition = levelStartPosition.position;
    // ... (sigue con corrección, parent y add)
}

Bloques siguientes con selección uniforme:

else
{
    var block = Instantiate(allTheLevelBlocks[randomIdx]);
    spawnPosition = currentLevelBlocks[currentLevelBlocks.Count - 1]
                        .exitPoint.position;
    // ... (sigue con corrección, parent y add)
}

Haz que todos los bloques sean hijos del level manager para mantener la escena ordenada. Usa SetParent con false para no heredar transformaciones del padre:

block.transform.SetParent(this.transform, false);

¿Cómo se alinea el start point con el exit point?

Para “enganchar” los bloques como eslabones, alinea el start point del nuevo bloque con la spawn position calculada (final del anterior). Aplica una corrección vectorial restando coordenadas. En 2D, la z es 0.

Vector3 correction = new Vector3(
    spawnPosition.x - block.startPoint.position.x,
    spawnPosition.y - block.startPoint.position.y,
    0f
);
block.transform.position = correction;

Finalmente, añade el bloque a la lista actual:

currentLevelBlocks.Add(block);

Claves técnicas: - Random.Range: distribución uniforme para elegir bloques con igual probabilidad. - Primer bloque determinista: índice 0 para facilitar el onboarding del jugador. - Enlace por puntos: usar exit point del último y start point del nuevo. - Parentesco seguro: SetParent(this.transform, false) para no heredar escalados o rotaciones.

¿Cómo probar y ajustar la generación procedural?

Valida visualmente que los bloques se enganchan bien y que no hay artefactos.

  • Elimina el bloque placeholder antes de ejecutar: ya no es necesario.
  • Pulsa Play y comprueba la generación dinámica.
  • En GenerateInitialBlocks, sube temporalmente a 20 para ver variedad.
  • Verifica que todos terminan en la misma altura (ejemplo: y = -4).
  • Haz zoom out y revisa el encadenado de rombos, cuadrados y círculos.
  • Recupera el número inicial (por ejemplo, 2) cuando acabes la prueba.

Próximo paso natural: seguir al personaje y ajustar el manager para destruir bloques cuando ya no hagan falta.

¿Tienes dudas o quieres compartir tu implementación? Escribe un comentario con tu avance y cualquier bloqueo: entre todos podemos mejorar el flujo de generación procedural en Unity.