Índice del curso de Unity 3D

  1. Introducción
  2. Instalación
  3. Interfaz
  4. Conceptos básicos
  5. Ejercicio: cubos
  6. Cambiar el color de algo
  7. Introducción a C#
  8. Conceptos para realización de juegos 2D
  9. Acceder a componentes desde código
  10. Uso del transform
  11. User Interface
  12. Elije tu propia aventura
  13. Acierta imagen
  14. Fall Down Game
  15. Galería de tiro
  16. Flappy Bird
  17. PONG
  18. Carreras
  19. Panel Animator
  20. Plataformas
  21. Plataformas
  22. Puzzle
  23. Plataformas con RayCast
  24. Memory (juego de las parejas)
  25. Máscara
  26. Publicación
  27. Ejercicio: First Person Shooter

Plataformas

Ponemos la imagen de fondo

imagen de fondo juego con Unity 3D

Ponemos al prota en pantalla

  • Debe tener la animación de respirar.
  • Debe caer por gravedad
player juego plataformas

Tilemap Editor

Crear un nuevo tilemap

  1. Botón derecho sobre el panel de jerarquía -> 2D Object -> Tilemap
  2. Menú Window -> 2D -> Tile Palette
  3. New Palette -> Seleccionamos los tiles independientes -> Se creará una nueva paleta con los tiles seleccionados.

