La nouvelle méthode pour générer un NavMesh et rendre vos PNJs dynamiques dans des environnements complexes


Nous en avons déjà parlé ici, rendre son univers vivant est important pour l’immersion du joueur. Cette immersion passe entre autres par une intelligence artificielle capable d’effectuer des actions naturellement, comme par exemple se déplacer dans des environnements complexes. Unity propose un système de NavMesh pour répondre à cette problématique.

Introduction

Un monde vivant, c’est un monde intéressant à parcourir. Et pour apporter de la vie dans vos univers, il est immédiatement question d’intelligence artificielle. C’est un sujet très très très vaste. Au moins aussi grand que Tamriel. Alors abordons aujourd’hui une seule facette de l’IA : le déplacement.

Typiquement, dans n’importe quel RPG, certains PNJs se promènent en ville. Ils sont pilotés par une IA qui va décider de ses action. Par exemple : aller jusqu’au marché, patrouiller sur la muraille, etc. Mais la ville est grande, il y a des ruelles et des maisons partout. Et si on trace une ligne droite du point de départ au point d’arrivée, il parait évident que cette ligne va rencontrer des obstacles. L’IA doit donc déterminer un chemin pour arriver à destination.

Pour calculer ce chemin, l’IA fait comme vous, elle ouvre Google Maps et… et voilà le coeur du problème : il faut fournir à l’IA sa version de Maps. Dans Unity, on appelle ça un NavMesh.

Dans cet article nous allons aborder deux version du NavMesh : la version intégrée à Unity 2017.1, et la version améliorée disponible sur les dépôts publics d’Unity Technologies. Il est probable qu’à l’avenir, cette seconde version soit intégrée directement à l’éditeur.

Le Legacy mode

Le NavMesh dans Unity

Dans NavMesh, il y a Mesh. En effet, Unity va générer un modèle 3D spécial qui correspondra aux « rues » sur une carte. C’est à partir de ce Mesh, le NavMesh, qu’Unity va pouvoir calculer des chemins d’un point A à un point B.

Avant de créer un NavMesh, il vous faut un environnement. En effet, sans environnement il a peu d’intérêt. Commencez donc par créer un cube aplati qui servira de sol. Ajoutez quelques murs, des rampes, etc :

Exemple de scène pour tester un NavMesh

Une fois votre environnement de construit, il vous faut effectuer une manipulation. Vous devez spécifier quels sont les GameObjects correspondant à des décors fixes. Cette déclaration se fait dans l’inspecteur du GameObject concerné, via la coche « static » tout en haut :

Inspecteur avec la propriété "static"

Maintenant que les décors sont définis comme « static », vous pouvez passer à la génération du NavMesh. Ouvrez le menu Window > Navigation pour faire apparaître la fenêtre Navigation. Cette fenêtre vous permettra de gérer tous les détails de la génération de votre NavMesh. Laissez ça de côté pour l’instant. Allez sur l’onglet Bake et cliquez sur le bouton Bake.

Fenêtre de génération du NavMesh

Cela peut prendre un peu de temps. Vous obtenez ensuite un espèce de calque bleu, comme ceci :

NaMesh généré et affiché sur la scène

Les zones en bleu correspondent aux zones calculées comme étant accessibles. Toutes les autres sont considérées inaccessibles. Et voilà le travail. Vous avez maintenant de quoi donner à vos agents les outils pour qu’ils puissent se balader eux même ! Mais… c’est quoi un agent ?

Les Agents sur le NavMesh

Un agent, c’est une entité qui va pouvoir se déplacer sur un NavMesh. C’est aussi simple que ça ! Donc si vous voulez qu’un personnage puisse se déplacer de lui même avec un NavMesh, il vous faudra lui ajouter un composant NavMeshAgent.

Le NavMeshAgent prend un Transform en paramètre, sa destination. Dès qu’une destination est assignée, l’agent va déplacer le GameObject pour aller à destination en se basant sur le NavMesh que vous avez généré.

Pour tester et voir à l’oeuvre, ajoutons un script de contrôle à la souris qui va initialiser la destination au point où on a cliqué sur la scène :

public class ClickAndGo : MonoBehaviour
{
    private NavMeshAgent _agent;
    private Transform _moveTarget;

    private void Start()
    {
        _agent = GetComponent(typeof(NavMeshAgent)) as NavMeshAgent;
    }

    private void Update()
    {
        if(Input.GetMouseButton(0))
        {
            var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 1000))
            {
                var clickPosition = hit.transform.position;
                _agent.SetDestination(clickPosition);
            }
        }
    }
}

