En este post voy a mostrar cómo se usa el NavMesh de Unity3D. Esta es una de las ramas de la inteligencia artificial, que incluye búsqueda de caminos y un comportamiento grupal, al hacer que uno o más agentes (personajes), puedan llegar de un punto A hasta un punto B esquivando obstáculos e incluso evitarse entre ellos. Vamos a ver también que este componente de Unity puede tener en cuenta el uso de portales y fragmentos de escenarios que no están conectados, para los cuales se necesita saltar.

CONFIGURANDO EL NAVMESH

En este post voy a explicar cómo funciona el NavMesh de Unity3D. Voy a explicarlo con las mismas imagenes que usan en su página web, con las respectivas traducciones.

Para empezar, dentro del editor, vamos a “Windows -> Navigation” para que se muestre la ventana de navegación.

El siguiente paso será seleccionar todos los objetos que representan la parte caminable del escenario. No importa si están demasiado o poco distantes entre sí, o si hay pozos en el medio (de eso nos encargaremos más adelante). Veremos que hay 3 pestañas. Vamos una por una. En la primera ajustamos los seteos del mesh seleccionado:

 

 

En la segunda pestaña, “Bake“, configuramos algunas cosas como para que el personaje camine mejor por nuestro escenario.

 

 

Con estas opciones simples ya queda bakeado el camino para nuestro personaje. Pero antes de largarnos de una con toda la parte de código, vamos a ver que también podemos modificar algunas opciones para que el movimiento sea más preciso.

Veamos un poco las opciones avanzadas:

 

 

Se puede configurar manualmente el área en voxels. Sin entrar en tanto detalle, un valor más chico haría que el NavMesh use módulos más chicos, por ende más precisos, para detectar caminos y mover a los agentes. La contra de esto es que consume más memoria y el proceso de bake tarda más. En resumen:

MIENTRAS MÁS CHICO SEA EL VALOR ======> MÁS MEMORIA, MÁS PRECISIÓN.

MIENTRAS MÁS GRANDE SEA EL VALOR ====> MENOS MEMORIA, MENOS PRECISIÓN.

O sea que la precisión que queramos es directamente proporcional a la cantidad de memoria que queramos consumir. Conviene que este valor sea más chico en aquellos niveles en los que hay muchos rincones y se requiere mucha precisión para pasar.

 

PREPARANDO A NUESTRO PERSONAJE

Ahora es el turno de configurar a nuestro personaje. Por empezar, a lo que sea que va a representar a nuestro personaje le agregamos un componente llamado “Nav Mesh Agent“. Este componente va a hacer que pueda moverse por toda la parte que configuramos anteriormente, que detecte a otros personajes, otros obstáculos y que sepa por dónde no debe pasar.

 

 

Vamos a detallar cada una de las partes que contiene este personaje. Las fundamentales para el movimiento son las marcadas en rojo. El resto es para darle más detalle o hacer que se mueva en algún tipo de situaciones o casos particulares:

Agent Size

  • Radius: Indica el radio de ESTE agente. Nótese que se ve una imagen guía similar a la de los colliders para poder editarla de manera más cómoda. Esto va a representar al personaje en sí para que el NavMesh sepa cómo calcular el camino para él exclusivamente. Esto también va a definir qué tan cerca o lejos pasa de los obstáculos.
  • Height: Representa la altura del personaje dentro del NavMesh. También se usa para calcular por dónde puede y no puede pasar el personaje. Si es demasiado alto el sistema va a elegir pasar por lugares que tengan un mínimo de altura igual a la del personaje.
  • Base Offset: Al modificar este valor lo que vamos a hacer es cambiar la posición en el eje Y del personaje dentro de nuestro NavMesh. Esto es útil para cuando el personaje tiene el pivot en cualquier lado, decirle a Unity que los pies están más abajo o más arriba.

Steering

  • Speed: Velocidad máxima con la que el personaje se va a mover de un punto a otro.
  • Angular Speed: Define la velocidad con la que el agente va a doblar para mirar hacia el nodo destino.
  • Acceleration: Aceleración/Desaceleración que va a tener el personaje para llegara su velocidad máxima. También la usa para girar, así que este valor va a definir qué tan bien gira en las curvas.
  • Stopping Distance: Distancia a la que frena el personaje con respecto al punto de destino. Esto se usa mucho cuando se mueven múltiples personajes a un mismo punto, o en casos en los que se tiene un enemigo o un aliado que nos va siguiendo, para que no se ponga encima nuestro.
  • Auto Braking: Especifíca si el personaje frena automáticamente al llegar al objetivo. Esto se usa si querémos que el mismo no se pase luego de llegar, sino que el último paso que da sea EXACTAMENTE al punto de destino.

Obstacle Avoidance

  • Quality: Especifíca la calidad de esquive que van a tener los agentes. Si se tienen muchos personajes, esto va a consumir mucho en los valores más altos, ya que va a tratar de ser lo más “perfecto” posible el esquive. Si se desactiva, cada agente va a esquivar los obstáculos que encontró al setear el destino, pero no va a evitar a otros agentes en movimiento ni otros obstáculos. Quizás lo mejor podría ser variar este valor en cada situación en concreto dentro del juego.
  • Priority: Define la prioridad de ESTE agente dentro del NavMesh. Cuando este se mueva va a ignorar a los agentes de menor prioridad al detectar obstáculos. Se pone un valor entre 0 y 99, donde 0 es la máxima prioridad (contrario a lo que parece).

Path Finding

  • Auto Traverse OffMesh Link: Esto define si el personaje pasa automáticamente por los pozos y/o portales. Mayormente debería estar desactivado, ya que la forma en la que lo hace es como si fuera un camino más y en los juegos quedaría mal (más adelante explico cómo hacer que salte).
  • Auto Repath: Si se activa, el agente va a intentar buscar otra ruta, cuando llegue a una parte en la cual no haya más camino. Si no puede, va a llegar al lugar más cercano.
  • Area Mask: Define el tipo de área que va a considerar este personaje para pasar. Se pueden definir distintos tipos de áreas, y cada personaje puede considerar diferentes. Por ejemplo, al hacer un plan de búsqueda, la mitad podría buscar un camino hacia el personaje por las escaleras y la otra por el ascensor.

EMPEZANDO CON EL CÓDIGO

Habiendo configurado lo básico de nuestro personaje y el NavMesh, vamos a moverlo de un punto al otro. Imaginemos que tenemos un script en el personaje (supongamos que ya tiene puesto el componente NavMeshAgent):

 

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

public class Character : MonoBehaviour
{
    //Acá guardamos el componente del agente.
    public NavMeshAgent agent;

  //Se ejecuta al inicializar...
  void Start ()
  {
        //Obtenemos el componente.
        agent = this.GetComponent<NavMeshAgent>();

        //Le damos un lugar hacia donde ir.
        agent.SetDestination( Vector3.zero );
  }
  
  //Se llama al pintar la pantalla...
  void Update ()
  {
    
  }
}

 

En este ejemplo, le decimos al personaje que vaya desde donde está actualmente a la posición cero del mundo. La función “SetDestination()” recibe como parámetro un Vector3 que indica la posición en el mundo hacia donde queremos llegar. Seteando los parámetros que mostramos más arriba podemos modificar cómo se mueve el personaje de un punto al otro.

 

POZOS Y PORTALES

 

Para simular pozos y/o portales dentro de nuestro juego, vamos a usar algo llamado NavMeshLink. Para eso, vamos a crear dos GameObjects, uno que simboliza la entrada o inicio y otro la salida o final. Ambos GameObjects pueden representar tanto una posición para saltar como una entrada/salida a un portal.

Lo que tenemos que hacer es agregar un componente llamado OffMeshLink. Dicho componente lo podemos agregar a CUALQUIER GameObject, sea a una de las partes o a otro que las contenga.

 

 

Como vemos, el componente tiene varias opciones para configurar. Veamos una por una:

  • Start: Transform que indica la posición del punto inicial de salto.
  • End: Transform que indica la posición final del salto.
  • Cost Override: Costo del path finding al pasar por esta conexión. Si es negativa, el costo es igual a la distancia euclídea.
  • Bi Directional: Especifíca si se puede ir y volver por la misma conexión o no.
  • Activated: Le dice al NavMesh si el personaje puede o no puede pasar por acá. Esto podría representar un portal/puerta cerrado.
  • Auto Update Positions: Esto se usa para saber si se tiene que actualizar la posición de los transforms Start y End. Si no está tildado, al mover ambos transforms no se actualizará su posición.
  • Navigation Area: Tipo de área que representa este componente. Por ejemplo: Un área caminable.

 

Con estos parámetros entonces nuestro personaje podrá elegir si pasar o no a través de los puntos. Si representa un portal, podría elegir usarlo siempre y cuando llegue más rápido por él. Dicho cálculo se va a tener en cuenta a la hora de llegar más rápido.

 

NOTA IMPORTANTE

Algo importante a tener en cuenta es que si querémos que nuestro OffMeshLink sea usado como portal, se debe setear el valor “Cost Override“, puesto que si se deja en -1 el costo de atravesarlo será la distancia entre ellos, y si están muy distantes el personaje podría elegir caminar, ya que pasar por el le costaría mucho.

Desde nuestros scripts es importante que podamos acceder a estos componentes porque la mayoría de las veces no vamos a querer que nuestro personaje pase automáticamente por ellos, puesto que el salto jamás va a ser como nosotros queramos. Lo primero que deberíamos hacer, ya sea por código o desde el editor, es no permitir que nuestro personaje los atraviese automáticamente. Luego nos dedicaremos a preguntar cuándo nuestro personaje está sobre un punto de salto, para encargarnos de pasarlo nosotros mismos y que luego siga como deba seguir.

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

public class Character : MonoBehaviour
{
    //Acá guardamos el componente del agente.
    public NavMeshAgent agent;

    //Se ejecuta al inicializar...
    void Start ()
    {
        //Obtenemos el componente.
        agent = this.GetComponent<NavMeshAgent>();

        //Le damos un lugar hacia donde ir.
        agent.SetDestination( Vector3.zero );

        //¿Debe traspasar el OffMesh Link?
        agent.autoTraverseOffMeshLink = false;
    }
  
    //Se llama al pintar la pantalla...
    void Update ()
    {
        //¿Está en un OffMesh Link?
        if(agent.isOnOffMeshLink)
        {
            //Movemos al personaje al punto final del portal.
            this.transform.position = agent.currentOffMeshLinkData.endPos;

            //Notificamos al agente que ya lo pasamos por el portal.
            agent.CompleteOffMeshLink();
        }
    }
}

 

La variable “currentOffMeshLinkData” guarda información acerca de cuál es el punto de salto actual del personaje. Dentro de dicha variable tenemos los datos como el punto inicial del salto y el final. Algo importante a notar es que si nuestro OffMeshLink está marcado como bi-direccional, la variable “endPos” representará el otro extremo al momento de ir para el otro lado.

También se pueden generar automáticamente los puntos de salto. Se van a agregar bastantes más puntos, pero en algunos casos puede servir. Al momento de generar el NavMesh, hay una opción llamada  “Generate OffMeshLinks”:

 

 

Luego, en la pestaña de “Bake”, vamos a la parte que dice “Generated Off Mesh Links” para configurarlo:

 

 

Cualquiera de los dos parámetros que se configure con el valor cero provocará que se desactive dicha opción. Algo que se debería tener en cuenta es que el valor de caída (“Height Drop“), debería ser siempre mayor que el “Step Height“, de lo contrario sólo sería un paso más abajo, en lugar de una caída.

 

OBSTACULOS FIJOS

En todos nuestros mapas vamos a tener obstáculos de todo tipo. Por ahora vamos a centrarnos en aquellos que son fijos. Conocer cuáles son es importante para optimizar mucho más la navegación, ya que los obstáculos bakeados como fijos se tienen en cuenta directamente al crear la parte caminable y no se calculan nuevamente al decirle al personaje que vaya de un punto al otro.

Para ello, seleccionamos todas las partes NO CAMINABLES de nuestro escenario, y en la ventana de navegación los marcamos como “Not Walkable” de manera similar a como lo hicimos con las partes caminables al principio. Lo que va a hacer esto es modificar nuestro NavMesh teniendo en cuenta todos los objetos NO CAMINABLES, o sea, los obstáculos fijos o simplemente caminos por los que el tipo no debería pasar.

 

NOTA IMPORTANTE

Hay que tener en cuenta que no hace falta seleccionar TODAS las partes del escenario por las que el personaje no puede caminar. SOLAMENTE hacerlo con aquellas partes que están DENTRO del camino del personaje. Las otras directamente no se van a tener en cuenta, así que no hace falta que carguemos más a nuestro NavMesh.

OBSTÁCULOS DINÁMICOS

Ahora nos vamos a encargar de aquellos obstáculos que pueden moverse. Estos consumen, lógicamente, más que los fijos, pero son útiles cuando hay puertas que pueden abrirse/cerrarse, autos en movimiento, etc.

Entonces, para empezar, vamos a ponerle a nuestro obstáculo dinámico un componente llamado “NavMeshObstacle“.

 

 

Vamos a pasar por cada una de las propiedades:

  • Shape: El tipo de “forma” que se va a usar para chequear la colisión. Se puede elegir entre una caja o una capsula.
  • Center: Punto central del shape de colisión. Debería coincidir con el collider, para que la física se corresponda.
  • Size: Tamaño del shape de colisión. También debería coincidir con el collider.
  • Carve: Al activarlo, habilitamos al personaje a buscar un camino alrededor del objeto.
    • Move Threshold: Umbral de distancia para actualizar el obstáculo.
    • Time to Stationary: Tiempo que debe esperar el personaje hasta considerar el obstáculo no se mueve.
    • Carve Only Stationary: Si está activado, el personaje sólo lo rodea cuando está quieto.

 

 

En resumen, si un obstáculo, como un auto o algo “peligroso” se mantiene en movimiento, la opción “carve” debería estar desactivada así el personaje intenta evitarlo. Sino, la activamos para que el personaje pueda evitarla pasando por el costado.

 

NOTA IMPORTANTE

No hace falta que otros personajes tengan este componente, porque los personajes ya saben cómo esquivarse entre ellos.

AUMENTANDO LA PRECISION DEL NAVMESH

Comúnmente el NavMesh va a intentar aproximar la posición del personaje al área caminable. Se puede aumentar la precisión para que el personaje se posicione correctamente, a costa de un poco de rendimiento (nada sale gratis). Para hacerlo, en la pestaña de “Bake”, en la ventana de “Navigation”, abrimos la parte que dice “Advanced”. Lo único que habría que hacer es activar la opción “Heigh Mesh”.