Unity Detección de colisiones. Colliders
Sumario
Introducción
- Más información en:
- En los juegos vamos a necesitar disponer de algún mecanismo que nos diga cuando dos objetos 'chocan'.
- Ejemplo de uso:
- Proyectiles de todo tipo (balas, misiles, láser,...)
- Enemigos que van tras el protagonista
- Abrir o cerrar puertas al acercarse / alejarse
- Unity da solución a esto mediante el uso de Colliders.
- Disponemos de varios tipos de 'colliders', pero los que consumen menos CPU para realizar cálculos son los denominados Primitives Colliders:
- Esfera
- Caja
- Cápsula
- Otro aspecto a tener en cuenta es que los Collider están íntimamente relacionados con los RigidBody, ya que el RigidBody hace uso de los Collider como base para producir los 'efectos' físicos, como choques, rotaciones,...
Creando Colliders
- El Collider es un componente de un GameObject, por lo tanto sólo tenemos que escribir 'Collider' para ver todos los disponibles:
- El collider tiene un tamaño y una posición la cual puede ser modificada de forma gráfica:
En la ventana Inspector podemos cambiar la posición y tamaño del Collider. Al igual que en la componente Transform, si mantenemos presionado el botón izquierdo del ratón encima de cualquiera de dichas propiedades (de su etiqueta, es decir, sobre la letra X, Y,Z) y movemos a izquierda-derecha el ratón sin soltar el botón, los valores cambiarán.
Otra forma mucho más fácil de modificar su tamaño es presionando el icono indicado en la imagen. Al hacerlo aparecerán varios puntos (en el caso del cubo 6, uno por cada cara). Si ahora presionamos con el ratón cualquiera de dichos puntos y lo movemos, podemos cambiar gráficamente el tamaño del Collider.
La forma importa
- Como comenté antes, el Collider va a ser empleado tanto para determinar cuando dos GameObjects 'chocan' como para ser utilizad por el RigidBody para determinar el comportamiento físico del GameObject.
- Dependiendo de nuestras necesidades, el Collider se tendrá que ajustar de forma lo más precisa al GameObject o no.
- Por ejemplo, podría tener un Collider en forma de esfera con un tamaño más grande que el GameObject para determinar cuando un enemigo ataca al jugador. Sería su radio de acción.
- Sin embargo, a nivel de el comportamiento físico, por ejemplo, al caer, querremos que los Collider se ajusten lo más posible al GameObject.
- Veamos un ejemplo de esto segundo.
Descargamos de Unity si no lo hemos hecho antes algún prefab nuevo, por ejemplo estos coches.
- Existe un collider denominado Mesh Collider que se 'adapta' completamente a la forma del modelo, pero que lleva asociado un coste computacional elevado por lo que su uso debería estar limitado a situaciones muy concretas.
Atención al posicionamiento con colliders
- Cuando asociemos un Collider a un GameObject, Unity va a entender que la posición del GameObject estará marcada por el collider asociado y por lo tanto al pulsar sobre el GameObject, el indicador de posición será el del collider, no el del transform. Si por cualquier razón movemos el Collider con respecto a su modelo y este no se ajusta a su posición, Unity mostrará por defecto su Pivot (es decir, el punto intermedio entre la posición del GameObject (su transform.position) y la posición del Colllider. Independientemente del tipo de Collider que tengamos asociado.
- Esto puede dar lugar a confusiones.
- Veamos un ejemplo:
Lo anterior puede dar a lugar a confusiones. Por ejemplo, en esta pantalla se muestra un juego en el que un soldado lleva un arma. Dentro de la jerarquía hay un PuntoDisparo que lleva asociado un collider para detectar cuando tiene que disparar. Este collider está desplazado hacia abajo para hacer que la detección la realice una semi-esfera. Para Unity el punto de disparo está en el suelo cuando queremos que esté en la punta del arma. Será un fallo desplazar ahora la punto de disparo a la punta del arma.
Colliders y RigidBody
- Como comenté antes, los dos están íntimamente relacionados.
- Cuando añadimos un Collider sin un RigidBody asociado, realmente estamos haciendo uso de una especie de Static RigidBody.
- Este tipo de RigidBody está pensado para ser utilizado en escenarios en los que el GameObject no va a moverse ni a rotarse.
- Al collider que no tiene RigidBody asociado se denomina Static Collider. A los que sí tienen rigidbody asociado se denominan Dinamic Collider.
- Lo que hace Unity es 'juntar' todos los GameObjects con Collider sin RigidBody y va a calcular sus RigidBody/Collider en una sección de memoria separada la cual no va a tocar una vez calculado, ya que parte de que dichos GameObjects son estáticos.
- NOTA IMPORTANTE: Si asociáis un Collider sin RigidBody, el GameObject no debe moverse ni rotarse en la escena.
- Un Dynamic Collider seguirá chocando con un Static Collider, pero al chocar, el Static se mantendrá en su posición ya que es inamovible...
- Veamos un ejemplo:
Propiedad IsKinematic del RigidBody
- Cuando hablé del RigidBody en esta Wiki ya comenté la propiedad Is Kinematic.
- Lo que hace dicha propiedad si se activa es hacer que el GameObject no se vea afectado por la gravedad y por el motor de físicas, haciendo que los movimientos y rotaciones se gestionen a través de su componente Transform como ya se vio en esta Wiki.
- NOTA IMPORTANTE: Siempre que queramos utilizar GameObject con Collider para detectar colisiones, pero que no nos importa ni queremos hacer uso del motor de físicas, debemos de marcar la opción Is Kinematic'. Recordar que si no tenemos un RigidBody asociado, es como si lo tuviéramos creado, siendo este de tipo 'Static Collider'.
- Un uso de esta propiedad es cuando queramos que un objeto se 'mueva' temporalmente pero que normalmente se encuentre en un estado de 'quieto'.
- Por ejemplo, cuando en un juego abrimos una puerta.
- Veamos un ejemplo aplicado a una animación (las animaciones las vimos en este punto de la Wiki).
- Al marcar dicha opción, el objeto deja de estar sujeto a las propiedades del motor de físicas, pero los demás GameObjects que sean de tipo 'Dynamic RigidBody' seguirán chocando con él (Dynamic RigidBody son los que tenga un RigidBody asociado y que no tenga marcada la opción 'Is Kinematic').
- En el ejemplo que se muestra en la pantalla siguiente se puede ver como la puerta se mueve hacia la derecha haciendo uso de una animación en la que se ha activado la opción 'Is Kinematic' de su RigidBody. De esta forma se puede mover haciendo uso de su componente 'Transform'. El coche que está en su camina también tiene la propiedad 'Is Kinematic' activada por lo que la puerta lo puede 'atravesar'. Sin embargo, el otro coche que va hacia la puerta es un Dynamic RigidBody y por tanto va a 'tropezar' con la puerta y no va a poder pasar.
Detectando los choques en Scripts
- Ahora llega el momento de determinar haciendo uso de los scripts, cuando se produce el evento de que dos GameObjects han chocado (sus colliders).
- Para ello tenemos dos formas de detectarlos:
- Métodos onCollionXXXX => La propiedad isTrigger del Collider está a false
- Métodos onTriggerXXXX => La propiedad isTrigger del Collider está a true
- NOTA IMPORTANTE: Si el collider es del tipo isTrigger, dicho collider atravesará a otros colliders, por lo tanto no chocarán físicamente.
Métodos OnCollision
- Para simplificar vamos a suponer que los GameObjects que están colisionando tienen un 'Static RigidBody' o un 'Dynamic RigidBody'.
- Vamos a hacer uso de dichos métodos cuando estemos trabajando con GameObjects a los cuales les afecte la gravedad y el motor de físicas.
- Disponemos de tres métodos:
- OnCollisionEnter(Collision collision): Entra en dicho método cuando un Collider toca a otro por primera vez.
- OnCollisionStay(Collision collision): Entra en dicho método mientras un Collider toca a otro.
- OnCollisionExit(Collision collision): Entra en dicho método cuando un Collider deja de tocar a otro.
- Como dato adicional indicar que el tipo de dato del parámetro es un objeto de la clase Collision.
- Gracias a este objeto podemos saber:
- El collider con el que chocamos (el del otro GameObject). Por lo tanto podremos acceder a la información del Collider.
- El número y puntos de contacto con los que hemos chocado.
- El gameobject con que el hemos chocado.
- El componente RigidBody del gamobject con el que hemos chocado.
- El componente Transform del gamobject con el que hemos chocado.
- Otras propiedades relativas al impulso y velocidad de impacto.
- Veamos varios ejemplos de uso.
- Creamos el siguiente Script de nombre: UD3_Collider_DetectandoCollision
1 using UnityEngine;
2
3 public class UD3_Collider_DetectandoCollision : MonoBehaviour {
4
5
6 private void OnCollisionEnter(Collision collision)
7 {
8 Debug.Log("El " + gameObject.name + " colicionó con el gamobject " + collision.gameObject.name);
9 }
10
11 }
- Asociamos dicho script a una Shpere con un Collider asociado y hacemos que un camión con un Collider - RigidBody asociado vaya hacia la esfera.
- Podemos comprobar como la esfera detecta el choque con el camión.
- Si cambiamos la esfera a Dynamic RigidBody (asociando un RigidBody a la esfera) y desmarcamos la opción de 'Gravedad' para que no se vea afectada por la misma, podemos ver como la esfera se desplaza empujada por el camión y seguimos detectando el choque.
- Si cambiamos la esfera e indicamos que es 'Is Kinematic' recordar que ahora la esfera tendría que moverse haciendo uso de su Transform pero seguiría influyendo (chocando) con los GameObject que fueran Static RigidBody y Dynamic RigidBody.
- Cambiemos ahora el script a añadimos el resto de eventos:
1 using UnityEngine;
2
3 public class UD3_Collider_DetectandoCollision : MonoBehaviour {
4
5
6 private void OnCollisionEnter(Collision collision)
7 {
8 Debug.Log("El " + gameObject.name + " colicionó con el gamobject " + collision.gameObject.name);
9 }
10 private void OnCollisionStay(Collision collision)
11 {
12 Debug.Log("El " + gameObject.name + " está colisionando con el gamobject " + collision.gameObject.name);
13 }
14 private void OnCollisionExit(Collision collision)
15 {
16 Debug.Log("El " + gameObject.name + " dejó de colisionar con el gamobject " + collision.gameObject.name);
17 }
18
19 }
- Si ahora cambiamos la posición de la esfera para que el camión 'la roce' llegará un momento en que dejará de tener contacto al desplazarla.
- Recordar desmarcar la opción 'Is Kinematic' de la esfera:
- Cuando dos GameObjects son Is Kinematic no van a detectar las colisiones con estos métodos y al menos uno de ellos tendría que hacer uso de OnTriggerXXXX que veremos a continuación...
Métodos OnTrigger
- Haremos uso de ellos en los siguientes casos:
- Cuando queramos detectar choques entre dos GameObjects marcados como 'Is Kinematic' temporalmente (debido a que se están moviendo da la forma tradicional, con el componente Transform).
- Cuando tengamos juegos en que no nos interese el motor de físicas y sólo queramos hacer uso de los Collider para detectar las colisiones. En este caso, también marcaremos la opción 'Is Kinematic' en su RigidBody.
- Cuando queremos emplear los collider como formas de 'detectar' a otros gameobjects para provocar un comportamiento en nuestro gameobject. Por ejemplo, puedo hacer uso de un collider aplicado a un enemigo para detectar cuando el protagonista está cerca e iniciar el ataque.
- Para poder utilizarlos, debemos marcar la opción Is Trigger en el componente Collider.
- NOTA IMPORTANTE: Es necesario tener un RigidBody asociado al GameObject para que llame a los métodos OnTriggerXXX.
- Al marcar la opción 'Is Trigger' el collider del GameObject deja de 'chocar' con el resto de GameObjects y por tanto no va a afectarles a nivel físico de ninguna forma.
- Si el RigidBody asociado no es de tipo 'Is Kinematic' el GameObject se verá afectado por la gravedad y se tendría que mover aplicando fuerzas a través de su RigidBody.
- Si queremos moverlo de forma tradicional (con su Transform), seleccionaremos la opción 'Is Kinematic' en su RigidBody.
- Disponemos de tres métodos:
- OnTriggerEnter(Collider other): Entra en dicho método cuando un Collider toca a otro por primera vez.
- OnTriggerStay(Collider other): Entra en dicho método mientras un Collider toca a otro.
- OnTriggerExit(Collider other): Entra en dicho método cuando un Collider deja de tocar a otro.
- Como dato adicional indicar que el tipo de dato del parámetro es un objeto de la clase Collider (en los otros era de la clase Collision)
- Podéis consultar de que forman se envían los eventos (a qué métodos de los vistos) en función del tipo de Collider-RigidBody:
Parar de moverse al chocar
- Si estamos utilizando el motor de físicas, este ya se encarga de gestionar el 'choque' entre diferentes GameObjects, impidiendo que unos pasen a través de otros.
- Sin embargo, al activar la opción Is trigger esto no sucede.
- Normalmente la bala o el enemigo desaparecerá al 'chocar' entre ellos, pero puede darse el caso de que no, o que choquemos con los bordes del escenario del juego o con elementos que queramos que sean 'fijos'.
- Para solucionarlo tendremos que hacer uso de la programación.
- Para hacerlo tendremos que guardar la posición del GameObject antes de moverse, de tal forma que al detectar el choque, situaremos al GameObject en la posición guardada.
- En este ejemplo para a comprobar como podemos 'implementar' un sistema de choques sin hacer uso del motor de físicas.
- Lo que vamos a hacer es que cuando se detecte el choque, moveremos al gameobject a la posición anterior del choque.
- Veamos ahora el código del script, que se encargará de mover el coche rojo con las teclas e impedirá que pase a través del coche azul.
1 using UnityEngine;
2
3 public class DetectarChoqueTrigger : MonoBehaviour {
4
5
6 private Vector3 posAnterior;
7 private bool choque = false;
8
9 [SerializeField]
10 public float velocidadRotacion;
11 [SerializeField]
12 public float velocidadTraslacion;
13
14 // Update is called once per frame
15 void Update()
16 {
17 if (choque) return;
18
19 posAnterior = transform.position;
20
21 if (Input.GetKey(KeyCode.A))
22 {
23 transform.Rotate(0, -velocidadRotacion * Time.deltaTime, 0);
24 }
25 if (Input.GetKey(KeyCode.D))
26 {
27 transform.Rotate(0, velocidadRotacion * Time.deltaTime, 0);
28 }
29 if (Input.GetKey(KeyCode.W))
30 {
31 transform.Translate(Vector3.forward * velocidadTraslacion * Time.deltaTime, Space.Self);
32 }
33 if (Input.GetKey(KeyCode.X))
34 {
35 transform.Translate(-Vector3.forward * velocidadTraslacion * Time.deltaTime, Space.Self);
36 }
37
38 }
39
40 private void OnTriggerEnter(Collider other)
41 {
42 choque = true;
43 }
44
45 private void OnTriggerExit(Collider other)
46 {
47 choque = false;
48 }
49
50 private void OnTriggerStay(Collider other)
51 {
52 transform.position = posAnterior;
53 }
54
55 }
- Línea 19: Guardamos la posición anterior antes de mover el gameobject.
- Líneas 50-53: Mientras detecta una colisión mueve el gameobject a la posición anterior a la misma.
Detección de GameObjects
- Comentamos antes, este tipo de Collider lo podemos emplear para 'detectar' otros GameObjects.
- Esto se puede emplear tanto si hacemos uso del motor de físicas como sino lo utilizamos.
- La idea es la de utilizar el Collider como un área para detectar otros GameObjects.
Preparamos la escena, que va a ser muy parecida a la anterior. El coche azul tendrá los siguientes componentes. Un BoxCollider que representa el cuerpo del coche y que será utilizado por el motor de físicas para los choques. Un RigidBody 'normal', que hace uso de la gravedad. Un componente Constant Force deshabilitado con una fuerza en el eje-Z del gameobject. Una shpere collider de tipo Is Trigger para que no se vea afectado por otros colliders. Esta esfera es la que vamos a emplear para detectar al otro coche. Un script que se encargará de activar el componente 'Constant Force' cuando detecte el coche rojo.
- El código del script asociado al coche azul es el siguiente:
1 using UnityEngine;
2
3 public class UD2_Ej8_DetectarChoqueTrigger : MonoBehaviour {
4
5
6
7 private void OnTriggerEnter(Collider other)
8 {
9 if (other.gameObject.name == "JeepRojo")
10 {
11 ConstantForce cf = GetComponent<ConstantForce>();
12 cf.enabled = true;
13 }
14 }
15
16 }
- Línea 7: Fijarse que el método asociado al evento es OnTriggerEnter ya que el collider es del tipo 'Is Trigger'.
- Línea 9: Para saber que GameObject ha chocado podemos buscar por su nombre, pero normalmente tendremos un Tag asociado a los GameObjects y la condición la haríamos así:
- if (other.gameObject.CompareTag("Player")) // En este caso detectaríamos cuando el protagonista (con tag='Player' entra dentro del campo de acción).
- Líneas 11-12: Obtengo la referencia al componente ConstantForce del GameObject y lo activo.
Resumen de opciones IsKinematic-IsTrigger
Partimos de dos objetos con RigidBody y Collider asociados.
Matriz de colisión
- Más información en https://docs.unity3d.com/Manual/LayerBasedCollision.html
- Para optimizar colisiones podemos emplear los Layers que ya nombramos cuando hablamos de la cámara en Perspectiva.
- Podemos indicar mediante una matriz que GameObjects pertenecientes a un Layer van a 'chocar' con los GameObjects de otro Layer.
- La matriz de colisiones se encuentra en pulsando el menú principal de Unity Edit => Project Settings => Physics
- Nota: La imagen puede variar dependiendo de los Layers creados.
- Veamos su uso en un ejemplo.
- Supongamos el siguiente escenario compuesto por 4 coches:
- Todos los coches tienen un rigidbody y un collider (o dos, parte delantera y parte trasera) asociado.
- Tienen asociado un componente 'Constant Force', aplicando una fuerza sobre el eje Z con respecto a cada coche (es decir, empleando el eje rotado de cada coche).
- Nota: Comentar que he añadido un Empty GameObject a cada coche y los he rotado, para que el eje Z del mundo coincida con la parte delantera del coche.
- Si ejecutamos el juego veremos que los cuatro coches chocan entre sí:
- Vamos a realizar los pasos necesarios para que los coches de cada color (rojos-rojos / verdes-verdes) no choquen entre sí.
Primero creamos dos Layers, una para cada tipo de coche. Recordar que ya vimos el manejo de Layers anteriormente en esta Wiki.
- Nota Importante: Debemos de tener en cuenta que si dos Layers no están conectadas por la matriz, los objetos que pertenezcan a dichos Layers no chocarán y no se detectará ningún tipo de choque, ni OnTrigger ni OnCollider.
Colliders especiales
- Wheel Collider: Pensado específicamente para simular el movimiento y leyes físicas que afectan a las ruedas de los coches cuando se mueven.
- Vídeo de ejemplo: https://www.youtube.com/watch?v=j6_SMdWeGFI
- Terrain Collider: Collider empleado por el motor de terrenos.
- Aunque no es un Collider está relacionado en cierta manera con ellos.
- Es un control que se asocia a un personaje e incorpora su propio 'Collider' que le permite 'chocar' contra otros collider, pero no va a influir en ellos, ya que no se rige por un RigidBody (de hecho no tiene).
- El objetivo de dicho control es utilizar en juegos en primera o tercera persona como el Doom, en el que el personaje se desplaza grandes distancias en poco tiempo. Desde el punto de vista 'físico' no podría ser (por eso no debemos de hacer uso de un RigidBody).
- De esta forma, con este control, vamos a poder desplazarnos sin seguir las reglas de la física pero seguiremos 'chocando' con otros cuerpos con RigidBody (static o dynamic).
Enlace a la página principal del curso
-- Ángel D. Fernández González -- (2018).