El personaje y el controlador

1

Introducción: De la idea al desarrollo

2

Estructura de carpetas en Unity

3

Assets, Game Objects y Sprite Atlas

4

Animaciones desde un sprite map

5

Animation Controller y Transiciones

6

Plataformas y gravedad

7

Física en Unity

8

El script de control

9

Programando funciones en C# y Unity: Jump

10

Detectar el suelo con Raycast

11

Herramientas de debugging visual

12

Cambiar las animaciones según los estados

13

Reto: Terminando nuestras animaciones

14

Solución del reto

15

Hacer que el personaje camine

El manager del videojuego

16

Cómo funciona el Game Manager

17

El Singleton

18

El modo de juego

19

Input manager y juegos multiplataforma

20

Corrección del Bug del salto

21

La zona de muerte

22

Reiniciar la partida

23

Corrigiendo los bugs al reiniciar nuestro juego

Diseño procedural de niveles 2D

24

El diseño de niveles a mano

25

Configurando nuestros assets para el diseño procedural

26

Generación procedural de niveles

27

Creando la zona que eliminará bloques del nivel excedentes

28

Añadir un nuevo bloque de forma aleatoria

29

La cámara que sigue al jugador

30

Destrucción de bloques antiguos

31

Terminando de programar la destrucción de bloques antiguos

32

Solucionando el salto de la cámara al reiniciar el juego

HUD, menús y gameplay

33

El canvas en Unity

34

Uso de botones para crear un menú

35

La lógica de los menús

36

Ejercicio: Preparando el menú del juego

37

Programando el menú del juego

38

Los coleccionables del juego

39

Actualizar UI de coleccionables

40

Iniciando con pociones y maná

41

Pociones de vida y maná

42

Programando las barras de vida y maná

43

Calculando los puntajes con la distancia y el tiempo de juego

44

La lógica del maná

Enemigos y dificultad

45

Plataformas móviles

46

Iniciar movimiento de la plataforma con trigger

47

Enemigos móviles

48

Enemigos móviles: preparando nuestro enemigo para girar

49

Enemigos móviles: programando que gire al chocar

50

Arreglando el collider de nuestra roca

51

Programando la condición de muerte del personaje

52

Añadiendo música de fondo a nuestro videojuego

53

Añadiendo efectos de sonido y cierre del curso

No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Curso de C# para Videojuegos

Curso de C# para Videojuegos

Juan Gabriel Gomila

Juan Gabriel Gomila

Solucionando el salto de la cámara al reiniciar el juego

32/53
Recursos

Aportes 9

Preguntas 5

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

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.

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

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

public class LevelManager : MonoBehaviour
{
    
	public static LevelManager sharedInstance;
	
	public List<LevelBlock> allTheLevelBlocks = new List<LevelBlock>();
	
	public List<LevelBlock> allGeneratedLevelBlocks = new List<LevelBlock>();
	
	public List<LevelBlock> currentLevelBlocks = new List<LevelBlock>();
	
	
	public Transform levelStartPosition;

	
	void Awake(){
		if(sharedInstance == null){
			sharedInstance = this;
		}
	}
	
    void Start()
    {
        GenerateInitialBlocks();
    }

    void Update()
    {
       
    }
	
