Sites


Blender pour le jeu video

Recherche de chemin automatique

Si nous voulons donner à nos PNJ* une intelligence, histoire de corser le jeu, la première chose à faire est de leur permettre de trouver leur chemin dans le niveau. Heureusement cette tâche ardue est grandement facilitée par le logiciel Recast & Detour de Mikko Mononen (https://github.com/memononen/recastnavigation) qui est intégré dans Blender et dans le BGE.

Suivre le héros

Une action fréquente de jeu est de faire des poursuites ou de mettre le héros en danger. Le principe est bien sûr de complexifier le jeu, d'en augmenter l'intérêt en contraignant le joueur à comprendre la logique de personnages non-joueurs (PNJ). Avant de passer en revue les possibilités de l'assistance automatisée aux déplacements, commençons par un premier exemple qui va permettre de prendre les choses en main. Il s'agira simplement de suivre le héros sur une scène. Pour cela nous utiliserons l'actuator steering.

  1. Supprimons le cube par défaut avec x puis ajoutons un plan Maj + a>Plane.
  2. Attribuons un matériau de base de couleur verte.
  3. Créons une pyramide à partir d'un Cube Maj + a>Cube, sélectionnons les vertices de l'un des côtés et fusionnons les avec Alt + m>At center. Attribuons un matériau rouge à cet objet et nommons-le ennemi.
  4. Ajoutons à présent le héros avec une simple sphère à laquelle nous pouvons attribuer un matériau jaune et le nom heros.
  5. Redimensionnons s et déplaçons g ces 2 personnages de manière à les espacer sur le terrain de jeu.
  6. Les bases du suivi étant à présent réalisées, sélectionnons l'objet ennemi puis passons en mode Blender Game si ce n'était pas encore le cas.
  7. Créons un sensor Alwayset relions-le à un actuator Steering.
  8. Dans la listeBehavior(comportement), vérifions que Seek (chercher) est bien sélectionné. Cette option définit l'option de base sur laquelle se basera le personnage pour rechercher sa cible. Ici, il fait une recherche directe à l'inverse de navigation Path que nous verrons ultérieurement.
  9. DansTarget Object, sélectionnons heros.
    Application de l'actuator stearing
  10. Vous pouvez tester le jeu. L'ennemi doit se diriger vers le héros.
  11. Maintenant, lorsqu'il l'atteint, il s'arrête un peu trop tôt car il ne le touche pas vraiment, ce qui n'est pas réaliste. Dans les paramètres de l'actuator, jouons sur les paramètres du steering. Dans notre cas diminuons Dist à une valeur qui rendra correctement en fonction des dimensions de nos objets.
  12. Vous pouvez enfin rendre le héros déplaçable à la souris et les choses deviennent alors très intéressantes.

Construire un plan de navigation

Nous utiliserons ce blend d'exemple comme support de ce chapitre : [recherche_de_chemin_automatique.blend].

La partie Recast s'exécute dans Blender et consiste à construire un plan de navigation (Navmesh dans le jargon Blender) à partir des différents objets qui constituent la partie fixe du niveau (les murs, le sol, les escaliers, les colonnes, etc). Dans notre exemple, il n'y a qu'un seul objet qui constitue le niveau, mais on pourrait en avoir plusieurs, il suffit juste de les sélectionner tous. 

Générer un mesh de navigation

Ensuite nous ouvrons le panneau Navigation Mesh(Properties > Scene) et nous choisissons les options en fonction des caractéristiques de notre PNJ. Il y a de nombreuses options mais les principales sont :

  • Polygonization : Verts Per Poly. Cette option doit être mise à 3 pour que l'algorithme de nagivation fonctionne correctement.
  • Agent : Radius. Spécifiez ici le rayon du PNJ en considérant le cylindre qui englobe son mesh visuel. L'algorithme utilise cette donnée pour ménager une marge par rapport aux murs du niveau de sorte que le PNJ ne se cogne pas au mur.
  • Agent : autres attributs. Les autres optionsAgentdéfinissent plus précisément les capacités de notre PNJ. Quel pente est-il en mesure de grimper ? Quelle hauteur d'obstacle peut-il franchir ? de quelle hauteur de plafond minimale a-t-il besoin pour entrer dans un espace ? Ces options sont à régler en fonction de la taille du mesh et des caractéristiques physiques de l'objet que nous avons choisies (voir le chapitre Des personnages au bon comportement de cette section).

Les autres options sont très techniques et généralement configurées correctement. Si besoin, référez vous à la documentation de l'algorithme Recast avant d'y toucher. Ensuite nous cliquons sur Build navigation mesh et un nouveau mesh est ajouté dans la scène. Ce mesh se nomme navmesh par défaut et a les caractéristiques suivantes :

  • Il est constitué de triangles qui couvrent la surface accessible à un agent ayant les caractéristiques précitées.
  • En mode solide les triangles sont colorés automatiquement pour que nous puissions voir clairement la position des vertex car ce seront les points de passage du PNJ dans le jeu.
  • Son type physique est mis à Navigation Mesh, ce qui, soit dit en passant, active la coloration automatique.
  • Il peut être divisé en plusieurs parties non connectées. Dans notre exemple, des surfaces de navigation sont créées aux sommets de certains murs. Ce n'est pas gênant pour notre PNJ.

Si nous ne sommes pas satisfaits du résultat, nous pouvons soit modifier le mesh à la main comme tout mesh (en veillant à n'ajouter que des faces triangulaires), soit supprimer l'objet et en relançant l'utilitaire après avoir modifié les paramètres (et ré-sélectionné tous les objets).

Il est tout à fait possible de créer plusieurs plans de navigation, pour différentes scènes, voire pour la même scène mais pour différents agents ayant des caractéristiques différentes. Les briques du BGE qui contrôlent la navigation permettent de définir quel navmesh sera utilisé par quel PNJ.

Aspect par défaut du mesh de navigation

Le Navmesh n'est visible que dans la Vue 3D (3D View), il est automatiquement invisible dans le jeu. Le panneau Physics d'un Navmesh montre des boutons spécifiques à ce type d'objets. Au cas ou nous aurions modifié le mesh manuellement, il est indispensable d'utiliser le bouton NavMesh Reset Index Values pour s'assurer de la cohérence du mesh.

Utiliser un plan de navigation par brique logique

L'actuator Steering permet d'utiliser un mesh de navigation dans le jeu.

Lorsque cet actuatorest activé pour un objet, il en prend le contrôle et le déplace automatiquement jusqu'à atteindre l'objectif qui est la position de l'objet spécifié dans Target Object. Nous utiliserons le terme agent pour désigner un objet qui passe sous le contrôle d'un actuator Steering.

Si l'objectif se déplace, l'actuator actualise la trajectoire de l'agent, c'est idéal pour un comportement de poursuite.

L'option Behavior détermine le comportement de l'agent :

  • Seek: l'agent cherche à atteindre l'objectif en ligne droite sans tenir compte des obstacles et du navmesh (nous pouvons omettre de spécifier un navmesh dans ce mode).
  • Flee : cette option est l'exacte opposée de Seek, l'agent cherche à fuir en ligne droite en partant dans la direction opposée de la cible.
  • Path Following : l'agent utilise le navmesh spécifié dans Navigation Mesh pour trouver le meilleur chemin pour atteindre l'objectif. Ce comportement est celui qui nous intéresse car il donne une réelle intelligence à l'agent.

Les autre options définissent les aspect dynamiques du mouvement :

  • Dist : la poursuite s'arrête si l'agent s'approche de l'objectif à une distance inférieure à cette valeur (en unités Blender, c'est-à-dire en mètres).
    La poursuite pourra éventuellement reprendre si l'objectif se déplace à nouveau. L'actuator ne définit pas en soi ce qui se passe lorsque l'objectif est atteint, c'est la logique du jeu qui le fait. 
  • Velocity : l'agent poursuit l'objectif à cette vitesse (en m/s).
    Malheureusement, aucune accélération n'est prévue dans l'actuator, l'agent se met en mouvement instantanément.
  • Acceleration & Turn Speed : ces options sont utilisées uniquement pour l'évitement des obstacles mobiles que nous n'aborderons pas ici. 
  • Facing/Axis/N : ces options définissent l'orientation de l'agent pendant la poursuite. Pour obtenir un agent qui se tourne dans le sens du chemin, nous cocherons l'option Facing et choisirons dans Axis l'axe local qui pointe vers l'avant de l'agent. L'option N n'est utile que si le navigation mesh n'est pas horizontal.
    De même que pour la vitesse, aucune rotation progressive n'est prévue. L'agent se réoriente instantanément à chaque angle de la trajectoire.
  • Self Terminated : en cochant cette option l'actuator se désactive automatiquement si l'objectif est atteint. Autrement dit, la poursuite s'arrête même si l'objectif se déplace à nouveau hors de la distance d'arrêt. Cette option est très utile pour faire une logique chaînée à peu de frais. Cette technique est expliquée plus loin dans ce chapitre.
  • Update period : cette option définit intervalle de temps en millisecondes entre deux re-calculs de la trajectoire pour tenir compte du déplacement de l'objectif. La valeur par défaut est 0, ce qui implique un re-calcul à chaque frame. Ça ne dérange pas pour notre petit test, mais pour des jeux plus complexes avec beaucoup d'agents, c'est très certainement excessif. Une valeur de 100 à 500 est plus raisonnable, à tester au cas par cas.
  • Visualize: Permet de visualiser en temps réel dans le jeu la trajectoire de l'agent sous la forme d'une ligne rouge.

Un exemple de comportement complexe par briques logiques

Nous savons maintenant comment faire un agent qui poursuit inlassablement un objectif qui se déplace dans un niveau (dans notre jeu, ce sera le piège qui poursuit le panda) mais nous aimerions lui donner un comportement plus typique d'un méchant dans les jeux. Nous voulons que l'agent fasse une ronde mais que si l'objectif s'approche à une certaine distance, différente par devant et par derrière, il se mette à sa poursuite jusqu'à ce que l'objectif soit atteint ou qu'il s'éloigne au-delà d'une certaine distance, auquel cas l'agent reprend sa ronde.

Utilisation d'une machine à état

Nous allons utiliser les notions de machine à état que nous avons vues au chapitre précédent pour modéliser l'agent que nous venons de décrire. Dans notre exemple nous tenterons de nous limiter à deux états plus un état terminal :

  • la ronde ;
  • la poursuite ;
  • la fin du niveau.

Les événements sont :

  • 1->2 : l'objectif s'approche assez près de l'agent ;
  • 2->1 : l'objectif est trop loin de l'agent ;
  • 2->3 : l'agent atteint l'objectif.
Les termes assez près et trop loin sont volontairement flous car ils n'influent pas sur la logique. Nous réglerons les valeurs précises dans les sensors de détections que nous utiliserons pour produire les événements.

Événement de proximité par brique logique

Les détections de proximité, dont nous avons besoin pour produire les événements de notre machine à état, seront effectuées par des sensors Near etRadar. Le sensor Near détecte la présence d'objet dans un rayon déterminé par le paramètre Distance.

Le sensor Radardétecte la présence d'un objet dans un cône orienté dont la pointe est centrée sur l'objet. On peut choisir l'ouverture du cône avec le paramètre Angle, la hauteur avec le paramètre Distance et l'orientation avec l'option Axis (les axes sont ceux de l'objet). Bien orienté, ce type de sensor simule la vue d'un agent.

Les sensors Near et Radar ne détectent que les objets dont l'option Actorest activée dans le panneau Physics ! C'est un oubli fréquent qui peut causer des soucis. Si nous avons un doute sur la taille et l'orientation des zones de détections, il nous suffit d'activer la visualisation de la physique (menu principal> Game > Show Physics Visualization).

Navigation mesh utilisé dans Blender Game Engine

Ces deux sensorspeuvent être rendus très sélectifs grâce au paramètre Property. En mettant un nom dans ce champ, seuls les objets qui ont une propriété (Game Property) de ce nom seront détecté par le sensor.

Comment faire exécuter une ronde à un agent, à peu de frais ?

Pour les états 1 et 2 nous pouvons utiliser l'actuatorSteering pour déplacer notre agent, il suffit de choisir l'objectif : pour la ronde ce sera un simple Empty qui sert de point de passage (inutile de surcharger le BGE avec un objet solide alors que nous avons juste besoin d'une balise) et pour la poursuite, ce sera le joueur (avec une vitesse plus rapide pour simuler la course). Cependant pour la ronde, un problème se pose: un seul point de passage n'est pas suffisant, il en faut au moins 3 pour faire une boucle (2 pour un aller-retour). Nous avons donc besoin de 3 actuators (1 par point de passage) et nous devons trouver le moyen de les activer en séquence pour que l'agent aille d'un point de passage à l'autre.

A priori il nous faudrait plus d'états pour réaliser cela mais une solution élégante est possible grâce au sensorActuator. Il s'agit d'un sensor qui surveille l'état d'un actuator en particulier (d'où son nom) et il produit une impulsion logique, positive ou négative, lorsque l'actuator en question devient respectivement actif ou inactif.

L'actuator sous surveillance est nécessairement un actuator de l'objet. On le sélectionne par son nom, d'où l'intérêt de choisir des noms parlants pour les briques logiques.

Ce type de sensor est générique, il fonctionne avec tous les actuators, en particulier avec ceux qui se désactivent spontanément comme Action et  Constraint > Force Field. Il permet d'une manière générale de chaîner les actions: lorsqu'un actuator termine son travail, un autre peut s'activer grâce au sensor.

États visibles et initiaux

Si nous changeons le state d'un contollersans autre précaution, nous aurons la surprise de le voir disparaître ainsi que toutes les briques auxquelles il était connecté. Pas de panique, les briques n'ont pas été supprimées, elles sont simplement invisibles car Blender n'affiche par défaut que les briques de l'état 1. Nous pouvons choisir quels états seront affichés grâce à l'outil de sélection des états. L'outil est lui-même invisible par défaut, il faut cliquer sur le petit+à coté de l'entête des controllers pour le voir.

Les boutons en face de Visible indiquent quels sont les états actuellement affichés et ceux en face d'Initial indiquent les états actifs au démarrage du jeu (il en faut au moins un). Dans notre exemple, ce sera l'état 1.

Etat 1 : le résultat

Nous avons maintenant toutes les données en main pour réaliser notre état 1 : la ronde. Nous avons choisi de donner un nom qui commence par 's' pour tous les sensors et 'a' pour tous les actuators.  Comme un dessin vaut mieux qu'un grand discours voici le résultat.

Nous avons mis un sensor sDebut1 qui est un Delay pour démarrer le mouvement après une petite pause pour faire joli. Cette pose sera visible au démarrage mais aussi chaque fois que l'état 1 sera réactivé par la suite.

Ensuite les 3 actuators Steering, aPoint1, aPoint2 et aPoint3 pour les 3 points de passages et les 3 sensors sPoint1, sPoint2 et sPoint3 associés qui forment une boucle, comme expliqué plus haut. Remarquons que les controllers connectés aux sensors sont des Nand (c'est-à-dire qu'ils inversent le signal logique). En effet, il faut démarrer le segment suivant dans la ronde (donc il faut une impulsion logique positive) quand le segment précédent s’arrête (ce qui produit une impulsion négative dans le sensor), d'où le besoin d'inverser le signal logique.

Les sensors sEnVue et sProche sont à l’affût du joueur: notons que nous avons écrit playerdans le paramètreProperty car le joueur à précisément une propriété du même nom et que c'est le seul objet dans ce cas: seul le joueur sera détecté (il faut aussi l'option Physics > Actor pour le joueur sinon ça ne marche pas !). Les 2 sensors sont connectés au même controller Or car il suffit que l'un ou l'autre détecte le joueur pour que l'événement de transition de l'état 1 vers l'état 2 se produise. La transition est effectuée par l'actuator sEtat2.

Etat 2 : le résultat

Voici notre état 2.

Notons le sensor Always qui active immédiatement le comportement de poursuite. Les options de l'actuator aPoursuite sont calculées pour aller plus vite que dans la ronde mais moins vite que le joueur, sinon nous n'aurions aucune chance de gagner. Le sensor sObjectif avec son controller Nand détecte la fin de aPoursuite et donc la fin du jeu puisque le joueur a été rejoint. Nous voyons que l'état 3 ne doit pas être réellement implémenté, il suffit d'un actuator pour terminer le jeu. Le sensor sTropLoin détecte quand le joueur est proche de l'agent, or nous avons besoin du contraire pour générer l'événement qui nous ramènera à l'état 1, d'où l'inversion par un controller Nand.

Conclusion et derniers conseils

Nous sommes arrivés au bout de notre tâche avec un minimum de briques logiques. Cet exemple montre que des comportements assez évolués sont réalisables facilement. Cependant la complexité peut augmenter rapidement et il devient nécessaire de comprendre ce qui se passe pendant le jeu. Nous avons la possibilité d'afficher des valeurs dans la fenêtre de jeu très simplement :

  1. Menu principal > Game>Show Debug Properties.
  2. Pour afficher l'état courant d'un objet.
  3. Pour afficher la valeur d'une propriété d'un objet.
  4. Penser à utiliser l'actuatorProperty pour changer la valeur d'une propriété pendant le jeu en vue du débogage.

Scrypthon !

L'algorithme de navigation est accessible par Python. Il est possible de ré-implémenter tout ce qui précède entièrement en Python et nous aurions en prime la possibilité de palier aux limitations de l'actuatorSteering : par exemple obtenir une accélération progressive et des rotations douces.

Imaginons une situation assez classique avec un ennemi poursuivant notre personnage. Celui-ci pour tenter de s'échapper active alors un pouvoir rendant collant le sol derrière lui. Lorsque l'ennemi marcherait sur ce sol collant (on détectera cela avec une collision), sa vitesse se réduirait. Une fois ressorti de la zone collante, il reprendra alors sa vitesse de départ.

Concernant la mise en place, on a décidé de détecter la collision du côté ennemi. Nous avons donc commencé par créer un sensor Collision qui ne s'active que lorsque notre ennemi entre en collision avec un décor de type sol magique (nous avons réussi cela en dotant notre décor sol magique d'une propriété et en filtrant le sensor avec cette propriété).

from bge import logic 

cont = logic.getCurrentController()
enemy = cont.owner

e_type = enemy['enemy_type']
sensor = cont.sensors["magic_collision"]
player_steering = enemy.actuators["Steering_player"]

if not sensor.positive:
     player_steering.velocity = enemy.normal_velocity
else:
     player_steering.velocity = enemy.normal_velocity - 1.0

Nous commençons, très classiquement, par l'import des modules. Nous récupérons ensuite le type d'ennemi ainsi que le sensor de collision qui a, dans notre exemple le nom de "magic_collision". Comme nous devons modifier la vitesse de poursuite de notre ennemi, il nous faut la référence de l'actuator de poursuite.

Ensuite nous n'avons plus qu'à modifier la vitesse de celui-ci. La valeur sensor.positive indique si la collision commence ou si elle vient de se terminer. Si elle commence, on réduit la vitesse, si elle vient de se terminer, on rend à l'ennemi sa vitesse normale. Tous les sensors possèdent d'ailleurs l'attribut positive qui indique si l'événement détecté est réalisé ou pas. Ceci est décrit plus en détails dans le chapitre Comprendre les briques logiques de la section Développer l'ergonomie.

Il y a une erreur de communication avec le serveur Booktype. Nous ne savons pas actuellement où est le problème.

Vous devriez rafraîchir la page.