What if a game object does not have a collider?

Unity: Guía Maestra de Colisiones y Raycast

18/12/2019

Valoración: 4.31 (11551 votos)

La detección de colisiones es uno de los pilares fundamentales en el desarrollo de videojuegos. Es la mecánica que permite que nuestro mundo virtual se sienta real y reactivo. Desde un personaje que recoge una moneda hasta un proyectil que impacta en un enemigo, todo depende de un sistema de colisiones robusto. En Unity, contamos con varias herramientas poderosas para gestionar estas interacciones, cada una con sus propias fortalezas y casos de uso. En este artículo exhaustivo, exploraremos a fondo los métodos más comunes: los colliders estándar, los triggers (disparadores) y el versátil sistema de Raycasting. Prepárate para desentrañar los secretos de las físicas de Unity y llevar tus proyectos al siguiente nivel.

How do you know if a ray hit a collider?
The distance from the ray's origin to the impact point. The uv lightmap coordinate at the impact point. The normal of the surface the ray hit. The impact point in world space where the ray hit the collider. The Rigidbody of the collider that was hit. If the collider is not attached to a rigidbody then it is null.
Índice de Contenido

Fundamentos de la Colisión: Colliders y Rigidbodies

Antes de sumergirnos en el código, es crucial entender los dos componentes principales que trabajan en conjunto para crear las colisiones: el Collider y el Rigidbody.

  • Collider: Es un componente que define la forma de un objeto para las colisiones físicas. No tiene por qué ser la misma forma que la malla visible del objeto. Unity ofrece una variedad de formas primitivas (Box, Sphere, Capsule Collider) y formas más complejas (Mesh Collider). Un objeto con solo un Collider es considerado un objeto estático en el motor de físicas.
  • Rigidbody: Este componente le indica a Unity que debe tratar al objeto como un cuerpo físico dinámico. Al añadir un Rigidbody, el objeto comenzará a ser afectado por la gravedad, la fuerza y otras interacciones físicas. Para que los eventos de colisión más comunes (como OnCollisionEnter) se disparen, al menos uno de los dos objetos que interactúan debe tener un componente Rigidbody.

La regla de oro es: para colisiones dinámicas donde se involucran fuerzas y movimientos realistas, necesitas Rigidbodies. Para objetos estáticos que solo necesitan delimitar un espacio (como paredes o el suelo), un Collider es suficiente.

Los Eventos de Colisión Clásicos

Cuando dos colliders (con al menos un Rigidbody entre ellos) chocan, Unity llama a una serie de funciones especiales en cualquier script que esté adjunto a esos objetos. Estas funciones nos permiten reaccionar al evento.

How do I detect hits per Collider?
Add a script to each of those empties and use the OnCollisionEnter/OnTriggerEnter stuff to detect hits per collider. Or just give each of those colliders a different Tag and compare that using compareTag. Option B: You create public/serialized variables and drag the colliders in the slots (inspector).
  • OnCollisionEnter(Collision collision): Se llama una sola vez, en el primer fotograma en que los dos colliders se tocan.
  • OnCollisionStay(Collision collision): Se llama en cada fotograma mientras los dos colliders permanezcan en contacto.
  • OnCollisionExit(Collision collision): Se llama una sola vez, en el fotograma en que los dos colliders dejan de tocarse.

El parámetro Collision que reciben estas funciones es muy importante, ya que contiene toda la información sobre el choque, como el otro objeto involucrado, los puntos de contacto y la velocidad relativa.

Veamos un ejemplo práctico:

