DESTRUCCION DE OBJETOS QUE COLISIONAN

Destruir los objetos no es nada de otro mundo. De hecho es exactamente igual que como ya lo veníamos haciendo. Lo que cambia es el momento en el que lo hacemos. Cada uno de los bloques de código que venímos creando representa un “momento” o “suceso” que ocurre dentro de Unity. Por ejemplo:

  • Update ocurre cuando Unity va a pintar un Frame.
  • Start ocurre cuando el GameObject se inicializa.
  • OnBecameInvisible ocurre cuando sale de la pantalla.

Y tenemos una gran cantidad de etceteras. Hay muchos más eventos que van a ir conociendo conforme vayan aprendiendo el motor. Vamos a ver ahora algunos más, relacionados con la colisión.

 

private void OnCollisionEnter2D(Collision2D other)
{
    //Entra acá cuando se detecta una colisión con este objeto
    //y otro más.

    //Sólo entra una vez y no vuelve entrar hasta
    //que el objeto no deja de colisionar y vuelve a hacerlo.
}

private void OnCollisionStay2D(Collision2D other)
{
    //Entra frame a frame mientras este objeto y otro más
    //estén colisionando.    
}

private void OnCollisionExit2D(Collision2D other)
{
    //Entra cuando este objeto y otro más dejan de colisionar.

    //Sólo entra una vez y no vuelve a entrar hasta que este
    //objeto y otro más vuelvan a colisionar y dejen de hacerlo.
}

 

Estos 3 bloques de código se ejecutan cuando se producen colisiones entre los distintos objetos. El motor de física los ejecuta dependiendo de cuál situación sea la que se dé.

 

¡ATENCIÓN!

Tengan en cuenta que para que estos eventos se produzcan, ambos objetos deben tener un Collider y al menos uno de los dos debe tener un Rigidbody.

 

Así como entramos en debate para ver qué tipo de Collider poníamos, acá también podríamos tener uno parecido. Pero “cuál” de los tres ponemos no sería tan importante como “a quién“. ¿Cuál de todos los objetos debería escuchar estos eventos? Esto ya depende de cómo hagamos el planteo. Podríamos hacer dos, entre muchos:

  1. La nave enemiga podría declararlos y al momento de colisionar con una bullet destruirLA y sacarSE vida.
  2. La bullet podría declararlos y al momento de colisionar con un enemigo destruirSE y sacarLE vida.

 

Por supuesto hay muchos más caminos posibles. Pueden tomar el que ustedes gusten. Yo planteo dos para no hacer mucho debate y salirnos de la idea original del post. Sin ir más lejos, una idea más estructurada podría ser crear un script que sea “Dañable” y otro que se encargue de hacer daño a los objetos “Dañable“s. Sin ir más lejos, Unity tiene un “2D Game Kit” que incluye varias herramientas como para hacer un juego 2D, que normalmente siempre escribimos. Pueden echarle un vistazo a la herramienta entera o específicamente a la parte del Sistema de Daño.

Voy a mostrar una forma sencilla, aunque vale aclarar que muchos puristas del código la van a odiar, pero para aprender les va a servir, luego ustedes tomen el camino que más les guste. Pero… NUNCA SE QUEDEN ÚNICAMENTE CON LO QUE YO MUESTRO ACÁ!!

Vamos a escribir el siguiente código en el script Bullet para empezar la detección de colisiones.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    public float speed;
    
    // Use this for initialization
    void Start ()
    {
        
    }
    
    // Update is called once per frame
    void Update()
    {
        transform.position += Vector3.up * speed * Time.deltaTime;
    }
    
    //Se ejecuta cuando este objeto deja de ser visible por todas las cámaras.
    void OnBecameInvisible()
    {
        GameObject.Destroy(gameObject);
    }

    //Se ejecuta cuando se produce una colisión con este objeto.
    private void OnCollisionEnter2D(Collision2D other)
    {
        Debug.Log("Choqué con algo!!");
    }
}

 

Este código va a ejecutarse en el momento que la bullet detecte una colisión con algún elemento. Tengan en cuenta que en pasos anteriores modificamos layers y especificamos que determinadas layers no colisionen. Esto quiere decir que cuando dos de esos elementos se superpongan TAMPOCO se va a ejecutar este bloque de código. Tenganlo en cuenta!!

Y acá nos queda el resultado:

 

 

Vamos a agregar ahora algo de código como para que la bullet se destruya. Este código ya lo conocen y no debería ser nada fuera de lo común a esta altura del post.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    public float speed;
    
    // Use this for initialization
    void Start ()
    {
        
    }
    
    // Update is called once per frame
    void Update()
    {
        transform.position += Vector3.up * speed * Time.deltaTime;
    }
    
    //Se ejecuta cuando este objeto deja de ser visible por todas las cámaras.
    void OnBecameInvisible()
    {
        GameObject.Destroy(gameObject);
    }

    //Se ejecuta cuando se produce una colisión con este objeto.
    private void OnCollisionEnter2D(Collision2D other)
    {
        GameObject.Destroy(gameObject);
    }
}

 

Ahora las bullets se destruyen al momento de colisionar contra un objeto. Pero… No “deberían” destruir al objeto contra el que chocan, sino hacerle daño. Para esto, vamos a hacer un bloque de código que se encargue de hacerle daño al objeto, y lo único que vamos a agregar en la bullet es que llamado a ese bloque. Pero ya depende de ese bloque qué se hace en consecuencia. Vamos a llamar a ese bloque “OnRecibirDano“. Hago dos aclaraciones acá:

  1. Lo puse en español para que se den cuenta que no es un bloque que ya exista en Unity, como Update o Start.
  2. No tiene la letra “ñ” porque se considera una mala práctica usar caracteres fuera del inglés (más info).

