El problema#
Un enemigo que persigue al jugador en cuanto está cerca (solo por distancia) resulta tonto: te ve a través de las paredes y por la espalda. Una percepción creíble combina tres comprobaciones: rango, ángulo (cono de visión) y línea de vista (que no haya nada en medio).
- ¿Está suficientemente cerca? (rango)
- ¿Está dentro del cono de visión, no a mi espalda? (ángulo)
- ¿Hay algo tapándolo entre él y yo? (línea de vista)
Solo si las tres son "sí", el enemigo ve al jugador.
La implementación#
using UnityEngine;
public class VisionEnemigo : MonoBehaviour
{
[SerializeField] private float radio = 12f;
[SerializeField, Range(0, 360)] private float angulo = 90f; // apertura del cono
[SerializeField] private LayerMask capaJugador;
[SerializeField] private LayerMask capaObstaculos;
[SerializeField] private Transform ojos; // desde dónde "mira" (la cabeza)
public Transform JugadorVisible { get; private set; }
public bool PuedeVerAlJugador()
{
// 1) RANGO: ¿hay un jugador dentro del radio?
Collider[] enRango = Physics.OverlapSphere(ojos.position, radio, capaJugador);
if (enRango.Length == 0) { JugadorVisible = null; return false; }
Transform objetivo = enRango[0].transform;
Vector3 dir = (objetivo.position - ojos.position).normalized;
// 2) ÁNGULO: ¿está dentro del cono de visión?
if (Vector3.Angle(ojos.forward, dir) > angulo / 2f)
{
JugadorVisible = null;
return false;
}
// 3) LÍNEA DE VISTA: ¿hay un obstáculo en medio?
float distancia = Vector3.Distance(ojos.position, objetivo.position);
if (Physics.Raycast(ojos.position, dir, distancia, capaObstaculos))
{
JugadorVisible = null;
return false; // algo tapa al jugador
}
JugadorVisible = objetivo;
return true;
}
}angulo es la apertura total del cono. Vector3.Angle mide desde el centro (el forward) hacia el objetivo, así que comparamos con la mitad. Un cono de 90° significa 45° a cada lado.
Dibujar el cono en el editor#
Ver el cono en la vista Scene ayuda muchísimo a ajustarlo. Con Gizmos (editor scripting):
void OnDrawGizmosSelected()
{
if (ojos == null) return;
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(ojos.position, radio);
Vector3 izq = Quaternion.Euler(0, -angulo / 2f, 0) * ojos.forward;
Vector3 der = Quaternion.Euler(0, angulo / 2f, 0) * ojos.forward;
Gizmos.color = Color.red;
Gizmos.DrawRay(ojos.position, izq * radio);
Gizmos.DrawRay(ojos.position, der * radio);
}Conectarlo con el comportamiento#
La percepción decide; el comportamiento actúa. Combínala con una máquina de estados: el enemigo patrulla hasta que PuedeVerAlJugador() es true, entonces pasa a Perseguir (con NavMesh). Al perderlo de vista, tras unos segundos, vuelve a patrullar por su última posición conocida.
Lanzar la comprobación completa (con su raycast) cada fotograma para muchos enemigos es caro. Hazlo cada 0,2 s con una corrutina o un temporizador: la IA no necesita "ver" 60 veces por segundo, y el ahorro es notable con muchos enemigos.
Mejoras habituales#
- Oído: además de ver, reaccionar a ruidos (disparos, pasos) con un radio de sonido.
- Memoria: recordar la última posición vista y buscar ahí antes de rendirse.
- Detección gradual: una barra que se llena mientras te ve (como en los juegos de sigilo) en vez de detección instantánea.
- Percepción por capas: distinta visión de día/noche, o reducida entre humo.
Errores frecuentes#
- Detectar solo por distancia (ve a través de paredes y por la espalda).
- Olvidar el raycast de línea de vista (la parte que da credibilidad).
- Comparar con el ángulo completo en vez de la mitad.
- Lanzar la comprobación cada frame para decenas de enemigos (coste innecesario).
- Poner los "ojos" en los pies del enemigo y que un escalón le tape la visión.
Ponte a prueba#
Resumen#
Una percepción creíble combina rango (OverlapSphere), ángulo de un cono de visión (Vector3.Angle contra la mitad de la apertura) y línea de vista (Raycast a obstáculos, para no ver a través de paredes). Dibuja el cono con Gizmos, comprueba cada ~0,2 s en vez de cada frame, y conéctala a una máquina de estados + NavMesh para patrullar, perseguir y buscar.