using UnityEngine; public class DetectorDeImpactos: MonoBehaviour { // Esta función se llama cuando este collider/rigidbody ha comenzado a tocar otro rigidbody/collider. void OnCollisionEnter(Collision collision) { // Imprimimos en la consola el nombre del GameObject con el que colisionamos. Debug.Log("¡Colisión detectada con: " + collision.collider.name); } // Esta función se llama una vez por fotograma por cada collider/rigidbody que esté tocando este rigidbody/collider. void OnCollisionStay(Collision collision) { // Comprobamos si el objeto con el que seguimos colisionando tiene un nombre específico. if (collision.collider.name == "CofreDelTesoro") { Debug.Log("¡Todavía estás tocando el cofre!"); } } }

¿Colisión o Disparador? Entendiendo los Triggers

A veces no queremos una colisión física sólida, sino simplemente saber cuándo un objeto entra en una zona determinada. Imagina un área que activa una trampa, una zona de curación o la entrada a una nueva habitación. Para estos casos, usamos los Triggers.

Un Collider se convierte en un Trigger simplemente marcando la casilla "Is Trigger" en su componente en el Inspector. Cuando un Collider es un Trigger, no provoca una colisión física (los objetos pueden atravesarlo), pero sigue detectando la superposición con otros colliders. Esto dispara un conjunto diferente de eventos:

  • OnTriggerEnter(Collider other): Se llama cuando otro Collider entra en el Trigger.
  • OnTriggerStay(Collider other): Se llama en cada fotograma mientras el otro Collider permanece dentro del Trigger.
  • OnTriggerExit(Collider other): Se llama cuando el otro Collider sale del Trigger.

Fíjate que el parámetro aquí es Collider other, dándonos una referencia directa al collider que entró en nuestra zona, en lugar del objeto de colisión más complejo.

Can I use a collider to detect a collision?
For this purpose, you can use (and monitor) colliders. However, the way you manage collisions, and the methods to be called in this case, will highly depend on the type of colliders used (for example, colliders or triggers) as well as the object from which you want to detect the collision (for example, simple objects or character controllers).
using UnityEngine; public class ZonaMagica: MonoBehaviour { // Para que este script funcione, el GameObject debe tener un Collider con "Is Trigger" activado. // Además, el objeto que entra en la zona debe tener un Rigidbody. void OnTriggerEnter(Collider otherObject) { // Imprimimos un mensaje cuando un objeto entra en nuestra zona. Debug.Log("El objeto '" + otherObject.gameObject.name + "' ha entrado en la zona mágica."); } void OnTriggerExit(Collider otherObject) { // Y otro mensaje cuando sale. Debug.Log("El objeto '" + otherObject.gameObject.name + "' ha salido de la zona mágica."); } }

Raycasting: Lanzando Rayos para Detectar Objetos a Distancia

El Raycasting es una técnica increíblemente poderosa y eficiente. Consiste en lanzar un rayo invisible desde un punto de origen, en una dirección específica, y comprobar si choca con algún collider en su camino. Es ideal para cosas como la línea de visión de un enemigo, disparos de armas (hitscan), seleccionar objetos con el ratón o verificar si hay suelo debajo de un personaje.

Lanzando un Rayo desde la Cámara (Estilo FPS)

En un juego de disparos en primera persona (FPS), a menudo queremos saber qué hay exactamente en el centro de la pantalla. Podemos lanzar un rayo desde la cámara hacia adelante.

using UnityEngine; public class VisionFPS: MonoBehaviour { public Camera camaraDelJugador; void Update() { // Creamos un rayo que se origina en el centro de la vista de la cámara. Ray rayo = camaraDelJugador.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2, 0)); RaycastHit hitInfo; // Variable para almacenar la información del impacto. // Dibujamos el rayo en la vista de escena para poder depurar (no es visible en el juego). Debug.DrawRay(rayo.origin, rayo.direction * 100, Color.yellow); // Lanzamos el rayo. Si impacta con algo, la función devuelve true. if (Physics.Raycast(rayo, out hitInfo, 100)) { // Si el rayo golpea un collider, imprimimos el nombre del objeto impactado. Debug.Log("El jugador está mirando a: " + hitInfo.collider.gameObject.name); } } }

La Información del Impacto: `RaycastHit`

Cuando un `Physics.Raycast` tiene éxito, nos devuelve un montón de información útil a través de la variable `RaycastHit`. Es crucial conocer sus propiedades para sacar el máximo provecho de esta técnica.

How to find all colliders that were hit in detail?
To find all colliders that were hit in detail, you have to iterate the contact points (contacts property). For each contact, you can access the GameObject and Collider that were involved in the collision. In the example, the name of the colliding GameObject is output to the console, and it also checks the name of the Collider.

Propiedades Clave de RaycastHit

PropiedadDescripción
pointEl punto exacto en el espacio 3D (coordenadas del mundo) donde el rayo impactó con el collider.
normalUn vector que representa la normal de la superficie en el punto de impacto (es decir, la dirección perpendicular a la cara del polígono que fue golpeada).
distanceLa distancia desde el origen del rayo hasta el punto de impacto.
colliderUna referencia directa al componente Collider que fue golpeado por el rayo.
rigidbodyEl componente Rigidbody asociado al collider que fue golpeado. Será nulo si el objeto no tiene un Rigidbody.
transformEl Transform del objeto que fue golpeado. Es un atajo para acceder a hit.collider.transform.
textureCoordLas coordenadas UV de la textura principal en el punto de impacto. Útil para efectos visuales como calcomanías de impacto.

Escenarios Avanzados y Solución de Problemas

A medida que los juegos se vuelven más complejos, también lo hacen nuestros requisitos de colisión.

Manejo de Múltiples Colliders en un Objeto

Imagina un personaje enemigo que tiene diferentes zonas de daño: cabeza, torso y piernas. Un solo collider no es suficiente. ¿Cómo diferenciamos un disparo a la cabeza de uno al cuerpo?

  • Opción A: Objetos Hijos y Tags. Puedes crear GameObjects vacíos como hijos del personaje principal (ej. "Cabeza", "Torso"). A cada uno le asignas un Collider (una esfera para la cabeza, una cápsula para el torso) y un Tag único (ej. "HeadshotZone", "BodyZone"). Luego, en tu código de detección de impacto, simplemente compruebas el tag del collider golpeado: if (hit.collider.CompareTag("HeadshotZone")) { ... }.
  • Opción B: Referencias Directas. En el script principal del enemigo, puedes declarar variables públicas para cada collider específico y arrastrarlos desde el editor. Esto crea una referencia directa y te permite comparar directamente en el código, lo que a menudo es más rápido que comparar strings (tags).
public class EnemigoComplejo: MonoBehaviour { public SphereCollider cabezaCollider; public CapsuleCollider cuerpoCollider; public void RecibirImpacto(Collider colliderGolpeado) { if (colliderGolpeado == cabezaCollider) { Debug.Log("¡Disparo a la cabeza! Daño crítico."); // Aplicar daño x3 } else if (colliderGolpeado == cuerpoCollider) { Debug.Log("Impacto en el cuerpo. Daño normal."); // Aplicar daño x1 } } } // En el script del arma: if (Physics.Raycast(rayo, out hitInfo, 100)) { EnemigoComplejo enemigo = hitInfo.collider.GetComponentInParent<EnemigoComplejo>(); if (enemigo != null) { enemigo.RecibirImpacto(hitInfo.collider); } }

El Problema Común: `NullReferenceException` con Raycast

Un error muy frecuente al usar `Linecast` o `Raycast` es intentar acceder a la información del `hit` sin comprobar primero si el rayo realmente golpeó algo. Si el rayo no impacta en ningún collider dentro de su rango, la función `Physics.Raycast` devuelve `false` y la variable `hitInfo` no se inicializa. Cualquier intento de acceder a `hitInfo.collider` resultará en un error `NullReferenceException`.

La solución es siempre, sin excepción, meter el código que procesa el impacto dentro de un bloque `if`:

Incorrecto:

Physics.Raycast(origen, direccion, out hit, distancia); // ¡ERROR! ¿Y si el rayo no golpeó nada? hit.collider será nulo. if(hit.collider.gameObject == objetoBuscado) { ... }

Correcto:

// Primero, lanzamos el rayo y comprobamos si tuvo éxito. if(Physics.Raycast(origen, direccion, out hit, distancia)) { // Solo si el rayo golpeó algo, podemos acceder a hit.collider de forma segura. if(hit.collider.gameObject == objetoBuscado) { // Lógica del impacto } }

Tabla Comparativa: ¿Qué Método Usar?

MétodoCaso de Uso PrincipalVentajasDesventajas
Colliders (OnCollision)Interacciones físicas realistas: objetos que rebotan, se apilan, ruedan.Manejado por el motor de físicas, simulación realista, proporciona información detallada de la colisión.Más costoso en rendimiento. Requiere al menos un Rigidbody.
Triggers (OnTrigger)Detección de zonas: áreas de activación, recogida de ítems, puntos de control.Muy ligero en rendimiento, no interfiere con el movimiento de los objetos.No proporciona información física detallada, solo detecta superposición.
Raycasting (Physics.Raycast)Detección a distancia y precisa: disparos, línea de visión, selección de objetos.Control total sobre el origen, dirección y distancia. Muy eficiente y versátil.Solo detecta el primer objeto en su camino (las versiones 'All' son más costosas). Representa una línea infinitesimal, no un volumen.

Preguntas Frecuentes (FAQ)

P1: ¿Por qué mi `OnCollisionEnter` no se activa?

R: Las causas más comunes son: 1) Ninguno de los dos objetos tiene un componente Rigidbody. 2) Uno de los colliders está marcado como 'Is Trigger'. 3) Los objetos están en capas (Layers) que están configuradas para no interactuar entre sí en la Matriz de Colisiones de Físicas (Physics Collision Matrix). 4) El script no está en el mismo GameObject que el Collider/Rigidbody.

How do you know if a ray hit a collider?
The distance from the ray's origin to the impact point. The uv lightmap coordinate at the impact point. The normal of the surface the ray hit. The impact point in world space where the ray hit the collider. The Rigidbody of the collider that was hit. If the collider is not attached to a rigidbody then it is null.

P2: ¿Cuál es la diferencia entre `hit.transform` y `hit.collider.transform` en un RaycastHit?

R: En la mayoría de los casos son lo mismo. Sin embargo, si un Rigidbody tiene varios colliders en objetos hijos, `hit.transform` se referirá al Transform del objeto con el Rigidbody, mientras que `hit.collider.transform` se referirá al Transform del objeto hijo específico que contiene el collider que fue golpeado.

P3: ¿Es el Raycasting costoso para el rendimiento?

R: Un solo Raycast es extremadamente rápido. Sin embargo, el coste puede acumularse si realizas cientos de ellos en cada fotograma, especialmente si tienen una distancia muy larga. Para optimizar, siempre limita la distancia del rayo al mínimo necesario y utiliza LayerMasks para que el rayo solo compruebe las capas de objetos que te interesan, ignorando el resto.

Si quieres conocer otros artículos parecidos a Unity: Guía Maestra de Colisiones y Raycast puedes visitar la categoría Juegos.

Subir