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.
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.
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.
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 :
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 :
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.
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 :
Les autre options définissent les aspect dynamiques du mouvement :
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.
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 :
Les événements sont :
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).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.
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.
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.
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.
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.
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 :
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.