Ajoutez sur la scène une capsule. Ajoutez lui un NavMeshAgent et le script que vous venez de créer. Déplacez la caméra de manière à voir la scène et lancez le test. Dès que vous cliquez à un endroit, l’agent se dirige de lui même vers la cible, en suivant le NavMesh.

Depuis les paramètres de génération, vous pouvez ajuster les détails du NavMesh, comme la hauteur minimum pour qu’un espace soit accessible, la pente maximale, etc. Vous aurez remarqué que la zone sous le balcon n’est pas considérée comme valide, c’est parce que la hauteur est insuffisante d’après le paramétrage. Si vos rampes ne sont pas considérées comme des zones valides, modifiez la pente maximale dans ces mêmes paramètres (ou ajustez la pente sur votre scène).

Dernier point concernant les agents : les types. Vous pouvez définir différents types d’agents réagissant de manière différentes. Chaque type d’Agent pourra donc avoir des réactions différentes. Par exemple, un type « Gros monstre » aura une taille plus grande que le type « joueur ». De ce fait, les agents du type « Gros Monstre » ne pourront pas passer à certains endroits, mais le joueur si.

Créer des passages supplémentaires

En supplément du NavMesh, vous pouvez placer des points de passage supplémentaires entre deux zones inaccessibles. Comme par exemple pour sauter du balcon vers la zone basse. On parle alors de OffMeshLink.

Pour créer un OffMeshLink, créez un nouveau GameObject et affectez lui le composant OffMeshLink. Le composant vous propose de renseigner deux Transforms qui sont le point de départ et le point d’arrivée du lien. Ces deux Transforms doivent évidemment être sur une zone valide du NavMesh pour fonctionner :

Représentation visuelle d'un OffMeshLink

De cette manière, quand bien même la zone n’est pas accessible, l’agent considérera qu’il y a un passage ici. Vous pouvez paramétrer le OffMeshLink pour être à sens unique. Par exemple pour ne pas pouvoir remonter sur le balcon. L’Agent le prend automatiquement en compte pour calculer ses déplacements.

Vous avez maintenant une entité qui est capable de se déplacer par elle même dans un environnement que vous aurez préparé au préalable… Mais cette utilisation implique de créer le NavMesh dans l’éditeur. Et si on veut générer un NavMesh à la volée, par exemple pour un niveau généré procéduralement, on fait comment ?

NavMesh Next gen

Génération du NavMesh au runtime

Depuis Unity 5.6, vous avez la possibilité de générer un NavMesh au runtime. Pour pouvoir utiliser cette feature, il vous faut installer des Assets qui ne sont pas packagés avec Unity. Commencez par aller télécharger les sources sur le dépôt github ici :

https://github.com/Unity-Technologies/NavMeshComponents

Copiez les répertoires Gizmos et NavMeshComponents dans votre projet. Ces nouveaux scripts ajoutent une entrée de menu « AI » dans le menu GameObject :

Menu ajouté par la nouvelle version du NavMesh

Le NavMeshSurface

Le composant NavMeshSurface va vous permettre de définir tous les paramètres de génération du NavMesh à la volée. Vous remarquerez que l’interface de l’inspecteur associé à ce composant est similaire à celle de génération d’un NavMesh. On retrouve la visualisation du type d’agent :

Inspecteur du NavMeshSurface

Nous avons vu le Agent Type dans la première partie sans le détailler. Ce point est important. En effet, un NavMesh est calculé pour un seul type d’Agent ! Pas de NavMesh = pas de déplacement. Si vous essayez d’initialiser une destination pour un agent dont le type n’a pas de NavMesh généré associé, Unity vous enverra gentiment sur les roses :

Erreur typique lorsqu'un type d'agent n'a pas de NavMesh associé

La bonne nouvelle, c’est que vous pouvez créer autant de NavMeshSurface que vous voulez ! Pour chaque type d’agent. Et même plus si vous voulez découper vos scènes.

Le second point d’intérêt est Collect Objects. Ce paramètre vous permet de configurer quels GameObjects le NavMeshSurface va prendre en compte pour calculer son NavMesh.

Ensuite, vous avez Include Layers. Avec ce paramètre, vous pouvez filtrer encore plus finement les GameObjects pris en compte via leur layer.

Enfin, vous avez Use Geometry. Si vous avez déjà utilisé le système de NavMesh actuellement intégré à Unity, vous avez peut-être rencontré quelques… difficultés. En effet, la génération se base sur les MeshRenderers, et uniquement les MeshRenderers. Autant vous dire que c’était assez rock’n’roll avec des modèles 3D comme ça :

