XIX. Définir les caractéristiques du monde▲
À la base, notre espace de jeu est simplement constitué d'un certain nombre de polygones virtuels, flottant dans le vide. Pour leur donner de la consistance et de la « matière », le moteur de jeu incorpore un simulateur de « lois physiques » incluant notamment la gravité, les collisions, des unités, et beaucoup d'autres paramètres et comportements. Dans le mode Blender Game, l'onglet World est radicalement différent de celui que l'on trouve lorsqu'on est en mode Blender Render. C'est là que se trouvent les options concernant le moteur physique, sous le panneau Physics.
Dans l'onglet World, on peut également changer la couleur du fond (Horizon color), donner un éclairage de base ou une couleur d'ambiance (Ambient Color), et on peut même ajouter un effet de brouillard dans l'onglet Mist. Mais au-delà de ces options habituelles, il faudra définir le comportement général de notre univers de jeu.
XIX-A. Changer les lois du monde : le moteur physique▲
La première option de l'onglet Physics est tout simplement de choisir le moteur physique. Blender ne supporte actuellement que le moteur Bullet*, le seul autre choix Noneest donc de désactiver la physique. Ce choix a du sens si le jeu ne nécessite aucune sorte de physique : le panneau Physics des objets existera toujours, mais les valeurs ne seront pas utilisées dans le jeu.
Si l'interface utilise une liste qui peut se dérouler au lieu d'une simple case à cocher, c'est pour permettre de compléter la liste avec d'autres moteurs physiques. Comme nous avons le choix entre une interface Cycles ou Blender Internal, dans des anciennes versions, il était possible de sélectionner le moteur physique Sumo ou dans des versions spéciales d'utiliser le moteur Open Dynamics Engine.
XIX-B. Définir la gravité▲
La gravité a un impact direct sur certaines capacités de mobilité dans le jeu. La valeur par défaut affichée par Blender est 9.80 unités, ce qui correspond à la gravité terrestre de 9,80 m/s². Le résultat sera donc le plus naturel pour les joueurs. Il s'agit de définir les forces fondamentales que le monde impose aux personnages et aux objets pour définir la pesanteur. Ainsi, pour un jeu qui se passerait dans l'espace ou sur une autre planète, le changement de gravité pourrait être un paramètre particulièrement actif et affecter fondamentalement la jouabilité. Plus la gravité est faible plus les chutes seront perçues comme lentes. Une gravité à 0 fera que les objets ne chuteront pas. Ce réglage n'affectera pas les objets de type static. Pour les autres, la modification de la masse dans les propriétés physiques pourra être un moyen de compenser individuellement la force qu'impose la gravité selon les situations.
XIX-C. La fluidité de l'affichage : gérer la fréquence de rafraichissement▲
Le FPS (Frame Per Second) est une option essentielle qui définit le nombre d'images que le jeu va afficher par seconde pour donner au joueur l'illusion du mouvement, de la même façon qu'on le fait pour un film. La valeur par défaut, 60 images par seconde, est souvent acceptée comme standard pour garantir un confort de jeu. Il est bien sûr possible de la diminuer si nécessaire.
Cependant, il arrive que le jeu soit graphiquement trop gourmand en ressources et qu'une telle fréquence de rafraichissement d'image soit impossible à atteindre. Dans de telles situations, Blender va donner en priorité les ressources de calcul au moteur physique et à la logique, quitte à perdre quelques images par seconde. Rassurez-vous, la sensation d'images saccadées (ou de « lag ») n'est perceptible qu'en dessous de 24 ou 25 images par seconde, ce qui nous laisse un peu de marge. Les options Max de Physics steps et Logic steps déterminent combien de rafraichissements physiques et logiques vont être intercalés entre deux rafraichissements d'image, en cas de manque de ressources.
Il faut comprendre que le moteur physique va calculer les événements de manière séparée de l'affichage : le paramètre Substeps de Physics steps détermine le nombre d'itérations* des calculs de physique entre le calcul des images. Plus il y aura d'étapes calculées, plus la simulation physique sera précise, mais aussi plus votre processeur aura du travail. Par exemple, si Substeps est réglé à 2, il y aura deux calculs de physique entre chaque image, donc 120 par seconde en laissant les FPS à 60. Ces options sont donc à considérer dans l'optique de l'optimisation du projet et elles sont généralement laissées à leur valeur par défaut pour commencer.
XIX-D. Optimiser la physique pour accélérer les calculs▲
Les deux options suivantes concernent l'optimisation du moteur.
Occlusion Culling est une technique avancée qui permet de ne pas afficher des objets cachés par d'autres. Bien qu'il s'agisse d'une technique graphique, elle utilise accessoirement le moteur physique, ce qui justifie la présence de cette option dans ce panneau. Cette technique est décrite plus en détail dans le chapitre Ignorer les objets hors champ de la section Travailler le rendu du jeu.
Physics Deactivation est une option qui permet de choisir en dessous de quelle vitesse, linéaire et angulaire, un objet peut être considéré comme inactif. Un objet inactif n'est plus mis à jour par le moteur physique : il s'arrête brusquement. Par défaut les limites de vitesse sont assez élevées: 0.8m/s en vitesse linéaire et 1 radian/s en vitesse angulaire (soit environ 1 tour en 6s). L'effet peut être assez déroutant : un objet qui est nettement encore en train de bouger se fige subitement. Pour éviter cela, nous pouvons soit réduire les vitesses de seuil, soit mettre les principaux objets qui pourraient être affectés par ce problème (par exemple le personnage du jeu) en mode No Sleeping (cette option fait partie des paramètres physiques de l'objet). Un objet dont cette option est cochée ne devient jamais inactif et les objets qui sont en contact avec lui restent actifs tant qu'ils restent en contact.
XX. Le comportement des objets de notre monde▲
Un objet dans le Game Engine possède généralement une double représentation : d'une part la représentation graphique qui est identique à ce que l'on voit dans la 3D Viewet d'autre part une représentation physique qui est spécifique au Game Engine. Dans le cas de la représentation physique, on parle de modèle physique. Celle-ci est gérée automatiquement par Bullet, le moteur physique du BGE, et détermine le comportement de l'objet dans l'environnement du jeu, c'est-à-dire ses interactions avec les autres objets et personnages. L'ensemble des modèles physiques constitue le Physics World (monde physique) qui est spécifique à chaque scène.
L'affichage de la physique n'est pas visible sauf si l'on active l'option Show Physics Visualization du menu Game dans barre de menu principale. C'est une option de développement (debugging) qui sera souvent indispensable pendant la création du jeu. Alors que le moteur graphique du BGE s'appuie sur l'énorme puissance de calcul des cartes graphiques, le moteur physique tourne uniquement sur le processeur central de l'ordinateur du joueur et la question de l'optimisation est donc déterminante. Beaucoup d'options physiques ont un impact sur la performance du jeu et choisir les bonnes options pour les bons objets est crucial.
Comme nous l'avons vu dans le chapitre précédent, l'utilisation du moteur physique est indispensable pour toute détection de contact ou de collision.
XX-A. Créer une réalité avec les types de physique d'objet▲
Nous créons donc véritablement une simulation de monde : l'enjeu va être de lui donner assez de « réalité » pour que le joueur y croie, et que l'immersion dans l'univers du jeu ne soit pas cassée, tout en demandant au moteur physique le minimum de calculs, ce qui nous garantit une fluidité de jeu. Un jeu trop gourmand devenant injouable, à cause des ralentissements qu'il provoque, retombe sur le même défaut : le joueur « décroche » parce que l'immersion ne fonctionne plus et qu'il est distrait par le problème technique.
Nous visons donc un compromis technique : juste assez de réalisme physique pour donner l'illusion que le monde est crédible ! Pour cela, nous devrons choisir avec attention le « type » de physique que nous attribuons à chacun de nos objets.
Afin de définir le meilleur type possible, il est important de bien définir à l'avance ce que l'on attend de l'objet en question. Par exemple :
- l'objet est-il un élément qui intervient dans le jeu ? ;
- l'objet fait-il partie de l'interface ? ;
- l'objet est-il un élément de décor ? ;
- est-ce un personnage ? ;
- l'objet doit-il pouvoir être déplacé, et si oui avec quelle méthode ?
Les options physiques se trouvent dans l'onglet Physics de l'éditeur des propriétés Properties. Le point de départ est le type d'objet (Physics Panel> Physics Type), qui va changer la façon dont se comportent nos objets.
XX-A-1. Des objets indépendants et sans interactions avec No Collision▲
Ce type est le plus simple : l'objet n'a aucune représentation physique. Cela signifie qu'un tel objet ne peut interagir avec aucun autre objet du jeu, car il est « invisible » au moteur physique. Exemples d'utilisation typiques : affichage d'information, détails graphiques secondaires, version high poly (maillage dense) d'un objet dont on utilise une version low poly (maillage léger) pour le moteur physique.
XX-A-2. Des éléments de décor avec une physique Static▲
Nous utiliserons ce type pour des objets immobiles ou animés qui ne sont pas influencés par les autres objets de l'environnement. Exemple : le sol et les murs d'une pièce ou un ascenseur.
D'un point de vue technique, on peut considérer que ces objets ont une masse infinie : aucune force ne peut les faire bouger. En particulier, ils sont insensibles à la gravitation. En pratique, cela veut dire que ces objets ne bougent que s'ils sont animés ou déplacés à l'aide d'actuators ou de scripts Python. Le moteur physique tient compte de leurs nouvelles positions pour calculer les effets sur les autres objets.
Attention, il y a des optimisations du moteur physique spécifiques aux objets statiques. L'une d'elles est que les objets statiques ne se collisionnent pas entre eux : une collision éventuelle ne provoque aucun effet, et il n'est donc pas possible de détecter le contact d'un objet statique avec un autre objet statique.
XX-A-3. Des objets mobiles en type Dynamic▲
Ce type s'applique à des objets qui ont une masse et peuvent bouger sous l'effet de la gravité ou de forces, mais ne peuvent pas tourner ou basculer, du moins pas sous l'effet des contacts avec les autres objets du monde physique. Plus précisément, ils restent parallèles à eux-mêmes tant que l'on ne leur impose aucune rotation via d'une brique logique ou via Python.
Ce n'est donc pas un comportement physiquement réaliste, mais c'est utile pour les objets de type joueur ou PNJ* (Personnage Non Joueur) qui a priori ne doivent pas basculer, ou des objets secondaires tels que par exemple, une caisse ou un tonneau que le joueur peut pousser, mais qui ne se renverseront pas.
XX-A-4. Des objets réalistes avec la physique Rigid Body▲
Ce type d'objets est le plus physiquement réaliste : ils roulent et basculent comme les objets réels. Ils sont aussi les plus coûteux en calculs. À n'utiliser que pour des détails qui rendent le jeu réaliste : par exemple des projectiles rebondissant sur les murs de la salle, ou les briques d'un mur qui s'écroulent (attention, très gourmand en calcul !).
XX-A-5. Des objets mous avec Soft Body▲
La physique Soft Body convient pour des objets mous, par exemple des tissus ou des choses organiques, qui se déforment sous un impact.
À utiliser avec précaution, car ce type d'objets a de nombreuses limitations :
- techniquement, le moteur physique considère ces objets comme des surfaces déformables et non pas comme des volumes mous. Cela convient pour une tenture, mais beaucoup moins pour une masse molle ;
- aucune détection n'est possible sur ces objets : ils collisionnent avec l'environnement, mais les détecteurs ne les voient pas ;
- ils n'ont pas de position ni d'orientation précise ;
- ils ont la fâcheuse tendance à traverser les sols !
XX-A-6. Des objets occultant avec Occlude▲
Ce type d'objets fait partie des options avancées d'optimisation que nous ne décrirons pas ici.
Référez-vous au chapitre Ignorer les objets hors champ de la section Travailler le rendu du jeu pour voir cet aspect plus en détail. Sachez juste qu'un objet de type Occlude cache les objets qui sont derrière lui et évite d'envoyer au moteur graphique les objets qui sont ainsi entièrement cachés.
XX-A-7. Des objets détecteurs d'autres objets avec Sensor▲
Plus haut, nous avons dit que les objets statiques ne peuvent pas entrer en collision entre eux pour des raisons d'optimisation. Cependant, les objets de type Sensor sont semblables aux objets statiques (ils ne sont pas soumis à la gravité) tout en étant capables de détecter les objets statiques et dynamiques. Ces objets sont uniquement utilisés pour la détection : ils n'opposent aucune résistance et seront en général rendus invisibles.
Exemples d'utilisations :
- Une zone de détection de passage dans le jeu (comme une porte).
- Une ou plusieurs zones de « contacts » autour du joueur, par exemple pour détecter à quel endroit celui-ci est touché par un projectile lancé par des ennemis. Dans ce cas nous définirons plusieurs objets Sensors parentés au joueur, par exemple un pour la tête, un pour le torse et un pour les jambes. Un système de score déduira plus ou moins de points suivant le sensor qui aura détecté le contact avec le projectile. Un autre exemple est d'avoir un sensor qui définit à partir de quelle distance les PNJ* (ou seulement les ennemis) repèrent le personnage joueur.
XX-A-8. Définir un chemin de déplacement avec Navigation Mesh▲
Un objet de type Navigation Mesh (abrégé NavMesh) est utilisé pour la navigation des PNJ. Il sera associé à un autre objet et lui servira de carte de manière à l'aider à trouver un chemin dans un niveau. Nous traiterons plus amplement de l'utilisation de cette option dans le chapitre Recherche de chemin automatique à la fin de cette section.
XX-A-9. Des personnages avec la physique Character▲
Le type Character est spécialement conçu pour les personnages joueurs et non joueurs (PNJ) : le moteur physique applique une mécanique simplifiée qui évite de nombreux soucis, comme par exemple traverser le sol. Le chapitre suivant est entièrement dédié à ce type très important pour beaucoup de jeux.
XX-B. Modifier la physique dans le détail, avec les options des types d'objets▲
Les objets physiques ont plus ou moins d'options, suivant le type utilisé, qui nous permettront d'affiner leur comportement.
XX-B-1. Définir la masse de nos objets, avec l'attribut Mass▲
Ce réglage nous permet de définir la masse d'un objet dynamique, son unité est le kilogramme. Plus un objet a de masse, plus il est lourd, ce qui va bien sûr modifier la façon dont il va réagir à certaines forces du monde ou d'objets environnants.
XX-B-2. Des objets « fantômes » avec la case à cocher Ghost▲
Lorsque l'option Ghost est activée, l'objet permet à la logique de collision de fonctionner, mais la collision ne produit pas de résultat visible. L'objet ne renvoie alors aucune force aux objets avec lesquels il entre en collision. Autrement dit, la collision ne produit pas de choc, et les autres objets passent au travers des objets Ghost.
Il ne faut pas confondre cette option avec le type No Collision, car l'objet est présent dans le monde physique et peut donc être détecté par des Sensors de collision (rappelez-vous, il y a collision même si celle-ci ne produit aucune force).
Nous utilisons rarement l'option Ghost pour des objets statiques, car le type Sensor est normalement plus adapté et déjà tout prêt. Nous nous en servons donc plutôt pour des effets sur des objets qui ont déjà des physiques plus complexes. Ainsi, un exemple d'utilisation est l'effet passe-muraille : le personnage joueur acquiert temporairement un super pouvoir qui lui permet de traverser un obstacle, il va retrouver sa physique habituelle, mais temporairement il n'entre plus en collision avec le monde.
Vous l'aurez deviné, en Python les objets Sensors sont d'office de type Ghost.
# True rend l'objet invisible pour le moteur physique,
# False rend l'objet visible pour le moteur
my_object.game.use_ghost =
True
XX-B-3. Des objets que l'on heurte, mais que l'on ne voit pas avec le bouton Invisible▲
Cette option désactive la représentation graphique de l'objet : seule la représentation physique existe. L'objet n'est donc pas affiché dans le jeu, mais il peut entrer en collision avec d'autres. Parmi les objets dont la représentation graphique n'est pas souhaitable, on peut citer :
- l'objet low poly (maillage léger) qui représente le modèle physique d'un objet graphique high poly (maillage dense) ;
- un volume de détection d'intrusion, typiquement un objet Sensor.
L'invisibilité d'un objet n'est pas définitive. Cette option ne détermine l'invisibilité qu'au moment du démarrage du jeu. Ensuite, l'invisibilité se contrôle grâce à l'actuator Visibility ou grâce à Python :
# True rend l'objet invisible, False rend l'objet visible
my_object.hire_render =
True
XX-B-4. Des objets détectables avec la case à cocher Actor▲
L'objet est détecté par les Sensors Near et Radar. Mais cette option est obsolète, car on peut faire mieux et plus finement avec l'option Collision Mask que nous allons expliquer ci-dessous.
XX-B-5. Une gestion des collisions plus fine avec les Collision Group et Collision Mask▲
Cette série de boutons fait partie des options avancées du moteur de jeu de Blender : Ils permettent de choisir finement quels objets entrent en collision avec quels autres.
L'intérêt d'utiliser les groupes de collision est double :
- ils réduisent le nombre de collisions parasites détectées par les Sensors ;
- ils réduisent sensiblement le travail du moteur physique, puisqu'il doit calculer moins de collisions.
Les boutons Collisions Group (groupes de collisions) définissent à quel groupe l'objet appartient : c'est nous qui choisissons à notre guise la signification des groupes en sélectionnant des cases. Les boutons Collision Mask (masques de collision) définissent avec quels autres groupes d'objets notre objet sélectionné peut entrer en collision. Une collision ne peut avoir lieu entre deux objets que si au moins un groupe de l'un est également présent dans le masque de l'autre et réciproquement.
On peut donc définir ainsi des groupes d'objets qui ne réagissent qu'à certains autres groupes (ceux qui sont présents − case enfoncée − dans leurs masques). Par défaut, les objets appartiennent tous au premier groupe et entrent en collision avec tous les masques de groupes. Autrement dit, tous les objets se heurtent. Un cas pratique, pour un personnage joueur, serait par exemple d'avoir un groupe de collision qui détecte les PNJ amicaux, et un autre qui détecte les PNJ hostiles.
XX-B-6. Des dynamiques de forces pour les jeux avec des véhicules▲
Les paramètres de Velocity (vitesse), Damping (amortissement) et Anisotropic Friction (anisotropie frictionnelle) pour les objets de type Dynamic et Rigid Body vous permettront d'affiner la physique de vos véhicules, et sont particulièrement utiles dans des jeux de courses de véhicules.
XX-C. Des détections de collisions optimisées avec les Collision Bounds▲
Ce sous-onglet permet de définir la forme exacte du modèle physique de notre objet. Cela veut dire que le modèle physique ne doit pas nécessairement correspondre au modèle graphique. L'idée est de choisir une forme simplifiée pour le modèle physique pour simplifier considérablement le calcul des collisions, mais qui correspond suffisamment bien au modèle graphique, pour conserver une cohérence entre les deux.
XX-C-1. Des formes physiques simplifiées▲
Le choix des formes simplifiées est limité : boîte, sphère, cône, cylindre, capsule (un cylindre complété par deux demi-misphères). Dans le cas de la sphère, le rayon est déterminé par l'option Radius, dans le sous-onglet précédent, en dessous de l'attribut Mass. Dans les autres cas, le BGE calcule automatiquement la dimension de la forme qui remplit au mieux le volume de l'objet.
XX-C-2. Les formes physiques complexes dites de type Mesh▲
Dans certains cas, une forme simplifiée n'est pas possible, par exemple pour le sol et les murs d'une pièce, ou un objet dynamique de forme complexe. Dans ce cas le modèle physique est dit de type Mesh (maillage) et se rapproche du maillage de l'objet.
Deux types de formes complexes sont disponibles :
- Convex hull : cette forme est appropriée pour des objets dynamiques, car plus simple. Le modèle physique « emballe » le modèle graphique pour former une surface convexe ;
- Triangle mesh : à réserver aux objets statiques dont c'est par ailleurs le choix par défaut, car le modèle physique va reproduire exactement le modèle graphique. Il y a donc la meilleure précision possible au prix de plus de calculs, d'où l'importance de simplifier au maximum le maillage de l'objet lorsque l'on choisit ce type.
XX-C-3. Corrigeons les imperfections avec une marge sur notre modèle physique▲
Quelle que soit la forme, simple ou complexe, nous pouvons de plus jouer sur le paramètre Margin: c'est une épaisseur qui est ajoutée sur toute la surface du modèle physique pour l'augmenter. Cette marge nous permet de corriger les imperfections du moteur physique. Le but principal est d'éviter qu'un objet de petite taille ne passe à travers une paroi s'il est animé d'une grande vitesse. Par défaut la marge vaut 0.06, soit 6 centimètres. Mais il faut bien constater que le paramètre Margin ne résout pas tout : s'il y a vraiment un problème physique, il faudra probablement changer le type de physique de notre objet, ou jouer avec les groupes de collisions, comme nous l'avons vu plus haut.
XX-D. Note sur l'usage des unités dans le BGE▲
Par défaut, le moteur physique est réglé de telle sorte que l'unité de longueur est le mètre (une unité Blender = 1 m) et l'unité de masse est le kilogramme. Définir une valeur réaliste pour votre objet donne en général de meilleurs résultats.
XXI. Des personnages au bon comportement▲
Dans le Blender Game Engine, il est apparu que les contrôles de physique et d'animation classiques n'étaient pas efficaces pour des entités adaptées à certains comportements courants, ceux des personnages de jeux et ceux des séquences interactives. Des options spécifiques sont prévues à cet effet dans le BGE.
XXI-A. La physique des personnages▲
Comme nous l'avons vu dans le chapitre précédent, la physique plus ordinaire des objets statiques et dynamiques ne convient en général pas pour les personnages-joueurs. Les objets statiques sont insensibles aux forces et les objets dynamiques ont aussi leurs problèmes.
- Ils peuvent être bloqués par de minuscules obstacles (par exemple un accroc dans le sol).
- Ils peuvent parfois traverser les parois, y compris le sol, s'ils ont une vitesse élevée.
- Les objets de type Rigid Body roulent et basculent facilement, ce qu'on peut vouloir faire pour un personnage, mais qu'il est plus facile de contrôler avec une animation.
Pour ces raisons, un type de physique dédié aux personnages a été spécialement prévu dans le moteur physique, le type Character. Ce type est semblable à Dynamic, car il n'a pas de rotation, mais il résout les problèmes cités précédemment et permet un contrôle fin des mouvements du personnage joueur et des PNJ via des options spécifiques.
Step Height : hauteur de marche maximale que peut franchir le joueur dans son mouvement. Cela permet entre autres de grimper un escalier.
Attention, la capacité à franchir un obstacle dépend également de la vitesse du personnage face à cet obstacle, plus la vitesse est lente et plus le franchissement sera difficile.
Jump Force : littéralement force de saut. Le nom de ce paramètre est mal choisi, car il s'agit en fait d'une vitesse verticale (dont l'unité est le mètre par seconde) appliquée au joueur quand l'action Jump est exécutée via l'Actuator spécialisé (voir juste après). Une fois propulsé, le joueur retombe sous l'effet de la gravité.
Fall Speed Max : ce paramètre règle la vitesse maximale que le joueur peut atteindre s'il tombe en chute libre, ce qui permet de simuler le frottement de l'air.
XXI-B. Usage de cette physique dans les briques logiques▲
Une brique logique spécifique est prévue pour déplacer un joueur de type Character, l'actuator Motion >Character Motion (Mouvement > Mouvement de personnage).
Il faut utiliser cet actuator de préférence en Motion>Simple Motion (mouvement simple) si nous voulons bénéficier de la physique Character. Les options sont identiques, excepté l'option Jump qui applique l'impulsion spécifiée dans Jump Force (évoqué précédemment). Le Simple Motion fonctionne avec le type Character, mais ne permet pas de franchir les obstacles.
Et attention, le Servo Control ne fonctionne pas avec le type Character.
XXI-C. Scrypthon !▲
La brique logique Motion ne permet qu'un contrôle assez grossier du personnage : la vitesse est définie en incrémentant la position et est appliquée instantanément (il n'y a pas de phase d'accélération ni de décélération). Pour un contrôle fin, il faut accéder par Python à l'objet et utiliser les API spécifiques du type Character.
from
bge import
logic, constraints
# Récupère le contrôleur et son objet
cont =
logic.getCurrentController
(
)
own =
cont.owner
# Récupère le Character Physique
c =
constraints.getCharacter
(
own)
Après import des modules nécessaires, nous récupérons le contrôleur courant (la brique logique qui a lancé le script). Ce contrôleur possède des sensors et des actuators reliés à lui, mais surtout un owner (littéralement le propriétaire) : c'est l'objet qui possède la brique logique. Nous récupérons le owner de notre contrôleur dans une variable own car c'est lui qui contient tout notre objet, et donc aussi sa physique.
À la dernière ligne, nous récupérons l'expression python correspondante à la physique character de notre objet types.KX_CharacterWrapper, et nous avons ainsi accès à toutes ses méthodes et attributs.
- onGround : un booléen indiquant si le personnage est actuellement sur le sol.
- gravity : la valeur de la gravité utilisée par le personnage (par défaut : 29.4).
- maxJumps : le nombre maximum de sauts autorisés au personnage avant de devoir retoucher le sol (par défaut : 1 ; 2 pour un double-saut, etc.).
- jumpCount : le nombre de sauts en cours.
- walkDirection : la vitesse et la direction des déplacements du personnage en coordonnées globales [x, y, z]. On ne doit pas utiliser les autres méthodes de déplacement des objets classiques (localPosition, worldPosition, applyMovement, etc.).
- jump() : fait sauter le personnage.
Et voici un code complet de déplacement de personnage, avec une classe créée pour l'occasion :
from
bge import
logic, events, types, constraints
from
mathutils import
Vector
def
keyDown
(
kevt):
return
logic.keyboard.events[kevt] ==
logic.KX_INPUT_ACTIVE
def
keyHit
(
kevt):
return
logic.keyboard.events[kevt] ==
logic.KX_INPUT_JUST_ACTIVATED
class
Panda
(
types.KX_GameObject):
def
__init__
(
self, own):
# facteur vitesse du personnage
self.speed =
.1
# facteur sqrt(2) pour les diagonales
self.factor =
0.70710678
self.KX_Char =
constraints.getCharacter
(
self)
def
main
(
self):
self.move
(
)
def
move
(
self):
# vecteur y = 0, 1 ou -1
y =
keyDown
(
events.UPARROWKEY) -
keyDown
(
events.DOWNARROWKEY)
# vecteur x = 0, 1 ou -1
x =
keyDown
(
events.RIGHTARROWKEY) -
keyDown
(
events.LEFTARROWKEY)
# diagonal
if
x and
y:
self.KX_Char.walkDirection =
[x*
self.speed*
self.factor, y*
self.speed*
self.factor, 0.0
]
# Nord, Sud, Est, Ouest
elif
x or
y:
self.KX_Char.walkDirection =
[x*
self.speed, y*
self.speed, 0.0
]
else
:
self.KX_Char.walkDirection =
[0.0
, 0.0
, 0.0
]
# vecteur représentant la direction de déplacement du personnage
vec =
Vector
((
x, y, 0.0
))
if
x !=
0
or
y !=
0
:
# on aligne le personnnage au vecteur
self.alignAxisToVect
(
vec, 0
)
def
main
(
cont):
own =
cont.owner
if
not
"init"
in
own:
own["init"
] =
True
Panda
(
own)
else
:
own.main
(
)
- Nous commençons, comme toujours, par importer nos modules.
- Nous définissons deux fonctions qui nous seront utiles pour simplifier le code par la suite. keyHit(events.AKEY) renverra True si A vient juste d'être pressée (KX_INPUT_JUST_ACTIVATED). De même, keyDown(events.AKEY) renverra True si la A est enfoncée (KX_INPUT_ACTIVE).
- Nous déclarons une classe Panda qui hérite de types.KX_GameObject (l'objet élémentaire du BGE).
- Dans le __init__ nous déclarons les attributs (nous récupérons un KX_CharacterWrapper et nous le gardons dans le self.KX_Char).
- Nous définissons la méthode main, qui contiendra toutes les autres méthodes appelées (ici il n'y en a qu'une, mais nous pourrions en imaginer d'autres comme attack, anim, heal, etc.).
- Nous définissons la méthode move. Ici elle récupère les inputs (touches fléchées), déplace et oriente le joueur dans le bon vecteur correspondant. Arrive en dernier la fonction main, qui sera appelée par une brique logique.
- Nous récupérons le owner (KX_GameObject). Si celui-ci n'a pas été initialisé, nous le remplaçons par Panda(own), sinon le owner a maintenant été changé et on peut appeler sa méthode main.
Vous trouverez un exemple de fichier .blend de personnage animé à la fin du chapitre Animer des personnages dans le Game Engine de cette même section.
XXII. Des animations dans notre jeu▲
Dans un jeu, nous voulons nous amuser ! Des animations ajoutent de la vie et du fun, et de plus, dans un jeu, il est souvent essentiel de pouvoir déplacer certains objets de façon prédictible (ou « scriptée »).
Blender possède un système d'animation permettant de changer une multitude de propriétés des objets (position, rotation, couleur, etc.), et également un système de rigging (de construction de squelettes) permettant de déformer un mesh (objet maillé) complexe à l'aide d'armatures (ou squelettes). Les deux systèmes sont complètement utilisables en dehors du moteur de jeu et la plupart de ces types d'animations fonctionnent également dans le moteur de jeu, à l'aide de l'actuator Action.
XXII-A. Les images-clés d'animation (Keyframe) dans Blender▲
Dans Blender comme dans la plupart des logiciels d'animation, les animations sont gérées par un système d'images-clés qui enregistre à un instant T la position, l'orientation et la taille des objets (ou des os −bones− dans le cas des armatures). Les images intermédiaires, qui ne sont donc pas définies précisément, sont calculées automatiquement par Blender (ce qui s'appelle du tweening).
L'image courante (frame) est indiquée entre parenthèses en bas à gauche dans le viewport de la vue 3D et elle est colorée en jaune si l'objet sélectionné dispose d'une keyframe (image-clé) sur la frame active. L'image de départ (Start Frame) par défaut est la frame 1, mais il vaut mieux utiliser la frame 0 pour nos animations, car, par défaut, l'actuator action prend comme frame de départ 0. Pour changer la frame courante, il nous suffit d'appuyer sur les touches fléchées gauche et droite. Mais pour définir celle de départ il faut aller au numéro 0 dans la timeline (ligne de temps) et appuyer sur S (pour Start - départ).
Ce qui va définir la vitesse de nos animations est le paramètre Animation Frame Rate (vitesse d'animation par image), défini dans le panneau Render sous l'onglet Display.
Définir les caractéristiques du monde
Ce Frame Rate est séparé de celui auquel le jeu se rafraichit (voir le chapitre Définir les caractéristiques du monde de cette même section). L'animation sera jouée dans le jeu en respectant la Frame Rate de l'animation et non celle du jeu, à condition cependant que le jeu soit fluide, sinon la vitesse de l'animation est ralentie proportionnellement au ralentissement du jeu. Par défaut, elle est réglée à 24 images par seconde, qui est le standard que l'on retrouve au cinéma. Nous utiliserons les mêmes techniques d'animation pour faire un court-métrage dans Blender que pour faire un jeu utilisant le Game Engine. Pour plus d'informations sur l'animation par images-clés dans Blender, reportez-vous à un ouvrage dédié. Dans le Blender Store en particulier, il existe un livre de Tony Mullen en anglais Introducing Character Animation, 2.5 edition. Pour apprendre les concepts du rigging, le DVD de Nathan Vegdahl en anglais Humane Rigging est l'une des rares références les explicitant en utilisant Blender. Il est également disponible dans le Blender Cloud.
XXII-B. Relier nos animations à notre jeu avec l'actuator Action▲
L'actuator possède beaucoup d'options, mais les plus importantes sont :
- La sélection de l'action à jouer ;
- La définition des images de début et de fin de l'animation avec Start Frame et End Frame pour contrôler la plage de lecture de cette action ;
- Le type d'animation.
Nous allons détailler ces types d'animations un par un.
XXII-B-1. Les différents types d'animation▲
La première liste de l'actuator Action présente les différentes possibilités (ou types d'animations) offertes par Blender pour jouer les animations que nous aurons réalisées ou récupérées.
XXII-B-2. Play▲
Joue l'animation du début jusqu'à la fin. Déclencher à nouveau l'actuator n'a d'effet que lorsque l'animation est terminée, il faudra donc attendre sa fin pour déclencher une autre animation du même type.
XXII-B-3. Ping-Pong▲
Permet d'alterner des lectures à l'endroit et à l'envers : le premier déclenchement joue l'animation à l'endroit, le suivant à l'envers, etc. Comme pour le Play, on ne peut pas l'interrompre lorsqu'il est en cours.
XXII-B-4. Flipper▲
Joue l'animation tant que l'entrée est active, mais dès que l'entrée est à l'état logique Faux (False), l'animation se joue a l'envers pour revenir à l'état initial. Si l'entrée est réactivée alors que l'animation n'est pas revenue à l'image de départ (Start Frame), alors celle-ci reprend à l'endroit où elle était au moment de la réactivation.
XXII-B-5. Loop Stop▲
Tant que l'entrée est active, l'animation est jouée en continu, mais si l'entrée passe à Faux (False), alors l'animation se stoppe à l'image (frame) courante.
XXII-B-6. Loop End▲
L'animation se joue en continu du début à la fin tant que l'entrée est active, si l'entrée passe à Faux (False), l'animation ne se stoppe pas tout de suite, elle va jusqu'à la dernière frame d'animation puis s'arrête.
XXII-B-7. Property▲
Joue l'animation de l'image (frame) passée en paramètre de l'actuator, via une propriété du jeu Property, qui a été définie par ailleurs.
XXII-C. Notre première animation en jeu▲
Nous allons créer un objet flottant, avec un mouvement vertical plutôt lent de la forme d'un cycle de montée et descente. Prenons un simple cube avec 3 Keyframes : à 0, 24 et 48. De cette façon, à 24 images par secondes, chaque étape prendra exactement 1 seconde. Sur ces 3 Keyframes, la position ou la rotation peut varier, mais les premières et dernières doivent être identiques pour que l'animation puisse boucler.
- Élevons légèrement le cube par défaut et en Edit mode, réduisons de moitié sa largeur et sa profondeur avec les raccourcis clavier SXY.5.
- Plaçons un plan au centre du monde et agrandissons-le. Nous percevrons ainsi un peu mieux le mouvement, en particulier grâce aux ombres.
- Dans le layout Blender Game, éventuellement, créons une nouvelle vue par le bas dans la fenêtre centrale et choisissons l'éditeur de type Timeline.
- Changeons la valeur Start à 0 et End à 48. L'intervalle d'animation apparaît en plus clair dans la ligne de temps. Zoomons dessus en tournant la molette pour qu'elle occupe bien l'espace disponible.
- Passons à la première image de l'animation en utilisant le raccourci Maj + flèche gauche : 0 doit apparaître dans le champ placé à droite de End, et que nous aurions pu renseigner à la main.
- Vérifions que le cube est bien sélectionné, plaçons-nous dans la vue 3D et appuyons sur i puis choisissons Location, pour mémoriser l'emplacement du cube à cet instant de l'animation.
- Déplaçons la Timeline à l'image 24 et modifions légèrement la hauteur du cube avec g puis z puis appuyons à nouveau sur i > Location.
- À l'image 48, replaçons le cube à sa position initiale et faisons encore i > Location. Pour nous faciliter la tâche, nous aurions pu faire l'image 48 avant l'image 24 puisque les deux ont la même position, ou nous aurions aussi pu dupliquer la clé avec Maj + d dans le Dopesheet Editor.
- Nous pouvons jouer l'animation en faisant Alt + a. Appuyons sur Echap pour sortir.
- Pour que notre animation joue en permanence dans le jeu, créons un sensorAlways.
- Créons un actuator Action de type Loop endet dans le champ situé en dessous sélectionnons l'action Cube Action automatiquement générée par Blender.
- Enfin, paramétrons le début et la fin des images à montrer : Start Frame doit être à 0 et End Frame à 48.
XXII-D. Tout peut s'animer ! Animons donc une lampe▲
Gardez en mémoire que Blender est conçu pour pouvoir tout animer ! Ainsi Blender permet de créer des images clés (Keyframe) sur presque tous les paramètres, et il est possible d'enregistrer la valeur Energy d'une lampe au cours du temps en pressant la touche de capture i lorsque notre curseur de souris survole le paramètre Energy d'une lampe. Une variation très simple de l'exemple d'animation Loop end précédent est d'animer une lampe avec les mêmes briques logiques, afin de la faire clignoter.
Cette option est très pratique pour la création d'atmosphères dans notre jeu, ou permet de mettre en évidence des objets ou des effets. Les animations et les lumières attirent le regard du joueur, combiner les deux est donc très efficace pour capter son attention !
XXIII. Animer des personnages dans le Game Engine▲
Les objets animés, c'est intéressant, mais ce qui va vraiment enrichir notre jeu, c'est de donner vie à notre personnage ! Vous vous en doutez, il faudra utiliser une armature : grâce à elle, il va pouvoir courir, sauter et même danser. Nous devrons donc tout d'abord lui construire un squelette (Armature), composé d'os (Bones), puis lier notre maillage à ce squelette (Skinning et Weight Painting), comme nous pourrions le faire pour l'animation de personnages dédiés aux films. L'animation avec les armatures est donc identique à celle des objets, sauf que nous animons le maillage en déplaçant les bones ou leurs contrôleurs.
XXIII-A. Créer une bibliothèque d'animations pour notre personnage▲
L'idée principale pour les animations dans les jeux est de séparer nos animations en pistes différentes : une animation pour marcher, une animation pour sauter, une autre pour danser, etc. Cette méthode va nous permettre par la suite de mixer nos animations.
Le Dopesheet Editor va nous permettre de créer ces différentes actions qui pourront être jouées les unes à la suite des autres, voire même simultanément. Pour cela, nous devons aller dans l'Action Editor qui nous permettra de donner des noms aux différentes actions : en effet, il est indispensable de les nommer pour s'y retrouver par la suite. Ainsi, à chaque piste une animation : marcher, courir, sauter, etc.
Donc à chaque nouvelle action du personnage, on crée une action dans le Dopesheet Editor qui sera appelée via l'actuator Action.
Attention, quand on crée plusieurs pistes d'animation, il faut toujours cocher le petit F (Fake User*) à la droite du nom de l'animation, sinon celle-ci peut ne pas être enregistrée (si elle n'est pas utilisée).
XXIII-B. Un exemple avec un cycle de marche▲
Nous allons commencer par ajouter une action dans le Dopesheet Editor, que nous allons appeler walk_cycle (cycle de marche), qui sera le mouvement de déplacement du personnage. Tout d'abord, nous allons mettre le personnage dans une position de départ qui correspond à l'action. Dans le cas de la marche, nous ne pouvons pas commencer avec les deux pieds l'un à côté de l'autre sur le sol, car cela ne se peut pas lorsque nous marchons.
Si vous n'avez jamais fait d'animation, ou si n'avez pas de connaissances théoriques sur l'animation, nous vous recommandons la lecture du livre de référence dans le genre : Techniques d'animation pour le dessin animé, l'animation 3D et le jeu vidéo de Richard Williams.
Une fois dans la bonne position, nous allons sélectionner tous les bones et shapes de notre personnage et insérer une clé d'animation avec Insert KeyFrame (i dans la vue 3D), puis choisir LocRot (position et rotation).
Une fois la KeyFrame (clé d'animation) enregistrée, nous allons nous déplacer dans la timeline (ligne de temps) afin de poser d'autres KeyFrames, comme on le ferait pour une animation standard. Le Dopesheet editor nous permet d'affiner l'animation en dupliquant ou en supprimant des clés par exemple.
Une fois cette animation finie, nous pouvons en créer une autre à partir de la première en cliquant sur le + à côté du nom de l'animation. Cela duplique notre animation : nous pouvons alors la renommer et la modifier à votre guise.
Le Dopesheet Editor possède des outils comme les marqueurs, ou différentes visualisations des keyframes, qui aident à organiser l'animation.
XXIII-C. Lire l'animation de marche dans le jeu▲
Pour jouer une action avec les briques logiques, il nous suffit d'ajouter un actuator de type Action et de lui mettre en paramètre le nom de l'animation que l'on vient de créer, walk_cycle. Comme notre animation doit être jouée en continu jusqu'au relâchement du bouton, nous allons mettre son Play mode en Loop End (comme nous l'avons détaillé dans le chapitre précédent à propos des objets).
Ensuite, il nous suffit de brancher directement l'actuator d'animation au sensor de la touche de déplacement, et notre personnage se déplace en marchant. Le controller de type And va être ajouté automatiquement.
XXIII-D. Si votre animation ne semble pas fonctionner dans le jeu▲
Il est possible que vous ne voyiez pas votre animation en lançant le jeu, et que le personnage ne bouge pas. Si c'est le cas, c'est probablement parce que le modifier Armature n'est pas le premier de la liste. Mais comme nous l'avons expliqué dans le chapitre Spécificités du Game Engine dans Blender de la section Introduction, il est conseillé d'appliquer vos modificateurs avant de publier votre jeu, afin d'avoir de meilleures performances.
XXIII-E. De la fluidité et des transitions entre deux animations▲
Blender est capable de changer d'animation de façon fluide. Cela rend les mouvements d'un personnage beaucoup plus naturels et évite l'effet saccadé (l'impression de sauts d'images) lors du changement d'animation. Pour les mettre en œuvre, nous allons avoir recours aux propriétés Blendin et Priority qui se trouvent dans l'actuator de type Action.
Le Motion blending (mixage) indique sur combien d'images (frames) la transition entre les deux animations se déroulera. Cela permet de bien lisser le changement. La priorité, quant à elle, sert à faire un choix : si deux animations sont actives en même temps sur une armature, c'est la priorité avec la valeur la plus faible qui passe en premier.
XXIII-F. Et même plusieurs animations simultanément !▲
Pour jouer deux animations, par exemple pour gérer le haut et le bas d'un personnage séparément, il nous suffit de mettre deux actuators jouant les actions voulues et de mettre les actions sur différents calques (les layers de l'actuator). Nous pouvons utiliser jusqu'à 8 calques (Layer de 0 à 7).
XXIII-G. Scrypthon !▲
Nous pouvons également lire des actions en Python. Le script doit être lancé depuis une armature.
from
bge import
logic
def
anim1
(
):
own =
logic.getCurrentController
(
).owner
own.playAction
(
"boy_idle"
, 4
, 188
, 1
, 2
, 6
, logic.KX_ACTION_MODE_LOOP)
if
not
any(
logic.keyboard.active_events):
own.stopAction
(
6
)
own.stopAction
(
4
)
def
anim2
(
):
own =
logic.getCurrentController
(
).owner
own.stopAction
(
1
)
own.playAction
(
"boy_walk"
, 5
, 62
, 0
, 1
, 12
, logic.KX_ACTION_MODE_PLAY)
Ressource : animation_python.blend
L'idée est de jouer deux animations différentes : une animation de marche et une animation d'attente statique (idle). Nous allons jouer les animations sur des calques (layers) différents. On utilise pour cela la méthode play Action :
self.playAction
(
name,
start_frame,
end_frame,
layer=
0
,
priority=
0
,
blendin=
0
,
play_mode=
KX_ACTION_MODE_PLAY,
layer_weight=
0.0
,
ipo_flags=
0
,
speed=
1.0
,
blend_mode=
KX_ACTION_BLEND_BLEND
)
On peut ensuite stopper toutes les animations jouées sur un calque avec stopAction(layer).
XXIII-H. Ajouter des déplacements▲
Nous venons de voir comment faire jouer des animations à un personnage, Nous pouvons maintenant lui ajouter les fonctions de déplacement vues dans le chapitre précédent Les personnages pour le terminer.
Ressource : personnage.blend
XXIV. Inversion de cinématique dans le Game Engine▲
L'inverse kinematics (IK dans le reste de ce chapitre) ou en français inversion de cinématique est le terme consacré qui désigne le procédé mathématique pour calculer la pose d'une armature qui doit réaliser un objectif imposé à son extrémité (on parle de contrainte). Cette technique est généralement perçue comme un truc pratique pour faciliter l'animation : nul besoin de contrôler un à un tous les os d'une chaîne, il suffit de contraindre le dernier et l'algorithme se charge de disposer les autres.
Ce serait cependant une erreur de croire que l'IK dans le BGE se résume à une aide à l'animation. C'est aussi un moyen puissant pour faire interagir nos personnages avec l'environnement. L'IK peut être en effet exécuté indépendamment de toute action, il suffit pour cela d'utiliser l'actuator Armature :
Le mode Run Armature exécute simplement l'IK avec les contraintes et les objectifs actuels (par défaut ceux qui étaient actifs dans le 3D view avant de lancer le jeu).
Si l'actuator n'est pas actif, l'IK n'est pas exécuté et l'armature garde la pose qu'elle avait au début du jeu ou lorsque l'actuator a cessé d'être actif. Cet actuator n'est pas nécessaire si l'IK est exécuté dans le cadre d'une Action (voir le chapitre Des aminations dans notre jeu de cette section).
Les autres modes de l'actuator permettent de manipuler les contraintes d'IK : Enable, Disable, Set Target, Set Weight. Ces termes font directement référence aux paramètres de l'IK dans Blender : il est possible de les modifier dans le BGE en temps réel. L'intérêt de l'IK en tant qu'outil d'interaction est qu'il est tout à fait possible de choisir comme target un objet extérieur au personnage. Par exemple, si nous considérons un personnage avec une contrainte d'IK sur son squelette de tête qui lui fait de tourner la tête vers un objectif et si nous lui donnons comme objectif la caméra du jeu, nous obtiendrons un personnage qui regarde en permanence la caméra même si celle-ci se déplace.
Pour améliorer encore l'interaction, il existe un sensor Armature qui détecte des changements dans une contrainte de l'IK.
- State Changed : la contrainte est devenue active ou inactive. La sortie du sensor est l'état de la contrainte.
- Lin (Rot) Error bellow (above) : détecte que la contrainte remplit ou ne remplit pas l'objectif en angle ou en position. Cela permet de déclencher un comportement lorsque l'objectif est atteint ou est hors d'atteinte de l'armature. Dans l'exemple précité du personnage qui regarde la caméra, nous pourrions déclencher un mouvement si la tête atteint les limites des articulations. Note : ce test ne marche qu'avec iTaSC (voir ci-après).
Toutes les actions réalisées par ces deux briques sont accessibles par Python. Il est également possible de contrôler une armature entièrement par Python en donnant directement les angles de chaque articulation.
XXIV-A. iTaSC comme IK privilégié dans le BGE▲
Il existe deux algorithmes d'IK dans Blender : Standard et iTaSC. Les deux sont utilisables dans le BGE. Le premier est l'algorithme historique de Blender qui convient bien aux animations et aux armatures simples. iTaSC est un algorithme plus récent et plus élaboré qui possède des propriétés particulièrement intéressantes pour le BGE.
- Mémoire d'état
Contrairement à Standard, iTaSC garde en mémoire l'état de l'armature d'une frame à l'autre. Cela garantit une bien meilleure continuité des mouvements pendant le jeu et une bien meilleure performance puisque quelques itérations de l'algorithme sont suffisantes pour suivre le déplacement de l'objectif depuis la pose précédente.
Note : nous devons choisir le mode Simulation pour obtenir ce comportement. - Multicontraintes
Contrairement à Standard qui ne considère véritablement que la dernière contrainte d'une armature (la contrainte la plus éloignée du début de la chaîne), iTaSC est capable de satisfaire simultanément plusieurs contraintes sur la même armature. Si ces contraintes ne peuvent pas être toutes réalisées en même temps, iTaSC réalise un compromis que nous pouvons régler avec les paramètres Weight.
Note : une limitation dans iTaSC impose que toutes les chaînes d'os contraints qui ont des os en communs partagent le même os d'origine. Dans ce cas il n'est pas absurde que la totalité de l'armature soit contrainte. -
Contraintes exotiques
Au contraire de Standard qui ne peut résoudre qu'une contrainte de position (et optionnellement d'orientation), iTaSC accepte une plus grande variété de contraintes :- contrainte de position et d'orientation partielle : seules certaines composantes de la position et de l'orientation sont contraintes avec le choix de la référence (armature ou objectif). Cela permet d'intéressants effets d'alignement sur un plan, sur un axe ou de parallélisme.
- contrainte de distance : l'os est maintenu à une certaine distance de l'objectif.
- contrainte d'articulation : un effet de ressort peut être appliqué sur les articulations pour les contraindre à rester au plus près de la pose de repos.
D'autres types de contraintes pourraient être ajoutés dans le futur, car iTaSC introduit un concept de contrainte généralisée. - Armature complexe
Contrairement à Standard, qui donne des résultats souvent médiocres (lent et incorrect) quand il est utilisé sur une armature entière, iTaSC produit généralement des poses correctes et permet un contrôle global d'une armature avec un minimum de points de contrôle. - Dynamique réaliste
Bien qu'iTaSC soit un algorithme cinématique (sans notion de force ou d'inertie), on peut obtenir un comportement pseudo dynamique en jouant avec le paramètre Max Velocity qui limite la vitesse de rotation des articulations : on obtient une armature « lente » qui peut prendre du retard sur l'objectif. - Retour d'information
Contrairement à Standard, iTaSC produit un retour d'information qui permet d'évaluer si une contrainte est réalisée ou non et dans quelle mesure. Cette information est utilisable comme entrée dans le sensor Armature prévu à cet effet.
Malheureusement, un bogue interdit d'utiliser iTaSC dans le BGE dans les versions 2.70 et 2.71. Ce bogue est déjà corrigé, mais la correction ne sera disponible qu'à partir de la version 2.72.
Il sort du cadre de ce livre d'expliquer iTaSC en détail. Une documentation détaillée est disponible en anglais: http://wiki.blender.org/index.php/Dev:Source/GameEngine/RobotIKSolver
Ce .blend fait la démonstration du mode interactif d'iTaSC : l'objet central se déforme et tourne sur lui-même si nécessaire pour suivre l'objectif. [lien vers : game_armature.blend].
XXV. Génération d'objets▲
Dans ce chapitre, nous allons voir comment générer des objets à la volée. Typiquement, c'est le cas du gorille qui lance toutes les dix secondes un tonneau vers notre héros, des caisses de munitions que l'on retrouve à intervalle régulier dans l'arène, ou des lemmings qui arrivent par groupe de 20 dans notre grotte, etc. En somme, il n'y avait rien, puis soudainement, quelque chose est là.
XXV-A. Définition du Spawning▲
Le fait de créer un objet ou un personnage en direct et en cours de jeu est ce que l'on appelle, dans le jargon du jeu vidéo, du spawning. Le spawning est donc le fait de typiquement faire apparaître un élément de jeu comme s'il venait d'être créé. Ces éléments existent donc bien dans l'univers du jeu et ont été créés par les concepteurs, mais ils n'apparaissent à l'écran et n'interagissent avec l'environnement du jeu qu'à partir du moment où la logique du jeu le nécessite. L'avantage des méthodes de spawning, c'est que l'on peut faire apparaître tout le temps de nouvelles copies d'objets, indéfiniment ou tant que c'est nécessaire.
Dans un jeu de survie par exemple, dont le but est de rester en vie le plus longtemps possible devant une attaque de zombies, peu importe combien de zombies tuera le héros, il y aura toujours de nouveaux zombies qui apparaîtront à l'horizon. En pratique, le créateur de jeu aura donc défini plusieurs modèles de zombies possibles (avec des apparences, des capacités et des modes de déplacement différents). Puis la logique de jeu ira pêcher dans cette bibliothèque d'ennemis pour créer cette invasion constante de morts-vivants.
Ce qu'il faut comprendre ici, c'est que nous avons donc un modèle, un objet de référence, et le spawning est la technique qui va permettre de créer des copies de ce modèle aux bons endroits du jeu. Toutes les copies, qu'on peut comprendre comme des clones, ou qu'on appelle aussi des instances, ont leur vie propre à partir du moment où elles sont créées. Ces instances, bien qu'elles partagent les mêmes propriétés lors de leur création, évoluent de façon indépendante une fois dans le jeu.
Pour ce chapitre, nous utiliserons un exemple très simple d'un canon qui tire des boulets lorsque le joueur appuie sur la barre d'espace. Le nombre de boulets est infini. Le joueur peut en tirer autant qu'il veut.
XXV-B. Placer des objets dans un calque non visible▲
Préparons notre scène de manière à être plus à l'aise au moment de la génération. Créons les objets de base et organisons-les de manière à y accéder facilement sans qu'ils gênent le jeu. De plus, il est impossible pour le BGE d'ajouter à la volée des objets présents dans un calque actif, il faudra en tenir compte.
- Dans notre scène, de manière visible, nous n'avons qu'un canon, représenté par un cylindre. Nous allons ranger dans un calque (layer) non visible du projet, les éléments que nous ne voulons pas voir apparaître dans le jeu, mais que nous désirons générer à la volée. Dans ce cas précis, il s'agira de nos boulets de canon. Si nous oublions cette étape, le BGE ne sera pas capable de générer l'objet et renverra une erreur.
- Vérifions les propriétés de physique du canon et assurons-nous que Ghost est coché de manière à ce que le canon ne retienne pas les boulets.
- Créons maintenant un objet sphère qui sera notre boulet de canon. Nous donnons à cet objet le nom : Boulet. Cela simplifiera la manière de le retrouver lorsque nous voudrons créer de nouveaux boulets pendant le jeu.
- Concernant toujours notre objet Boulet, dans l'onglet Physics de la fenêtre Properties, nous lui donnons une physique de type Dynamic afin que notre boulet se comporte comme un projectile réel.
- Revenons ensuite dans notre calque principal où nous avons placé notre canon. Assurons-nous que le calque où se trouve le boulet est bien désactivé ou, autrement dit, invisible.
XXV-C. Générer des instances▲
Nous allons attacher à notre objet canon la logique qui permet de tirer des boulets de canon lorsque le joueur appuie sur la barre d'espace.
XXV-C-1. Créer un objet▲
- Sélectionnons le canon et ajoutons un sensor de type keyboard que l'on configure pour s'activer lorsque l'on appuie sur la barre d'espace.
- Relions-le à un controller de type And que l'on relie ensuite à un actuator de type Edit Object.
- Par défaut, cet actuator est configuré pour ajouter des objets à la scène. C'est le mode Add Object. Nous devons ensuite lui spécifier le nom de l'objet qu'il doit ajouter à la scène. En cliquant dans la case après Object:, nous retrouvons facilement notre Boulet, nommé précédemment. Nous rappelons que cet objet doit être sur un calque invisible pour que cela fonctionne.
XXV-C-2. Donner un mouvement▲
Une instance sera toujours générée aux coordonnées d'origine de l'objet qui la crée. Dans ce cas-ci, nos boulets seront générés au milieu de notre canon.
4. Si besoin, replaçons l'origine pour que les boulets partent bien de l'emplacement souhaité (pour cela, nous pouvons positionner correctement le boulet dans le canon, y caler le curseur 3D avec Maj + s > Cursor to selected, puis sur le canon, utiliser Set Origin > Origin to 3D cursor de la boîte à outils.
5. Nous pouvons donner une vitesse de départ à nos instances en modifiant les paramètres Linear Velocity. Dans notre exemple, nous allons appliquer une vitesse de 8.00 en y selon les coordonnées locales du canon en cochant le bouton L en bout de ligne. Pour bien choisir les axes, passez la vue 3D en mode local également.
Nous avons choisi la coordonnée locale du canon parce que nous pourrions ensuite placer ce canon sur une tourelle qui tourne sur elle-même ou faire tenir ce canon par un personnage se déplaçant. Nous nous assurons ainsi que les boulets sortiront toujours par le bon côté du canon et iront dans la direction vers laquelle il pointe.
XXV-C-3. Donner une durée de vie▲
Toujours dans cet actuator, nous voyons l'option Time. Il s'agit de la durée de vie que nous allons donner à chaque instance que nous allons générer. Au-delà de cette durée, l'instance est détruite. Cela peut servir dans plusieurs cas comme simplement celui de limiter dans la durée la présence de cette instance dans le jeu. Mais, au-delà des règles du jeu, limiter cette présence peut devenir réellement important si l'on compte, par exemple, générer un très grand nombre d'instances. Parce que si aucun autre mécanisme ne vient les détruire, elles resteront présentes dans le moteur de jeu tant qu'on n'est pas sorti du jeu ou qu'on n'a pas changé de scène. Cela peut donc devenir rapidement très lourd pour le moteur physique de gérer autant d'objets. Donner une durée de vie limite permet ainsi de conserver un nombre d'instances raisonnable par rapport aux performances de la machine.
Par défaut, l'actuator a une valeur de Time égale à 0. Cela veut dire que l'instance générée aura par défaut une durée de vie infinie.
XXV-D. Scrypthon !▲
Le spawning peut bien évidemment se faire en Python. Gardons notre sensor Keyboard défini précédemment, mais relions-le plutôt à un controller de type Python pointant vers un script nommé tirer_un_boulet.py. Dans ce script nous écrirons.
from
bge import
logic
# Récupérer la scene
scene =
logic.getCurrentScene
(
)
# Récupérer le canon
canon =
scene.objects["Canon"
]
# Récupérer le boulet sur son calque invisible
boulet =
scene.objectsInactive["Boulet"
]
# Créer une instance de boulet
instance =
scene.addObject
(
boulet, canon, 0
)
# Donner une vitesse à l'instance
instance.localLinearVelocity.y =
8.0
Après import du module logic et récupération de la scène active, nous créons deux variables qui représentent nos objets « canon » et « boulet » présents dans la scène. Remarquons qu'on accède différemment à des objets se trouvant dans un calque non visible : scene.objectsInactive["Boulet"] qu'à des objets présents dans un calque visible : scene.objects["Canon"]. Dans les deux manières, nous utilisons le nom le l'objet précédemment défini.
Nous créons ensuite des instances de notre objet Boulet qui apparaîtront aux coordonnées de l'origine de notre objet canon de cette façon :
instance =
scene.addObject
(
boulet, canon, 0
)
Le troisième paramètre, ici mis à 0, est la durée de vie de l'objet. Observez aussi que cette méthode addObject() renvoie l'objet nouvellement créé. Nous conservons ceci dans une variable nommée instance afin de pouvoir lui appliquer des fonctions supplémentaires, comme dans ce cas-ci lui ajouter une vitesse.
instance.localLinearVelocity.y =
8.0
L'avantage d'utiliser Python ici par rapport aux briques logiques, c'est que l'on pourrait dynamiquement changer la durée de vie de nos instances, générer des instances différentes pêchées aléatoirement dans une liste d'objets cachés, ou encore la faire apparaître à des endroits différents de la scène en utilisant comme point de génération des objets vides (empty) placés à des endroits clés de notre scène de jeu.
XXVI. 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.
XXVI-A. 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.
- Supprimons le cube par défaut avec x puis ajoutons un plan Maj + a>Plane.
- Attribuons un matériau de base de couleur verte.
- 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.
- Ajoutons à présent le héros avec une simple sphère à laquelle nous pouvons attribuer un matériau jaune et le nom heros.
- Redimensionnons s et déplaçons g ces 2 personnages de manière à les espacer sur le terrain de jeu.
- 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.
- Créons un sensor Always et relions-le à un actuator Steering.
- Dans la liste Behavior(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.
- Dans Target Object, sélectionnons heros.
- Vous pouvez tester le jeu. L'ennemi doit se diriger vers le héros.
- 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.
- Vous pouvez enfin rendre le héros déplaçable à la souris et les choses deviennent alors très intéressantes.
XXVI-B. 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 de les sélectionner tous.
Ensuite nous ouvrons le panneauNavigation 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 navigation 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 options Agent définissent plus précisément les capacités de notre PNJ. Quelle 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 meshet 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 au sommet 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 resé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.
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.
XXVI-C. Utiliser un plan de navigation par brique logique▲
L'actuator Steering permet d'utiliser un mesh de navigation dans le jeu.
Lorsque cet actuator est 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'exact opposé 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 autres options définissent les aspects 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 la navigation mesh n'est pas horizontale.
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 recalculs de la trajectoire pour tenir compte du déplacement de l'objectif. La valeur par défaut est 0, ce qui implique un recalcul à 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.
XXVI-D. 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.
XXVI-D-1. 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.
XXVI-D-2. É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 et Radar. Le sensor Near détecte la présence d'objet dans un rayon déterminé par le paramètre Distance.
Le sensor Radar dé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 Actor est 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 sensors peuvent ê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és par le sensor.
XXVI-D-3. Comment faire exécuter une ronde à un agent, à peu de frais ?▲
Pour les états 1 et 2 nous pouvons utiliser l'actuator Steering 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 trois pour faire une boucle (deux pour un aller-retour). Nous avons donc besoin de trois actuators (un 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 sensor Actuator. 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 Actionet 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.
XXVI-D-4. États visibles et initiaux▲
Si nous changeons le state d'un contoller sans 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 + à côté 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.
XXVI-D-5. État 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 trois actuatorsSteering, aPoint1, aPoint2 et aPoint3 pour les trois points de passages et les trois 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 sProchesont à l'affût du joueur: notons que nous avons écrit player dans le paramètre Property, 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 deux 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.
XXVI-D-6. État 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.
XXVI-D-7. 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 :
- Menu principal > Game>Show Debug Properties.
- Pour afficher l'état courant d'un objet.
- Pour afficher la valeur d'une propriété d'un objet.
- Penser à utiliser l'actuator Property pour changer la valeur d'une propriété pendant le jeu en vue du débogage.
XXVI-E. 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 pallier les limitations de l'actuator Steering : 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étail dans le chapitre Comprendre les briques logiquesComprendre les briques logiques de la section Développer l'ergonomie.