Vamos al script de nuestro enemigo, y agreguemos este bloque de código:

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour
{
    public float speed;

    // Use this for initialization
    void Start ()
    {
        
    }
    
    // Update is called once per frame
    void Update ()
    {
        transform.position += transform.up * speed * Time.deltaTime;
    }

    private void OnBecameInvisible()
    {
        GameObject.Destroy(gameObject);
    }

    public void OnRecibirDano()
    {
        Debug.Log("Ouch!!");
    }
}

 

Y… ¿Cómo llamamos a ese bloque de código?

Cualquiera de los bloques que se ejecutan al detectar una colisión, recibe un parámetro. Ese parámetro cuenta con toda la información de la colisión, como el objeto que le acaba de pegar, el punto de colisión, y datos relevantes al proceso. De esta forma, podemos acceder al objeto que nos acaba de golpear mediante el uso de ese parámetro.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    public float speed;
    
    // Use this for initialization
    void Start ()
    {
        
    }
    
    // Update is called once per frame
    void Update()
    {
        transform.position += Vector3.up * speed * Time.deltaTime;
    }
    
    //Se ejecuta cuando este objeto deja de ser visible por todas las cámaras.
    void OnBecameInvisible()
    {
        GameObject.Destroy(gameObject);
    }

    //Se ejecuta cuando se produce una colisión con este objeto.
    private void OnCollisionEnter2D(Collision2D other)
    {
        Debug.Log("Me golpeó " + other.gameObject.name);
        GameObject.Destroy(gameObject);
    }
}

 

Si compilan este código, verán que cada vez que la bullet impacta nos muestra el nombre del objeto con el que impactó. Ahora… ¿Cómo le “hacemos daño”? Ponemos el siguiente código:

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    public float speed;
    
    // Use this for initialization
    void Start ()
    {
        
    }
    
    // Update is called once per frame
    void Update()
    {
        transform.position += Vector3.up * speed * Time.deltaTime;
    }
    
    //Se ejecuta cuando este objeto deja de ser visible por todas las cámaras.
    void OnBecameInvisible()
    {
        GameObject.Destroy(gameObject);
    }

    //Se ejecuta cuando se produce una colisión con este objeto.
    private void OnCollisionEnter2D(Collision2D other)
    {
        other.gameObject.SendMessage("OnRecibirDano");
        GameObject.Destroy(gameObject);
    }
}

 

SendMessage lo que hace es ejecutar el bloque de código con el nombre que especificamos en el objeto, en este caso, con el que colisionamos. Entonces, si el objeto es “Dañable” tendrá el bloque de código OnRecibirDano. La lógica nos dirá que si el objeto no es dañable, entonces no debería pasar nada. O sea, no se le debería hacer daño. Pero si el objeto no tiene el bloque de código OnRecibirDano Unity nos mostrará el siguiente mensaje:

 

 

Para evitar ese error, podemos especificarle a Unity que no nos interesa si el objeto no cuenta con ese bloque de código, y que en caso de no tenerlo no haga nada.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    public float speed;
    
    // Use this for initialization
    void Start ()
    {
        
    }
    
    // Update is called once per frame
    void Update()
    {
        transform.position += Vector3.up * speed * Time.deltaTime;
    }
    
    //Se ejecuta cuando este objeto deja de ser visible por todas las cámaras.
    void OnBecameInvisible()
    {
        GameObject.Destroy(gameObject);
    }

    //Se ejecuta cuando se produce una colisión con este objeto.
    private void OnCollisionEnter2D(Collision2D other)
    {
        other.gameObject.SendMessage("OnRecibirDano", SendMessageOptions.DontRequireReceiver);
        GameObject.Destroy(gameObject);
    }
}

 

Y con esto a Unity deja de importarle si el bloque de código existe o no. O sea, que si algún objeto con el que chocamos tiene ese bloque de código, lo ejecuta al colisionar, sino lo omite. También podemos especificarle algún parámetro, como por ejemplo la cantidad de daño que le hacemos. Simplemente tenemos que especificarlo. Podemos crear una variable damage en el script Bullet y luego pasarla a OnRecibirDano.

 

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    public float damage;
    public float speed;
    
    // Use this for initialization
    void Start ()
    {
        
    }
    
    // Update is called once per frame
    void Update()
    {
        transform.position += Vector3.up * speed * Time.deltaTime;
    }
    
    //Se ejecuta cuando este objeto deja de ser visible por todas las cámaras.
    void OnBecameInvisible()
    {
        GameObject.Destroy(gameObject);
    }

    //Se ejecuta cuando se produce una colisión con este objeto.
    private void OnCollisionEnter2D(Collision2D other)
    {
        other.gameObject.SendMessage("OnRecibirDano", damage, SendMessageOptions.DontRequireReceiver);
        GameObject.Destroy(gameObject);
    }
}

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour
{
    public float speed;

    // Use this for initialization
    void Start ()
    {
        
    }
    
    // Update is called once per frame
    void Update ()
    {
        transform.position += transform.up * speed * Time.deltaTime;
    }

    private void OnBecameInvisible()
    {
        GameObject.Destroy(gameObject);
    }

    public void OnRecibirDano(float cantidad)
    {
        Debug.Log("Ouch!! Me hicieron " + cantidad + " de daño!!");
        GameObject.Destroy(gameObject);
    }
}

 

Y con esto nuestros enemigos ya pueden recibir daño!! Y, por supuesto, la bullet ahora también puede dañar objetos. Para practicar un poco, prueben ponerle una determinada cantidad de vida a los enemigos. Recuerden ponerle un valor a la variable damage del script Bullet.