Destrucción de Bloques y Generación Procedural en Videojuegos
Resumen
Domina el reciclado de bloques en Unity con un flujo claro: destruye siempre el más antiguo, genera el siguiente al instante y mantén la escena limpia. Con un Level Manager basado en singleton, una exit zone con OnTriggerEnter y listas bien gestionadas, lograrás un diseño procedural estable y fluido.
¿Cómo reciclar bloques con un level manager en Unity?
La mecánica central es simple y robusta: mantener una lista ordenada de bloques activos y eliminar siempre el primero. Así, el bloque en posición 1 pasa a 0 tras cada eliminación, garantizando un ciclo sin errores y sin referencias perdidas.
Mantén una lista: CurrentLevelBlocks con el orden de aparición.
Elimina siempre el índice 0: el más antiguo desaparece primero.
Destruye el GameObject para limpiar la escena.
Asegura consistencia con un singleton: SharedInstance en el Level Manager.
¿Qué hace RemoveLevelBlock?
Elimina el primer bloque activo y su objeto en escena. Así se recicla el contenido sin acumular basura.
Porque la cantidad de bloques restantes es variable. while finaliza en cuanto la lista queda vacía. Seguro y predecible.
¿Cuándo eliminar un bloque con una exit zone y ontriggerenter?
La eliminación se dispara al cruzar una zona de salida. Al detectar al jugador por tag, se añaden y se quitan bloques en una sola acción: se agrega uno nuevo y se destruye el más antiguo. Así, siempre hay dos bloques visibles.
Usa un collider como exit zone y marca al jugador con tag "Player".
Detecta la entrada con OnTriggerEnter.
Llama a AddLevelBlock y luego a RemoveLevelBlock.
Mantén la experiencia suave: no dejes huecos ni cortes visibles.
¿Cómo detectar al player por tag?
Verifica la etiqueta del collider para asegurar que la lógica solo corre con el jugador.
El patrón de detección es el mismo: un trigger que reacciona al Player y ejecuta lógica al cruzar la línea. Cambia la acción: aquí reciclas bloques, allí gestionas la muerte.
¿Qué ajustes prácticos evitan problemas visuales y bugs?
Si la destrucción se nota, ajusta la posición de la exit zone unos puntos a la derecha para que el cambio ocurra fuera de cámara. También puedes retrasar la eliminación con Invoke para hacerla aún más discreta.
Desplaza la exit zone si el cambio se ve demasiado pronto.
Retrasa la eliminación con Invoke si necesitas unos milisegundos extra.
Vigila el reinicio tras muerte: hay que resetear juego y escenario.
¿Cómo retrasar la destrucción con invoke?
Invoca la eliminación tras un breve retraso para suavizar la transición.
privatevoidOnTriggerEnter(Collider other){if(other.CompareTag("Player")){ LevelManager.SharedInstance.AddLevelBlock();Invoke(nameof(DeferredRemove),1f);// ajusta el tiempo según necesidad}}privatevoidDeferredRemove(){ LevelManager.SharedInstance.RemoveLevelBlock();}
¿Qué bug aparece al morir el personaje?
Si el personaje muere y no se hace reset del escenario, el reinicio falla: el juego arranca sin reconstruir los bloques. Es clave restaurar tanto el estado del juego como el del nivel tras la muerte.
¿Te gustaría comentar cómo posicionas tu exit zone o qué timing te funciona mejor con Invoke?
Una alternativa mas amistosa y con una mejora en rendimiento es cambiar la condición.
collision.tag == "Player"
Por
Collision.CompareTag("Player")
A mi no me aparece el metodo CompareTag en Colussion
Mucho OJO, a mi teniendo el Player con 2 colliders (tal y como lo tiene Juan Gabriel, el collider de cápsula y el de caja para los pies), me detectaba dos colisiones con el ExitPoint y me borraba los 2 LevelBlock
Solución dejar al Player con 1 solo collider, por ejemplo el de capsula ;=)
No logro ver el tamaño de los 2 colliders de tu personaje, pero la exit zone deberia ser mas pequeña y al primer contacto con el collider de tipo capsula deberia desaparecer, incluyendo el propio exit zone. El metodo que hizo Juan Gabriel si funciona. Creo que omitiste algo :(
O haces un contador con el cual te aseguras de que solo se active una vez :o
Un desafío de las clases anteriores era programar que nuestro personaje pudiera ir no solo hacia adelante sino también hacia atrás, pero, ¿cómo podríamos ir atrás si destruimos los bloques inmediatamente después de superarlos? 🤔
Pues el desafio es que tienes que programar que haga todo lo contrario, mientras hacia delante destruyes los bloques que pasa, hacia atras los crea.
Tienes que pensar una forma donde eso sea posible 😃
Good point 😅. Thank u @ManuelSLemos 😄.
Si queremos destruir los componentes con delay, la misma función "Destroy" recibe como 2do parámetro un float para ese delay.
Destroy(oldBlock,1f);
Interesante, gracias.
Importante, me dio un error de index o algo así. Creía que por alguna razón todos los bloques se eliminaba por un error en el código pero resultó ser que la lista de todos los bloques iniciales a los cuales debemos arrastrar los prefabs de los niveles se había vaciado quién sabe por qué, solo los volví a arrastrar y ya estaba funcionando perfecto
Alguien sabe porque se borran todos mis bloques al momento de pasar por el trigger??
PD. es mi clase del levelManager
<using System.Collections;using System.Collections.Generic;using UnityEngine;publicclassLevelManager:MonoBehaviour{//Este es un singleton para generar niveles, se inicializa en el awekapublicstaticLevelManager sharedInstance;publicList<LevelBlock> allTheLevelBlocks =newList<LevelBlock>();//esto nos permite gestionar el contenido visual de la escena, creamos un espacio de memoria para crear los bloques de nivelpublicList<LevelBlock> currentLevelBlocks =newList<LevelBlock>();//Aqui se empieza a crear el primer bloquepublicTransformLevelStartPosition;privatevoidAwake(){if(sharedInstance ==null){ sharedInstance =this;}}// Start is called before the first frame updatevoidStart(){GenerateInitialBlocks();}// Update is called once per framevoidUpdate(){}publicvoidAddLevelBlock(){ int randomIdx =Random.Range(0, allTheLevelBlocks.Count);LevelBlock block;Vector3 spawnPosition =Vector3.zero;if(currentLevelBlocks.Count==0){// Instantiate se usa para instanciar o tomar uno de eso bloques que tenemos diponibles en este caso el 0 block =Instantiate(allTheLevelBlocks[0]); spawnPosition =LevelStartPosition.position;}else{ block =Instantiate(allTheLevelBlocks[randomIdx]); spawnPosition = currentLevelBlocks[currentLevelBlocks.Count-1].exitPoint.position;} block.transform.SetParent(this.transform,false);//Calculamos la correccionVector3 correction =newVector3( spawnPosition.x- block.startPoint.position.x, spawnPosition.y- block.startPoint.position.y,0);//Movemos la correcion del bloque actual a la position que toca block.transform.position= correction;//Lo añadimos a la seccion de bloques actuales de los currents currentLevelBlocks.Add(block);}//Destruimos los bloquespublicvoidRemoveLevelBlock(){//Llamamos al bloque que esta en la posicion 0LevelBlock oldBlock = currentLevelBlocks[0];//Invocamos el metodo remove para eliminar el bloque old (0) currentLevelBlocks.Remove(oldBlock);//Destruimos el bloque old (0)Destroy(oldBlock.gameObject);}//Aqui les incdicamos que debemos edstruir todospublicvoidRemoveAllLevelBlocks(){//mientras existan bloqueswhile(currentLevelBlocks.Count>0){//...Invocamos al metodo remove para eliminar todos los bloquesRemoveLevelBlock();}}publicvoidGenerateInitialBlocks(){for(int i =0; i <4; i++){AddLevelBlock();}}}>
Fíjese en el script "ExitZone" en el if de "InTriggerEnter2D" si tál vez ingresó RemoveAllLevelBlocks() en lugar de RemoveLevelBlocks()
Cada clase que avanzo me doy cuenta de que voy comprendiendo mejor la sintaxis de C# y lo mejor de todo, aprendiendo a interpretarla haciendo mis propios comentarios en el código.
publicvoidRemoveLevelBlock()//Metodo para eliminar bloque de nivel{/*Dentro del método RemoveBlock hemos instanciado la clase LevelBlock con la variable oldBlock
* en el elemento 0 del array CurrentLevelBlocks.
* Invocamos el método Remove para que elimine el OldBlock en la posición 0 del Array,
* de manera que cuando se elimine el bloque 0, el bloque siguiente pasará a ser el elemento 0
* del Array (Lista de bloques en Unity) y así sucesivamente con todos los bloques.
* Por último hemos invocado el método Destroy para que destruya de la pantalla de juego
* el GameObject instanciado y llamado OldBlock.*/LevelBlock oldBlock =CurrentLevelBlocks[0];CurrentLevelBlocks.Remove(oldBlock);Destroy(oldBlock.gameObject);}
Solemos hacer uso de bucle while cuando no sabemos la cantidad de iteraciones que queremos que se ejecute algo.
Tener en cuenta que al estar destruyendo y generando constantemente, hace que sea un mayor esfuerzo para el procesador.
Por eso el Patron de diseño Object Pool trata de reciclar los recursos.
El profesor recomienda usar la función Invoke, pero la misma recibe como parametro un string, no una función. Como podría resolverlo?
En cuestión de rendimiento comento que instanciar objetos en tiempo de ejecución consume bastante recurso y por eso usamos el object pooling , pero quiero preguntar si es mejor cuando instanciamos todos los objetos al iniciar el juego pero que estén desactivados y después los vamos activando, después nuevamente los desactivamos para volverlos activar en otro momento. esto seria mejor que estar instanciando?
Como segunda duda de la primera pregunta, los objetos que están desactivados no consumen recursos?