Génération du NavMesh peu précise avec le mode legacy

Avec cette propriété, vous avez la possibilité de choisir sur quoi va se baser le NavMeshSurface pour calculer le NavMesh. Si vous avez optimisé vos modèles 3D avec des Colliders utilisant des primitives, vous pourrez maintenant les utiliser pour générer le NavMesh. En se basant sur les Colliders, le résultat est tout de suite beaucoup plus satisfaisant :

Génération parfaite avec le nouveau mode

Elle est pas belle la vie ? 😀

Le NavMeshModifier et le NavMeshVolume

C’est bien d’avoir un fonctionnement général, mais c’est mieux de pouvoir ajouter des exceptions. C’est précisément à ça que servent les modificateurs comme le NavMeshModifier et le NavMeshVolume.

Le NavMeshModifier doit être ajouté à un GameObject servant de base au calcul du NavMesh. Vous pouvez dès lors lui permettre de surcharger le calcul général pour cette section physique. Ici, le bloc est tout simplement ignoré lors de la génération du NavMesh :

Exemple de NavMeshModifier

Le NavMeshVolume permet d’effectuer un affinage du calcul du NavMesh à l’intérieur de la zone. Vous pouvez l’utiliser sur un GameObject vide pour le placer comme bon vous semble. Seule la section à l’intérieur du volume sera impactée :

Exemple de NavMeshVolume

C’est un exemple bien aligné, mais vous pouvez faire les foufous avec de la composition :

Exemple de NavMeshVolume composite

L’utilisation de ces modificateurs est particulièrement utile dans le cadre de l’ajout de différents environnements à votre scène. Vous pouvez par exemple spécifier que telle zone correspond à un marais et que le coup de déplacement à l’intérieur de celle ci est plus élevé. Vous pouvez également paramétrer quels agents sont impactés par ce modificateur. Par exemple, les agents « Gros Monstre » peuvent se déplacer dans le marais sans malus.

Le NavMeshLink

Ce composant est le digne remplaçant du OffMeshLink. Il vous permet de créer un lien entre deux zones considérées comme inaccessibles de par le paramétrage de calcul du NavMesh. Le fond reste inchangé mais sa prise en main a été améliorée.

Pour créer un NavMeshLink, ouvrez le menu GameObject > AI > NavMesh Link. Le GameObject généré comporte deux extrémités déplaçables. Ces extrémités correspondent aux points de départ et d’arriver du lien. Déplacez les éléments pour que les points de départ et d’arrivée soient sur le NavMesh. Pour savoir si les position sont correctes, vous devez voir un cercle autour des marqueurs, comme ceci :

Représentation visuelle d'un NavMeshLink

Plus besoin de créer un Transform de départ et un Transform d’arrivée. Les points sont gérés en interne par le composant. Vous avez également un joli Gizmo qui s’affiche ! Absolument indispensable. Blague à part, lorsque vous aurez de grands niveaux, vous serez bien contents d’avoir ce genre de Gizmos pour retrouver facilement les liens sur la scène.

Générer un NavMesh au runtime

La grosse feature du nouveau système de NavMesh, c’est la génération au runtime. La mécanique de génération étant extraite dans un composant NavMeshSurface, vous pouvez l’utiliser in game. Pour générer un NavMesh au runtime, il vous faut utiliser l’API du NavMeshSurface. C’est simple comme bonjour :

public class GenerateNavMeshOnStart : MonoBehaviour
{
    private void Start()
    {
        var surface = GetComponent(typeof(NavMeshSurface)) as NavMeshSurface;
        surface.BuildNavMesh();
    }
}

Dur de faire plus simple. Le NavMeshSurface peut lui aussi être généré au runtime, c’est très flexible !

Pour conclure

Ce nouveau système de gestion des NavMesh est plus simple à utiliser et à personnaliser. Sans parler du fait qu’il est possible de générer des NavMesh au runtime. Voilà de quoi mettre un peu d’ambiance dans vos projets avec des PNJs qui se déplacent d’eux même dans un environnement complexe !

Pour plus d’informations sur le nouveau système de NavMesh, je vous invite à lire la documentation disponible ici (en anglais).

Si vous ne vous sentez pas encore prêt à sauter le pas vers la nouvelle version, la documentation du mode legacy est ici.

Maintenant que vous savez utiliser le NavMesh, vous pourriez avoir envie d’animer votre personnage : apprendre à piloter une animation avec Mecanim.

Cet article vous a appris quelque chose ? Partagez-le à vos amis !


Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *