Dans un précédent tutoriel, nous avons vu comment mettre en place la recherche de chemin (pathfinding) via le NavMesh. Ce système fonctionne parfaitement à une exception près : le NavMesh est statique. Il est généré depuis l’éditeur et ne changera plus du tout par la suite. Si ce détail peut paraître anodin, il ne l’est pas du tout en réalité.
Mais alors comment faire pour avoir des obstacles dynamiques comme par exemple une porte qui s’ouvre, un pont qui s’abaisse, ou encore… un très gros personnage qui bouche le passage ?
Disclaimer
Cet article a été originalement écrit le 25/07/2014 sur mon précédent blog. Certaines informations présentées ici peuvent donc ne plus fonctionner telles quelles.
Mise en place
Pour commencer, il vous faut créer un NavMesh. Si vous ne savez pas comment en créer un, c’est expliqué dans ce tutoriel. C’est toujours avec un NavMesh qu’Unity effectuera les résolutions de chemin pour vos agents. Il sera la base de travail sur laquelle on va placer des obstacles. Ces obstacles seront interprétés comme des zones non accessibles, ce qui aura pour impact de modifier le calcul de chemin. Notez que le NavMesh reste inchangé. Si l’obstacle disparaît… la zone redeviendra accessible par un agent. Et c’est là que ça devient cool.
Note : La recherche de chemin est une fonctionnalité bridée en version gratuite. Elle est utilisable, mais peut montrer des limites si on pousse un peu. Par exemple, si vous créez un obstacle qui bouche un accès et que c’est le chemin le plus court, les agents essaieront quand même de passer par là et seront coincés, même s’il y a un autre chemin plus long. Oui je sais c’est moche, mais comme je le répète souvent, ils vivent de la vente de licences.
Nous voilà avec un NavMesh assez simple. Deux grandes zones et un goulot d’étranglement dans lequel nous allons mettre une porte :
Cette porte sera notre obstacle. Lorsque la porte est fermée, on s’attend à ce que rien ne puisse passer à travers. Dans le cadre de ce tutoriel, nous allons rester très simple pour la porte. Si vous voulez aller plus loin, un autre tutoriel explique comment créer une porte automatique avec Mecanim.
Placez la porte sur la scène comme ceci :
Création de l’obstacle dynamique
On va maintenant ajouter à notre porte un composant particulier : le NavMeshObstacle. Pour faciliter la suite, créez un GameObject enfant de la porte et ajoutez le NavMeshObstacle dessus. Cela vous permettra de jouer sur son placement :
Déplacez verticalement le GameObject « Obstacle » afin d’être sûr que le cylindre vert de l’obstacle passe bien à travers le NavMesh. Oui, un NavMeshObstacle, c’est forcément un cylindre, il faut faire avec. Si vous avez de grandes surfaces à rendre inaccessibles, vous pouvez créer plusieurs GameObject enfants avec chacun un NavMeshObstacle pour obtenir quelque chose d’approchant ce que vous voulez (n’en abusez pas tout de même !). Dans notre cas, on pourrait avoir quelque chose comme ça :
Il nous faut également un agent. Une capsule… un composant NavMeshAgent… et hop !
public class FollowMouse : MonoBehaviour { private NavMeshAgent _agent; void Start () { _agent = GetComponent(); } void Update () { if(Input.GetMouseButton(0)) { // Check for a new destination with raycast var ray = Camera.main.ScreenPointToRay(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0)); var hitInfo = new RaycastHit(); Physics.Raycast(ray, out hitInfo, 500); if(hitInfo.collider.gameObject.isStatic) { _agent.SetDestination(hitInfo.point); } } } }
La seule différence avec le script précédent est dans la gestion du GameObject touché par le rayon. Ici, on teste simplement s’il est défini comme static, ce qui signifie qu’il a été utilisé pour la génération du NavMesh (et qu’il est donc très probablement accessible).
Créons maintenant un nouveau script qui va gérer l’obstacle (la porte) en l’activant / désactivant. C’est volontairement très simple :
public class DoorScript : MonoBehaviour { private bool _open; void Update() { if(Input.GetMouseButtonDown(1)) { Toggle(); } } private void Toggle() { _open = !_open; GetComponent().enabled = _open; GetComponentInChildren().enabled = _open; } }
Lors du clic droit, on désactive le NavMeshObstacle ainsi que le MeshRenderer qui affiche le cube pour avoir un retour visuel. Dans le cadre d’une réelle animation de porte, l’activation de l’obstacle serait à placer au lancement de l’animation de fermeture, et la désactivation à la fin de l’animation d’ouverture.
Ajoutez ce script à la porte et faites un essai…
Le clic droit affiche ou masque la porte, et le clic gauche envoie notre agent là où on a cliqué. Observez le comportement de votre agent lorsque vous « ouvrez » ou « fermez » la porte. Il ne passe plus à travers.
Voilà de quoi ajouter encore plus de dynamisme à vos niveaux. Aller, maintenant c’est le moment d’aller faire du level design !