	public void AddLevelBlock(string dir, int blockID, bool rem){
		//Creo un número random para obtener el siguiente bloque
		int randomIdx = Random.Range(0, allTheLevelBlocks.Count);
		
		//Inicializo variables
		LevelBlock block;
		Vector3 spawnPosition = Vector3.zero;

		//algoritmo: generamos el primer bloque
		if(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 = new Vector3(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 bloque
				if(dir == "r"){ //si la dirección a la que camina el personaje es derecha
					
				block = Instantiate(allTheLevelBlocks[randomIdx]); //Instancio el bloque nuevo
				
				List<int> blockIDs = new List<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 generado
				
				foreach(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 actuales
				
				foreach(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 = new Vector3(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 listas
				
				
				if(rem){ //si en AddLevelBlock se le ha pasado al booleano rem el parámetro true, se eliminará el bloque anterior
					RemoveLevelBlock(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 deshabilitado
					Destroy(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 actuales
					RemoveLevelBlock(blockID, "r"); //remuevo el bloque anterior del trigger (si el trigger es blockID 1, se elimina el blockID 0)
				}
				
				
				}else if(dir == "l"){ //si la dirección del personaje es izquierda
				if(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 actuales
					if(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 2
					RemoveLevelBlock(blockID, "l");
				}
				}
				}

		}	
	}
	
	public void RemoveLevelBlock(int actualPlayerBlockID, string dir){
		if(dir == "r"){ //si la dirección del personaje es hacia la derecha
		try{ //intento hacer el siguiente código
		LevelBlock 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 debug
			Debug.Log("arregla el catch, ID: " + (actualPlayerBlockID) + "dir: " + dir); //debug
		}
		
		
		}else if(dir == "l"){  //si la dirección es izquierda
		LevelBlock 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 debug
			Debug.Log("arregla el catch, ID: " + actualPlayerBlockID + 1 + "dir: " + dir);//debug
			}
		}	
	}
	
	public void RemoveAllLevelBlocks(){
		
		GameObject go = GameObject.Find("LevelManager"); //encuentro el levelmanager
		
		foreach (Transform child in go.transform){ //destruyo todos los hijos de levelmanager
			Destroy(child.gameObject);
			allGeneratedLevelBlocks.Clear(); //limpio las listas
			currentLevelBlocks.Clear();
		}
		
	}
	
	public void GenerateInitialBlocks(){
			AddLevelBlock("r", 0, false); //solo se añade un bloque al iniciar el juego
	}
}

EnterZone.cs

public class EnterZone : MonoBehaviour
{
	public bool isEnterZoneOfFirstLevelBlock = false; //para saber si es el primer bloque
	public bool isOther = true; //para saber si es otro bloque
	public int EnterZoneOfFirstLevelBlockNumber = 0; //numero para que no bugee y siga generando en el primer bloque
	
    void OnTriggerEnter2D(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 0
				if(this.transform.parent.GetComponent<LevelBlock>().blockID != 0){ //y el id del bloque es diferente de 0
					LevelManager.sharedInstance.AddLevelBlock("l",  this.transform.parent.GetComponent<LevelBlock>().blockID, true); 
				}
			}else if(collider.gameObject.GetComponent<Rigidbody2D>().velocity.x > 0){ //si la velocidad en x del rigidbody del player es mayor a 0
				if(isEnterZoneOfFirstLevelBlock){ //si es el primer nivel
					if(EnterZoneOfFirstLevelBlockNumber < 1){ //si la variable de debug es menor a 1 se ejecuta lo siguiente
						LevelManager.sharedInstance.AddLevelBlock("r", this.transform.parent.GetComponent<LevelBlock>().blockID, true);
						EnterZoneOfFirstLevelBlockNumber++; //para evitar que siga ejecutandose
					}
				}else if(isOther){ //si es otro bloque, ejecutar lo siguiente
					LevelManager.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:

        while(randomIdx == previousLevelBlockIdx)
        {
            randomIdx = Random.Range(1, allTheLevelBlocks.Count);
        }

Al final de AddLevelBlock se actualiza el anterior con el actual

        previousLevelBlockIdx = randomIdx;

yo como estamos ocupando la main camera ocupe este codigo

Camera.main.GetComponent<CameraFollow>().ResetCameraPosition();

Que buena seccion fue esta, de verdad muy util

Desde un principio creé un Singleton para la camara, así que lo llamé con esta línea:

CameraFollow.sharedInstance.ResetCameraPosition();

La camara tuvo un desface al seguir al personaje en el momento de subir el background como hijo. [](

Excelente clase.