Insertar tiles en la escena

  1. Habrá que ajustar el grid size del grid para que se corresponda con el de los tiles que estamos añadiendo en pantalla. Por ejemplo, si el tamaño de los tiles que estamos insertando es de 32x32px, el grid size será X:0.32 e Y:0.32. unity3d cell size
  2. El punto origen del tile debería ser su centro, así que nos aseguramos de que tenemos los valores para el anchor con 0.5. unity 3d tile anchor
  3. Deberemos tener seleccionada la herramienta de pintar. pintar tiles en unity
  4. Si quiero desplazar tiles dentro del tile palette, debo:
    1. Tener la opción de edit seleccionada
    2. Seleccionar el tile con la herramienta de selección.
    3. Desplazar el tile con la herramienta de desplazar
    desplazar tiles en unity 3d

    Insertar tiles colisionables

    1. Seleccionamos el grid y le añadimos un nuevo tilemap que contendrá los tiles que sean colisionables.
    2. A este nuevo tilemap le añadimos el componente Tilemap Collider 2D.
    3. Los tiles que añadamos a la escena y que provengan de este tilemap, tendrán colisión.
    4. Recuerda que para que la colisión funcione, el player tenga un Box Collider 2D.

    Desplazamiento horizontal

    Para gestionar el movimiento del personaje podemos:

    • Usar fuerzas (como en este ejemplo, usando el RigidBody).
    • No usar fuerzas (utilizando transform.position para gestionar el desplazamiento). Para este caso es una solución peor, ya que cuando el jugador colisione contra una pared, lo que va a ocurrir es que se va a introducir dentro ligeramente. Es como si fuese un balón de playa que se introduce dentro del agua y luego es expulsado. Esto no ocurre cuando usamos fuerzas.
    o no

    • El protagonista tendrá el componente Capsule Collider 2D. Usaremos un Capsule Collider en lugar de un Box Collider porque en este juego en el que estamos utilizando tiles, a veces el box colider colisiona contra una esquina saliente de un tile, impidiendo el desplazamiento del personaje. Esto es un error de unity, ya que realmente los tiles estarían todos a la misma altura.
    • El protagonista tendrá, además, el componente Rigidbody2D.
    • Es posible controlar las teclas asociadas al Input Horizontal en Edit -> Project Settings -> Input -> Axes -> Horizontal
    • Si intentamos usar las flechas de dirección para mover al personaje, es probable que tengamos problemas. Tenemos dos opciones:
      • Usar las teclas a y d.
      • Jugar con la pantalla de juego maximizada (shift + space).
    private int walkspeed = 3;
    private float horInput;
    private Vector2 movement;
    private Rigidbody2D rb;
    
    //Esto se ejecuta más veces por segundo. Lo usaremos para evaluar las físicas
    void FixedUpdate(){
    	this.horInput = Input.GetAxis ("Horizontal");
    	//Recogemos la velocidad actual del rigid body, para que no se resetee el valor de y cuando asignemos la velocity
    	this.movement = this.rb.velocity;
    	this.movement.x = horInput * walkspeed;
    	this.rb.velocity = this.movement;
    }

    Diferencia entre el FixedUpdate y Update

    El FixedUpdate se utiliza para que en ordenadores con distinta potencia, las fuerzas se apliquen el mismo número de veces por segundo. Por tanto, lo usaremos cuando haya una fuerza constante que se está ejecutando. En este caso, la fuerza que hace que el protagonista se desplace.

    El método Update lo usaremos para escuchar, por ejemplo, eventos que se producen de forma puntual e inmediata, como un salto o disparar una bala.

    Salto (se puede saltar muchas veces)

    private bool jumpInput;
    
    void Update(){
    	jumpInput = Input.GetButtonDown("Jump");
    }
    
    void FixedUpdate(){
    	if (jumpInput){
    		movement.y = jumpImpulse;
    	}

    En Rigid Body -> Constraints pondremos restricciones para la rotación para que el jugador no pueda caer de cabeza.

    Se puede saltar sólo cuando estamos en contacto con el suelo

    Debemos asignar un tag al tilemap.

    private bool grounded;
    
    	...
    	if (this.jumpInput && grounded) {
    	...
    	
    void OnCollisionEnter2D(Collision2D col ){
    	if (col.gameObject.tag == "suelo") {
    		grounded = true;
    	}
    }
    
    //Ponemos el grounded a false en este método en lugar de cuando saltamos, para que el salto deje de funcionar si caemos por un precipicio (en este caso perdemos el contacto con el suelo aunque no hemos saltado)
    void OnCollisionExit2D(Collision2D col){
    	if (col.gameObject.tag == "suelo") {
    		grounded = false;
    	}
    }

    Añadir animación de andar

    En este caso, gestionaremos la activación de las distintas animaciones utilizando parámetros desde el panel animator.

    unity 3d animator plataformas

    Para que las transiciones entre una animación y otra se produzcan de manera instantánea cuando el usuario pulse las teclas de movimiento, deben seleccionar dichas transiciones en el panel animator y cambiar algunos de sus parámetros en el panel inspector:

    transiciones instantáneas unity 3d

    En lugar de llamar a SetBool constantemente, lo haremos sólo cuando sea necesario, es decir cuando haya un cambio entre estar corriendo y dejar de correr. Hacemos esto porque el método SetBool es costoso a nivel de rendimiento.

    // Si el jugador no estaba corriendo y se pone a correr
    // y si el jugador estaba corriendo y deja de correr
    // cambio la animación de correr
    if((horInput != 0.0f && !corriendo) ||(horInput == 0 && corriendo)){
    	corriendo = !corriendo;
    	anim.SetBool("corriendo", corriendo);
    }
    if (jumpInput > 0 && grounded){
    	anim.SetBool("saltando", true);
    	movement.y = jumpImpulse;
    }

    Hacer que el prota encare la dirección correcta

    if (horInput < 0) this.facing = true;
    else if(horInput > 0) this.facing = false;
    this.spr.flipX = facing;

    Añadir un enemigo que está quieto

    Enemigo que camina

    enemigovoid Update () {
    	transform.Translate (direccion*velocidad * Vector2.right * Time.deltaTime, 0);
    }

    El enemigo cambia de dirección

    void Start(){
    	spr = GetComponent<SpriteRenderer>();
    	boxCol = GetComponent<BoxCollider2D>();
    	float margenRayo = 0.2f;
    	distanciaRayo = boxCol.size.x / 2 + margenRayo;
    }
    
    void Update(){
    	...
    	Vector2 puntoOrigenHaciaAbajo = new Vector2 (this.transform.position.x  + direccion * distanciaRayo, this.transform.position.y);
    	RaycastHit2D hitAbajo= Physics2D.Raycast(puntoOrigenHaciaAbajo, Vector2.down,2f);
    	Debug.DrawRay (puntoOrigenHaciaAbajo, Vector2.down, Color.red);
    
    	if(hitAbajo.collider == null){
    		spr.flipX = (direccion == 1) ? false : true; 
    		direccion = direccion * -1;
    	}
    }
    rayo enemigo

    Colisión contra el enemigo

    El siguiente código hace que cuando impactemos contra un enemigo salgamos despedidos en dirección contraria.

    Player.csvoid OnCollisionEnter2D(Collision2D col){
    	...
    	if (col.gameObject.tag == "enemigo") {
    		controlesActivos = false;
    		CancelInvoke ("Rehabilitar"); //Cancelamos todas las invocaciones del método rehabilitar que pudiesemos tener de antes.
    		Invoke ("Rehabilitar", 0.5f);
    		float direccionEmpuje = Mathf.Sign (this.gameObject.transform.position.x - col.gameObject.transform.position.x);
    		Debug.Log ("Direccion empuje = " + direccionEmpuje);
    		rb.velocity = new Vector2(direccionEmpuje*10f, 10f);;
    		Debug.Log (rb.velocity);
    	}
    }
    void Rehabilitar(){
    	controlesActivos = true;
    }
    
    //Sólo podremos movernos si no estamos volando tras colisionar con un enemigo
    if (controlesActivos == true ) {
    	movement.x = horInput * walkspeed;
    
    	if (jumpInput && grounded ) {
    		movement.y = jumpImpulse;
    	}
    	rb.velocity = this.movement;
    }

    Balas

    Bala.csvoid Start(){
    	...
    	// transform.right tiene en cuenta la rotación local del objeto
    	rb.AddForce(transform.right * velocidadInicial, ForceMode2D.Impulse);
    }
    Player.csvoid Update(){
    	...
    	fire1 = Input.GetButtonDown("Fire1"); // Por defecto Fire1 es left ctrl
    	...
    }
    
    void Disparar(){
    	Instantiate (balaPrefab, transform.position, balaPrefab.transform.rotation) as GameObject;
    }

    Marcaremos la check is Trigger del box collider para que las balas no empujen al player que las está disparando.

    La bala se mueve en la dirección correcta

    Utilizaremos un código similar a este para el protagonista:

    Player.csvoid Disparar(){
    	Quaternion rotacion = facing ? Quaternion.Euler(new Vector3(0, 180, 0)) : Quaternion.Euler(new Vector3(0, 0, 0));
    	Instantiate(balaPrefab, transform.position, rotacion);
    }

    Plataforma móvil I

    Utilizaremos un empty con dos hijos: la plataforma que voy a mover y el punto de destino. A la plataforma que queremos mover le asociaremos el siguiente script. La propiead target será el destino del movimiento, que irá cambiando según lo alcancemos.

    plataforma móvil con unity 3d
    PlataformaDinamicapublic class PlataformaDinamica : MonoBehaviour {
    	public Transform target;
    	public float speed;
    	private Vector3 start,end;
    
    	void Start () {
    		if (target != null){
    			start=transform.position;
    			end=target.position;
    		}	
    	}
    
    	void FixedUpdate(){
    		if (target != null){
    			float fixedSpeed = speed * Time.deltaTime;
    			transform.position=Vector3.MoveTowards(transform.position, target.position, fixedSpeed);
    		}
    //comprobamos si la posicion final es igual al ppio, target position vale end y si no, vale start
    		if (transform.position == target.position){
    			target.position= (target.position ==start) ? end: start; //cambiamos la posicion del target
    		}	
    	}
    }

    Si queremos ver dibujada en pantalla la línea de la trayectoria, podemos vincular al objeto, además, la siguiente clase

    DrawSceneLine.cspublic class DrawSceneLine : MonoBehaviour {
    
    	public Transform from; //origen del desplazamiento
    	public Transform to;//final del desplazamiento
    
    	void OnDrawGizmosSelected(){
    
    		if (from != null && to != null){
    			Gizmos.color=Color.cyan;
    			Gizmos.DrawLine(from.position,to.position);
    			Gizmos.DrawSphere(from.position, 0.15f);
    			Gizmos.DrawSphere(to.position, 0.15f);
    		}
    	}
    }

    Plataforma móvil II - sin colisión cuando entramos desde abajo

    1. En el Box Collider de la plataforma, marcamos la check Used By Efector
    2. Añadimos a la plataforma el componente Platform Effector 2D. Definimos un surface arc de menos de 180º.

    Plataforma móvil III - Evitar que la plataforma "escupa" al prota

    Cuando el player entre en la plataforma desde abajo, no habrá colisión. Cuando entre desde arriba, haremos al player hijo de la plataforma.

    PlataformaMovil.csvoid OnCollisionEnter2D(Collision2D col){
    	if (col.transform.tag == "Player"){
    		if (col.transform.position.y > transform.position.y){
    			col.transform.parent = transform;
    		}
    	}
    }
    
    void OnCollisionExit2D(Collision2D col) {
    	if(col.gameObject.tag == "Player"){
    		col.transform.parent = null;
    	}
    }

    Evitar que el Player se quede enganchado al ir contra una plataforma

    Evitar que el player se quede enganchado al ir en la dirección de una plataforma
    Player.csLayerMask capasAfectadasPorElRayo;
    
    void FixedUpdate(){
    	...
    	if ((this.horInput < 0 && !PuedeMoverseIzquierda())
    	|| (this.horInput > 0 && !PuedeMoverseDerecha())){
    		this.movement.x = 0;
    	}
    	...
    }
    
    
    bool PuedeMoverseIzquierda(){
    	int cantidadRayos = 10;
    	//Usamos el skinWidth para que el rayo que sale de los pies y el rayo
    	//que sale de la cabeza no choquen contra el suelo ni el techo
    	float skinWidth = 0.1f;
    	float distanciaEntreRayosDetectores = (cc.size.y - skinWidth*2) / cantidadRayos;
    	float coordenadaYDelRayo = transform.position.y - (cc.size.y / 2) + skinWidth;
    
    	for (int i = 0; i <= cantidadRayos; i++){
    		Vector3 puntoOrigenRayo = new Vector3(transform.position.x - cc.size.x / 2, coordenadaYDelRayo, 0);
    		RaycastHit2D hit = Physics2D.Raycast(puntoOrigenRayo, Vector2.left, longitudRayo, capasAfectadasPorElRayo);
    		Debug.DrawRay(puntoOrigenRayo, Vector2.left, Color.red);
    		if (hit){
    			return false;
    		}
    		coordenadaYDelRayo += distanciaEntreRayosDetectores;
    	}
    	return true;
    }
    
    // Faltaría programar el método PuedeMoverseDerecha();

    No queremos que los rayos colisionen con el propio personaje, así que definimos una layermask en la que no incluímos al Player.

    layer mask con unity 3d

    Barra de vida

    ControlPlayerpublic BarraVita barraVita;
    
    barraVita.actualizarVidas (vidas, maximoVidas);

    BarraVita será el script asociado a un objeto image del canvas al que habremos asociado como sprite un punto blanco. Seleccionaremos en su componente Image:

    • Image Type: Filled
    • Fill Method: Horizontal
    BarraVita.cspublic class BarraVita : MonoBehaviour {
    	private Image barraVida;
    
    	void Start () {
    		barraVida = this.GetComponent<Image> ();
    	}
    
    	public void actualizarVidas(int vidas, int maximoVida){
    		barraVida.fillAmount = vidas*1.0f / maximoVida;
    	}
    }
icono de mandar un mail¡Contacta conmigo!
Pablo Monteserín
contacta conmigoPablo Monteserín

Para dudas técnicas sobre los ejercicios de mis cursos es necesario estar suscrito.