Soluciona el barrido de cámara tras la muerte del personaje y mejora la estética con un fondo estático que acompaña la acción. Con una notificación inmediata a la cámara desde el player controller y un simple ajuste en la jerarquía de objetos, obtendrás un salto instantáneo y una experiencia visual estable.
¿Cómo evitar el barrido y lograr un salto inmediato de la cámara?
Al reiniciar al personaje a su posición de origen, la cámara debe ser notificada enseguida para que haga un salto, no un barrido. Para ello se expone un método público en la cámara, por ejemplo reset camera position dentro de la lógica de camera follow, y se invoca desde el player controller justo después de reiniciar posición y velocidad.
Reinicia posición y velocidad del personaje.
Busca la cámara por nombre con GameObject.Find usando "Main camera" o por etiqueta: el resultado es el mismo.
Obtén el componente CameraFollow con GetComponent.
Invoca el método público ResetCameraPosition para forzar el salto.
Evita nombrar una variable local como "camera": oculta la propiedad interna de MonoBehaviour y confunde al editor. Usa un nombre como "mainCamera" para claridad.
¿Qué código conecta player y cámara para el reset?
// Desde el PlayerController, tras reiniciar posición y velocidad del personaje:GameObject mainCamera = GameObject.Find("Main camera");mainCamera.GetComponent<CameraFollow>().ResetCameraPosition();
Beneficio inmediato: sin barrido, la cámara vuelve al origen con un salto limpio.
Razón práctica: si el personaje ha avanzado mucho (decenas o miles de metros), el barrido resulta mareante; el salto elimina ese efecto.
¿Cómo usar un background como sprite que siga a la cámara?
El color azul de fondo configurado en el parámetro background de la cámara puede sustituirse por un sprite de la carpeta "sprites" (por ejemplo, "background") para mejorar la estética. El objetivo: que el fondo sea estático visualmente, pero se desplace unido a la cámara.
Arrastra el sprite a la escena y suéltalo al centro.
Escálalo con la herramienta de escalado: estira el cuadrado central para mantener proporción.
Centra su posición: usa 0, 0, 0 para asegurar alineación.
Arrástralo como hijo de la cámara: así se moverá con ella como un "pack" unido.
Al reproducir: el fondo se mantiene estático en pantalla y acompaña a la cámara sin rezagarse.
Resultado: un background consistente que aporta coherencia visual y elimina el desfase típico cuando un fondo no sigue al personaje.
¿Qué ventajas prácticas notarás al implementar estos cambios?
Estos ajustes pulen la experiencia sin sobrecargar el proyecto. La cámara responde con precisión y el entorno luce mejor.
Flujo de reintentos ágil: muerte y reinicio con salto inmediato de cámara.
Comodidad visual: adiós al barrido largo que puede marear tras recorridos extensos.
Estética sólida: el sprite de fondo acompaña la acción y refuerza la inmersión.
Continuidad con lo aprendido: se mantiene la base de escenarios generados con bloques y se prepara el terreno para crecer con nuevos elementos como enemigos o coleccionables en futuras secciones.
¿Tienes dudas sobre el uso de GameObject.Find, el componente CameraFollow o la jerarquía del fondo? Comenta qué parte quieres profundizar y qué comportamiento de cámara buscas lograr.
Si en algún momento el fondo les tapa el contenido es bueno usar la propiedad en el Sprite Render de Order in Layer, la cual es como si tuviéramos capas de imágenes que podemos sobreponer. Y ya que por defecto las demás imágenes que no son el fondo esta en el 0, basta con poner el fondo en -1.
Pensé en hacerlo y tú me lo confirmaste. Gracias!
Excelente aporte muchas gracias!
Si el background o su fondo les llega a tapar sus plataformas revisen en sus plataformas si la posición de Z esta en 0 por que esto podria hacer que el background oculte sus sprites. Me ocurrio que tenia las plataformas grandes con position 10 y me ocultaba
En Z
Ya despues recuerden darle Apply para que se guarden los cambios en su prefab
Luego de completar el diseño de generación de bloques procedural me di cuenta que al ir hacia atrás, no había bloques ya que los destruíamos, mi idea era que los bloques al volver hacia atrás sean los mismos que se habían generado anteriormente y luego destruimos, logré adaptar mi código a esto, el personaje puede ir libremente de adelante hacia atrás y de atrás hacia adelante, conservando los bloques, por delante y por detrás, consumiendo menos CPU, ya que he desactivado los bloques que no están en escena. Les dejo mi código, lo he comentado para subirlo aquí, espero que les sirva. Además tuve que hacer unos cambios con la clase LevelBlock, donde solo añadí una variable int llamada blockID inicializada a 0, y cambios también en el trigger, he creado una clase llamada EnterZone, que sirve como trigger para tanto ir hacia adelante y atrás, y he eliminado el ExitZone:
Les dejo aquí el gif donde se muestra el funcionamiento:
Estos son mis bloques:
Y este es mi código:
LevelManager.cs
publicclassLevelManager:MonoBehaviour{publicstaticLevelManager sharedInstance;publicList<LevelBlock> allTheLevelBlocks =newList<LevelBlock>();publicList<LevelBlock> allGeneratedLevelBlocks =newList<LevelBlock>();publicList<LevelBlock> currentLevelBlocks =newList<LevelBlock>();publicTransform levelStartPosition;voidAwake(){if(sharedInstance ==null){ sharedInstance =this;}}voidStart(){GenerateInitialBlocks();}voidUpdate(){}publicvoidAddLevelBlock(string dir, int blockID, bool rem){//Creo un número random para obtener el siguiente bloque int randomIdx =Random.Range(0, allTheLevelBlocks.Count);//Inicializo variablesLevelBlock block;Vector3 spawnPosition =Vector3.zero;//algoritmo: generamos el primer bloqueif(currentLevelBlocks.Count==0){ block =Instantiate(allTheLevelBlocks[0]);//añado al bloque a la lista "allGeneratedLevelBlocks" donde estaran todos los bloques que hemos generado en la partida allGeneratedLevelBlocks.Add(block); spawnPosition = levelStartPosition.position; block.transform.SetParent(this.transform,false);Vector3 correction =newVector3(spawnPosition.x-block.startPoint.position.x,spawnPosition.y-block.startPoint.position.y,0); block.transform.position= correction;//En nuestro trigger "EnterZone" activo el booleano de que, este bloque, el primero generado, es el primer bloque del nivel, por lo tanto no habrá bloque detrás de él. block.gameObject.GetComponentInChildren<EnterZone>().isEnterZoneOfFirstLevelBlock=true;//añado el bloque a la lista de los bloques actuales. currentLevelBlocks.Add(block);}else{//si no es el primer bloqueif(dir =="r"){//si la dirección a la que camina el personaje es derecha block =Instantiate(allTheLevelBlocks[randomIdx]);//Instancio el bloque nuevoList<int> blockIDs =newList<int>();//Declaro una lista "blockIDs" para obtener la última blockID de los bloques actuales,//para que el algoritmo sepa donde poner el próximo bloque que va a ser generadoforeach(LevelBlock lvlb in currentLevelBlocks){ int blID = lvlb.blockID; blockIDs.Add(blID);}//inicializo la lista blockIDs.Sort();//la ordeno de menor a mayor blockIDs.Reverse();//la doy vuelta, de mayor a menor quedaría block.blockID= blockIDs[0]+1;//declaro que el blockID de este bloque generado es el, el blockID del último bloque en la lista de los bloques actuales más uno. LevelBlock currentLastBlock =null;//inicializo en null una variable LevelBlock donde tendré el último bloque en la lista de los actualesforeach(LevelBlock lvlb in currentLevelBlocks){ int bllvlID = blockIDs[0];if(lvlb.blockID== bllvlID){ currentLastBlock = lvlb;}}//acá la variable currentLastBlock ya tiene valor, no es nula. spawnPosition = currentLastBlock.endPoint.position;//declaro la posición del siguiente bloque, osea, del que estoy generando ahora block.transform.SetParent(this.transform,false);Vector3 correction =newVector3(spawnPosition.x-block.startPoint.position.x,spawnPosition.y-block.startPoint.position.y,0); block.transform.position= correction;//las correcciones de posición currentLevelBlocks.Add(block); allGeneratedLevelBlocks.Add(block);//añado al bloque a las 2 listasif(rem){//si en AddLevelBlock se le ha pasado al booleano rem el parámetro true, se eliminará el bloque anteriorRemoveLevelBlock(blockID,"r");//se le pasa por parámetro el blockID del bloque que llamó al trigger y la dirección en la que iba el personaje}//Este bloque de código es porque si ya hay un bloque delante del bloque en donde ha sido efectuado el trigger, pero desactivado//GameObject.SetActive(false) se eliminará el bloque que estaba siendo generado por la parte superior del código.//por ejemplo: Tienes el bloque con ID 0, un bloque con ID 1, donde activaste el trigger de la EnterZone y otro adelante con ID 2 pero desactivado//esto hará que ese bloque desactivado solo se active y no instancie otro.//creo una Lista de Transforms donde estos serán los hijos de "Level Manager" pasando (true) por los parámetros de//GetComponentsInChildren<T>() hace que también ingrese en la lista los objetos que están GameObject.SetActive(false)Transform[] allChildren = currentLastBlock.gameObject.transform.parent.GetComponentsInChildren<Transform>(true);LevelBlock disabledNextBlock =null;//declaro en null una variable donde veo si existe un bloque siguiente al del trigger (variable blockID pasada por AddLevelBlock())foreach(Transform child in allChildren){if(child.gameObject.activeSelf==false&& child.gameObject.GetComponent<LevelBlock>().blockID== currentLastBlock.blockID+1){//si el hijo está desactivado Y el blockID del hijo es igual al blockID del último bloque más uno disabledNextBlock = child.gameObject.GetComponent<LevelBlock>();//declaro que el bloque deshabilitado es "child"Debug.Log("Encontrado: ID: "+ disabledNextBlock.blockID+". ID DEL ULTIMO ACTIVO: "+ currentLastBlock.blockID);//debug}}if(disabledNextBlock !=null){//si se ha encontrado en el bloque de código de arriba un bloque siguiente al del trigger deshabilitadoDestroy(block.gameObject);//destruyo el bloque generado currentLevelBlocks.Remove(block);//remuevo ese bloque de la lista de los actuales, ya que lo destruí, sino quedaría "missing" en la lista allGeneratedLevelBlocks.Remove(block);//lo mismo de arriba, pero como ya tengo otro bloque generado en el mismo lugar no necesito que esté en esta lista disabledNextBlock.gameObject.SetActive(true);//activo el bloque currentLevelBlocks.Add(disabledNextBlock);//lo añado a los bloques actualesRemoveLevelBlock(blockID,"r");//remuevo el bloque anterior del trigger (si el trigger es blockID 1, se elimina el blockID 0)}}elseif(dir =="l"){//si la dirección del personaje es izquierdaif(blockID !=0){//y el blockID del trigger es diferente de 0 block = allGeneratedLevelBlocks[blockID -1];//se obtiene de todos los bloques generados el index de el blockID - 1 block.gameObject.SetActive(true);//activo ese bloque currentLevelBlocks.Add(block);//lo añado a los bloques actualesif(rem){//si el booleano rem está true en el método se ejecuta borrar el bloque siguiente al del trigger//por ejemplo, si el trigger se ejecutó en el blockID 1, aquí se habilitó el blockID 0, y se quedará desactivado el blockID 2RemoveLevelBlock(blockID,"l");}}}}}publicvoidRemoveLevelBlock(int actualPlayerBlockID, string dir){if(dir =="r"){//si la dirección del personaje es hacia la derechatry{//intento hacer el siguiente códigoLevelBlock oldBlock = allGeneratedLevelBlocks[actualPlayerBlockID-1];//el bloque viejo es igual al indice del actualblockID, osea el blockID del trigger menos uno. currentLevelBlocks.Remove(oldBlock);// remuevo ese bloque de la lista de los actuales oldBlock.gameObject.SetActive(false);//lo deshabilito}catch{//si he encontrado alguna excepción, por ejemplo que no existe el índice actualPlayerBlockID-1, etc. Saldrá un debugDebug.Log("arregla el catch, ID: "+(actualPlayerBlockID)+"dir: "+ dir);//debug}}elseif(dir =="l"){//si la dirección es izquierdaLevelBlock oldBlock = allGeneratedLevelBlocks[actualPlayerBlockID +1];//el bloque viejo es igual al indice del actualblockID, osea el blockID del trigger más uno.try{//intento hacer el siguiente código currentLevelBlocks.Remove(oldBlock);// remuevo ese bloque de la lista de los actuales oldBlock.gameObject.SetActive(false);//lo deshabilito }catch{//si he encontrado alguna excepción, por ejemplo que no existe el índice actualPlayerBlockID+1, etc. Saldrá un debugDebug.Log("arregla el catch, ID: "+ actualPlayerBlockID +1+"dir: "+ dir);//debug}}}publicvoidRemoveAllLevelBlocks(){GameObject go =GameObject.Find("LevelManager");//encuentro el levelmanagerforeach(Transform child in go.transform){//destruyo todos los hijos de levelmanagerDestroy(child.gameObject); allGeneratedLevelBlocks.Clear();//limpio las listas currentLevelBlocks.Clear();}}publicvoidGenerateInitialBlocks(){AddLevelBlock("r",0,false);//solo se añade un bloque al iniciar el juego}}
EnterZone.cs
publicclassEnterZone:MonoBehaviour{public bool isEnterZoneOfFirstLevelBlock =false;//para saber si es el primer bloquepublic bool isOther =true;//para saber si es otro bloquepublic int EnterZoneOfFirstLevelBlockNumber=0;//numero para que no bugee y siga generando en el primer bloquevoidOnTriggerEnter2D(Collider2D collider){if(collider.tag=="Player"){if(collider.gameObject.GetComponent<Rigidbody2D>().velocity.x<0){//si la velocidad en x del rigidbody del player es menor a 0if(this.transform.parent.GetComponent<LevelBlock>().blockID!=0){//y el id del bloque es diferente de 0LevelManager.sharedInstance.AddLevelBlock("l",this.transform.parent.GetComponent<LevelBlock>().blockID,true);}}elseif(collider.gameObject.GetComponent<Rigidbody2D>().velocity.x>0){//si la velocidad en x del rigidbody del player es mayor a 0if(isEnterZoneOfFirstLevelBlock){//si es el primer nivelif(EnterZoneOfFirstLevelBlockNumber<1){//si la variable de debug es menor a 1 se ejecuta lo siguienteLevelManager.sharedInstance.AddLevelBlock("r",this.transform.parent.GetComponent<LevelBlock>().blockID,true);EnterZoneOfFirstLevelBlockNumber++;//para evitar que siga ejecutandose}}elseif(isOther){//si es otro bloque, ejecutar lo siguienteLevelManager.sharedInstance.AddLevelBlock("r",this.transform.parent.GetComponent<LevelBlock>().blockID,true);}}}}}
Para lograr que el LevelBlock anterior y el actual sean distintos se puede, en LevelManager poner dos variables, una para el índice aleatoria anterior y otra para el actual:
int previousLevelBlockIdx, randomIdx;
Al principio de AddLevelBlock se le dice que cambie el aleatorio actual en caso de que se haya repetido:
Hola a todos. Quisiera por favor me dieran algunas ideas de como solucionar este inconveniente. El personaje en algunos puntos se queda paralizado y creo que es por los collider. Gracias! Adjunto imágenes del problema:
Hola. De momento se me ocurre que puedes hacer un poco más pequeño el Collider para darle un poco mas de margen al personaje. Aun así, creo que en algunos casos se seguirá atascando. Esto ocurre por la forma curva del capsule collider.
En una clase el profesor deja el capsule collider como Trigger y coloca otro box collider muy pequeño en los pies del personaje que se encarga de que el personaje colisione con el suelo. Podrías implementarlo de esta misma forma y así reducir al máximo estos atascos con las plataformas.
Saludos.
Que buena seccion fue esta, de verdad muy util
Tengo un pequeño problema y es que cada vez que le doy al enter toda la escena tiritea, ademas cuando muero y le doy al enter no me reposiciona ni el personaje ni la cámara a la posicion inicial, la camara continua desde donde muere el personaje :(
Hola compañero, asegurate de que estas tomando las referencias de la camara y del personaje de manera correcta, ademas de que estas llamando las funciones respectivas al reinicio en el momento de que el personaje muere.
Podrias compartirme tu codigo y te ayudo con gusto :D
Puedes agregar codigo dando click aqui
Y pegandolo entre las comillas invertidas que te aparecen(````)
Y recuerda posicionar a tu personaje en la escena en el mismo punto en el que programaste que regrese tu personaje al morir
Yo quize hacerlo por codigo pues le puse un efecto de barrido para que la camara mirara en la direccion del personaje y quedaria genial y utilice que la posicion del backgraound sea todo el tiempo la misma que el perosnaje, el punto es que al moverse la imagen tiembla como si batallara para seguir al personaje, alguien sabe porque?
Mi cámara sigue haciendo un barrido al resetear la posición del personaje a pesar de ejecutarse el código de RestarPosition, ¿A alguien más le pasa?
No sé como enviar el background al fondo, me esta quedando en frente y esconde al personaje y a las plataformas
Desde un principio creé un Singleton para la camara, así que lo llamé con esta línea: