Unity NavMesh

De MediaWiki
Ir a la navegación Ir a la búsqueda

Introducción

  • Más información en:


  • Uno de los problemas a los que nos podemos enfrentar al desarrollar un juego es de dotar de inteligencia a los enemigos y que sean capaces de 'esquivar' los obstáculos de un escenario mientras intentan coger a nuestro protagonista.
Podríamos intentar hacerlo nosotros, ya que vimos en esta Wiki el uso del vector dirección y podríamos hacer que los enemigos calcularan dicho vector con respecto al jugador y que se movieran hacia él.
El problema que tendríamos es que si existen obstáculos entre el enemigo y el jugador, quedarían atrapados, como se observa en la siguiente escena:
Unity3d navmesh 1.jpg
Si tuviéramos que programarlo podría ser muy laborioso.
  • Para solucionarlo, Unity ofrece el sistema de navegación inteligente: Navigation System.
Este componente va a permitir definir mediante una serie de parámetros que área de nuestra escena es 'caminable' por los enemigos.
Por ejemplo, podremos definir que las zonas elevadas más de X grados no sean navegables (por tanto serían un muro) o que las bajadas o subidas de desnivel a partir de un tamaño no sean accesibles (formarían otro muro).
Una vez creado el Navigation, asociamos a cada enemigo un NavMesh Agent el cual hará que dicho enemigo vaya 'detrás' del protagonista esquivando los posibles objetos que encuentre por el camino.



Preparando la escena

  • Vamos a preparar una escena para mostrar el uso de este componente.
Los gráficos son utilizados de otras partes de este manual de la Wiki. Si no dispones de ellos no pasa nada. Puedes emplear los que tengas disponibles llegados a este punto de la Wiki y como último recurso puedes emplear figuras básicas como Cubos o Esferas.
  • Creamos una nueva escena o un nuevo Empty GameObject, sobre el que situaremos todos los gameobjects siguientes.
  • Un plano de dimensiones 5x1x5 y un material con una textura color Rojo asocicada (si ya habéis visto como crear un Terrain podríais hacer uso de uno).
  • Dos vallas que conformarán los obstáculos.
  • Un Jeep que será nuestro protagonista.
  • Un tanque que será nuestro enemigo.
El tanque viene rotado por lo que el eje-Z del mismo no se corresponde con el eje-Z del mundo.
Para solucionarlo, creamos un Empty GameObject y dentro del mismo ponemos el tanque con el eje-Y rotado -90 grados.




Navigation Mesh

  • El NavMesh o Navigation Mesh es el componente con el que vamos a definir el área que es 'utilizable' por los enemigos para poder desplazarse.
  • NOTA IMPORTANTE: Para que una superficie forme parte del NavMesh es necesario que sea de tipo Navigation Static.
En nuestro ejemplo, queremos que el plano forma parte del NavMesh, por lo que debemos ir a la ventana Inspector y comprobar que, al menos, esté marcada la opción indicada:
Unity3d navmesh 9.jpg
También queremos que las vallas formen parte del NavMesh en el sentido que queremos que no se pueda pasar a través de ellas, por lo que también las marcaremos como Navigation Static:
Unity3d navmesh 13.jpg



  • Cuando creamos un NavMesh estamos creando la superficie sobre la que los enemigos van a poder desplazarse. Esta superficie es 'precalculada' y guardada por Unity y es por lo que se conoce como Baking un NavMesh (al igual que tenemos el proceso de Bake sobre las luces para precalcular las sombras generadas).


Para crear un NavMesh debemos ir a al menú principal Window => AI => Navigation
Unity3d navmesh 5.jpg


Al abrir la ventana Navigation aparecerá en la 'Inspector Window' una nueva pestaña (Navigation) formada por 4 pestañas. En la pestaña Bake disponemos de las propiedades que van a conformar nuestra superficie 'caminable'.
Unity3d navmesh 6.jpg
  • Agent Radius: Indica a que distancia (medio metro) los enemigos deben de parar cuando choquen con un obstáculo.
  • Agent Height: Indica la altura (2 metros) que quedará libre de cualquier otro enemigo o protagonista (veremos un ejemplo más adelante).
  • Max Slope: Indica en grados (45 grados) la inclinación máxima de un obstáculo por el cual el enemigo va a poder caminar.
  • Step Height: Altura (0,4 metros) que es considerada como un escalón por el cual el enemigo va a poder 'subir'. En el ejemplo, un obstáculo de más de 0.4 metros no podría ser caminable.


La sección Generated Off Mesh Links hace referencia a la posibilidad de 'conectar' diferentes puntos de un NavMesh.
Unity3d navmesh 7.jpg
Más información en este enlace.
La sección Advanced se puede consultar en este enlace.


  • Una vez conformadas las características de nuestro NavMesh tendremos que hacer el proceso de Bake para que Unity calcule la superficie caminable y los obstáculos de nuestra escena. Para ello presionamos el botón Bake:


  • Vamos a 'complicar' un poco más la escena y vamos a hacer que haya una especie de 'paso'.
La idea es hacer que el tanque pueda pasar por debajo de dicho paso. Este ejemplo sería aplicable a juegos con un túnel o lugares sobre los que queramos que los enemigos (o incluso el propio protagonista) puedan pasar.






NavMesh Agent


  • Ahora que tenemos conformado sobre que superficies van a poder caminar los personajes, es necesario asociar los NavMesh Agent a dichos personajes.
Los enemigos serán considerados Navigation Characters y el coche será el Target al cual se dirigirán todos los Navigation Characters.


  • Lo primero que tendremos que hacer será asociar el componente 'NavMesh Agent' al tanque:
  • En la ventana Inspector, presionamos el botón Add Component => Navigation => NavMesh Agent.
Unity3d navmesh 18.jpg
  • Agent Type: El NavMesh por defecto hace uso del tipo 'Humanoide'. Es posible crear nuevos tipos pero necesitaríamos crear nuestro propio NavMesh para que hago uso de un agente creado por nosotros.
  • Tamaño y colocación del collider del agente:
A los agentes se les asocia algo parecido a un collider, utilizado para saber cuando están en contacto con otro agente o con obstáculos móviles.
Unity3d navmesh 19.jpg
  • Base Offset: Distancia con respecto al suelo.
  • Radius: Radio
  • Height: Altura
  • Sección Steering:
  • Speed: Velocidad de movimiento del agente.
  • Angular Speed: Velocidad de giro del agente.
  • Acceleration: Aceleración hasta llegar a la máxima velocidad.
  • Stopping Distance: Distancia a la que se debe parar del objetivo.
  • Auto Braking: Al llegar al objetivo irá disminuyendo de velocidad.
  • Sección Obstacle Avoidance:
  • Quality: Indica como los agentes deben evitar los obstáculos y otros agentes. Cuanta más calidad más CPU se necesitará. Si establecemos Quality en None, se tendrán en cuenta las colisiones pero no se calcularán rutas alternativas para evitar a otros agentes y obstáculos.
  • Priority: Número entre 0 y 99 siendo 0 la prioridad más alta. Si hay problemas de rendimiento, los agentes con la prioridad más baja no serán atendidos.
  • Sección Path Finding:
  • Auto Traverse Off Mesh Links: Si combinamos dos NavMesh a través de un Off Mesh Links, necesitamos que el agente sea capaz de pasar de un NavMesh a otro.
  • Auto Repath: Los NavMeshes pueden ser 'divididos' en diferentes secciones. Si queremos que el agente no pueda pasar de una sección a otra, debemos desactivar esta casilla.
  • Area Mask: Podemos indicar sobre que superficies el agente puede desplazarse. Estas superficies se determinan haciendo uso de máscaras, las cuales están definidas en la pestaña de 'Areas':
Unity3d navmesh 20.jpg
Por defecto, el NavMesh sólo permite 'caminar' sobre las superficies marcadas como 'Walkable'.


  • Modificamos nuestra escena y ajustamos las propiedades del agente para que tengan el tamaño del tanque:
Unity3d navmesh 21.jpg
Ajustamos la distancia 'Stoping Distancia' a 5 metros y la velocidad a 5.
Nota Importante: Recordar que el collider del NavMesh Agent no tiene nada que ver con el Collider del motor de físicas. Este collider es utilizado por el NavMesh Agent para determinar los choques entre otros NavMesh Agent y determinar rutas para alcanzar el objetivo teniendo en cuenta los GameObjects marcados como Static generados con el NavMesh al realizar el proceso de 'Bake'.
Si quisiéramos utilizar el motor de físicas junto con el NavMesh Agent, debemos de añadir un Collider y un RigidBody marcado como de tipo 'is Kinematic'


  • Ahora es necesario mediante un script, indicar al NavMesh Agent a donde tiene que dirigirse.

Script: UD3_Navigation_MoverTanque

 1 public class UD3_Navigation_MoverTanque : MonoBehaviour
 2 {
 3 
 4     [SerializeField] private NavMeshAgent navigation;
 5     [SerializeField] private Transform player;
 6 
 7     void Start()
 8     {
 9     }
10     // Start is called before the first frame update
11     void Reset()
12     {
13         navigation = GetComponent<NavMeshAgent>();
14     }
15     private void Update()
16     {
17         navigation.destination = player.position;
18     }
19 
20 
21 
22 }
  • Líneas 19-23: Obtenemos una referencia al NavMeshAgent y asociamos a la propiedad 'destination' la posición del jugador, para que lo siga.


  • Asociando un script para controlar el NavMesg Agent
  • Una vez creado el script y asociado, debemos presionar la opción 'Reset' para referenciar el NavMesh Agent.

  • Arrastramos el Jeep a la propiedad 'Player' del script.


    • Si ahora probamos el juego podremos comprobar como el tanque sigue al Jeep y sólo por laz zonas donde puede pasar (realizado en la operación de Bake).


    • Esta forma no es la más óptima, ya que en cada fotograma está calculando las rutas y choques entres los diferentes agentes.
    Es mejor que dicho cálculo lo realice cada x segundo, por ejemplo, cada medio segundo.
    Modificamos el script por el siguiente y en el inspector pondremos 0.5 en la propiedad 'updateDelay'.

    Script: UD3_Navigation_MoverTanque

     1 public class UD3_Navigation_MoverTanque : MonoBehaviour
     2 {
     3 
     4     [SerializeField] private NavMeshAgent navigation;
     5     [SerializeField] private Transform player;
     6     [SerializeField] private float updateDelay;
     7 
     8     void Start()
     9     {
    10         InvokeRepeating("SeguirPlayer", 0f, updateDelay);
    11     }
    12     // Start is called before the first frame update
    13     void Reset()
    14     {
    15         navigation = GetComponent<NavMeshAgent>();
    16         updateDelay = 0.3f;
    17     }
    18 
    19     // Update is called once per frame
    20     private void SeguirPlayer()
    21     {
    22         navigation.destination = player.position;
    23             
    24         
    25     }
    26 
    27     private void OnTriggerEnter(Collider other)
    28     {
    29         Debug.Log(other.gameObject.name);
    30     }
    31 }
    



    Unity3d navmesh 24.jpg




    NavMesh Obstacle


    • Este tipo de NavMesh se aplica a aquellos GameObjects que queremos que sean tenidos en cuenta por el resto de agentes NavMesh pero que no son estáticos (y pueden generarse haciendo un Bake) sino que pueden moverse a lo largo del juego.
    Nota Importante: Nunca debemos de mezclar un NavMesh Obstacle con un NavMesh Agent.


    • Cambiemos la escena anterior por la siguiente:
    Unity3d navmesh 25.jpg


    • Nota:
    • Desmarcamos la valla frontal como 'Navigation Static' en la ventana Inspector (ya que es la que se va a mover).
    • Recordar aplicar el 'Bake' en la pestaña Navigation ya que hemos cambiado las vallas de sitio.



    Como observamos, ahora el tanque está rodeado de tres vallas.
    Si ejecutamos el juego tal cual está, podemos observar como el tanque atraviesa la valla ya que no tiene collider (del motor de físicas) asociado ni es un 'Static Navigation'.


    • Debemos de recordar que el movimiento del tanque lo 'dicta' el NavMesh Agent y no el motor de físicas.
    Para solucionar el problema no debemos de emplear un Collider-RigidBody asociado al tanque, ya que en ese caso el movimiento lo debería regir el motor de físicas y no el NavMesh Agent, y si marcamos la opción 'Is Kinematic' del tanque, este ya no se vería afectado por el motor de físicas. Podríamos asociar a la valla un RigidBody y un Collider, pero el tanque empujaría la valla y podría salir.


    • La opción más simple es hacer uso de un NavMesh Obstacle.
    Como la valla que se va a mover es la frontal, añadimos un NavMesh Obstacle, en la ventana Inspector: Add Component => Navigation => NavMesh Obstacle
    Podemos observar como se crea una especie de 'collider' asociado.
    Aviso: El collider lo crea en la posición (0,0,0) por lo que es necesario que tengáis una visión amplia de la escena para verlo.
    En las propiedades del NavMesh Obstacle podemos cambiar su tamaño y posición así como su forma entre dos posibles: Cápsula-Caja.
    Modificamos sus propiedades para ajustarlo a la forma y posición de la valla frontal.
    Si ahora nos situamos en la pestaña 'Navigation' de la ventana Inspector podemos observar como las dos vallas marcadas como Static Navigation tienen la zona restringida pero la valla con el NavMesh Obstacle no.
    Unity3d navmesh 26.jpg


    • Si ahora ejecutamos el juego podemos observar como el tanque no es capaz de salir de la zona de las vallas.
    Unity3d navmesh 27.jpg


    • Esta sería una posible solución pero existe una modificación más eficiente.
    Si os fijáis en las propiedades del NavMesh Obstacle aparece una sección carve.
    Unity3d navmesh 28.jpg
    Dicha sección indica que dicho NavMesh Obstacle cree una zona de no paso en el NavMesh (equivalente a un Static Navigation).
    La diferencia con un Static Navigation es que el mover el GameObject (como es este caso) se genera un nuevo NavMesh como si presionáramos el botón 'Bake' del Nagigation.
    Esta opción sólo se debe emplear para aquellos GameObjects que se mueven puntualmente en el juego como por ejemplo trampas, puertas,....
    Entre las propiedades:
    • Move Threshold: Indica la distancia que se debe mover el GameObject para que Unity lo trate como un objeto en movimiento. Cuando deje este 'estado' se generará un nuevo NavMesh (como si presionáramos el botón de Bake).
    • Time To Stationary: Tiempo (en segundos) que debe pasar para que el GameObject sea considerado como un objeto 'parado' cuando termina de moverse.
    • Carve Only Stationary: Indica que se realice el proceso de Bake (Carved) sólo cuando el objeto se encuentro en dicho estado (mientras no se cumple el tiempo anterior, la zona de prohibición de paso desaparece).


    • Si marcamos dicha opción en nuestra escena, podemos comprobar como en la pestaña 'Navigation' se genera una zona de no paso asociado al NavMesh Obstacle al igual que las vallas de tipo 'Static Navigation' pero a diferencia de ellas, al mover el GameObject, la zona se 'genera' automáticamente sin necesidad de presionar el botón 'Bake':



    NavMesh Agent y otros componentes


    • Hasta ahora vimos que el NavMesh lo podemos emplear para mover a los enemigos y que estos vayan contra un objetivo esquivando los obstáculos.
    Indicar que también podría ser utilizado por un protagonista haciendo uso del propiedad NavMeshAgent.velocity. Indicando con un Vector3 hacia donde tiene que moverse.



    • Como indiqué anteriormente, si queremos utilizar el motor de físicas junto con el NavMesh Agent, debemos de añadir un Collider y un RigidBody marcado como de tipo 'is Kinematic'.



    • Por otro lado no se debe de utilizar un NavMesh Agent con una animación aplicando un 'Root Motion', ya que el agente y la animación intentarán mover al GameObject haciendo uso de su transform 'a la vez'.





    -- Ángel D. Fernández González -- (2018).