XXVII. Gérer les périphériques▲
Une partie très importante de la création d'un jeu vidéo est l'interface avec le joueur, ou IHM (Interface Homme Machine). Définir comment le joueur va interagir avec notre jeu, c'est penser une ergonomie : si le joueur a l'impression que le jeu ne réagit pas à ses commandes ou au contraire si le jeu réagit trop vite, il risque de ne pas arriver à jouer ou bien de se désintéresser du jeu. Nous devrons probablement passer du temps à tester et à affiner nos réglages pour que le jeu donne un bon « feeling » au joueur, qui est là pour s'amuser !
Expliquer au joueur comment jouer lors du premier niveau, faire des niveaux avec une difficulté progressive sont également des principes à garder en tête : nous avons conçu notre jeu, donc nous savons comment y jouer… mais le futur joueur lui ne le sait pas, ce n'est pas évident pour lui. La première chose à considérer est donc quel matériel votre joueur va utiliser pour jouer. En fonction des périphériques du joueur, nous ne ferons pas les mêmes types de jeux : concevoir un jeu jouable au clavier et à la souris n'a rien à voir avec un jeu tactile sur un smartphone, ou un jeu qui suppose que le joueur fasse de grands gestes devant un détecteur de mouvements.
XXVII-A. En pratique▲
Dans le BGE, on récupère les informations envoyées par les périphériques via les briques logiques de type sensor (Keyboard, Mouse, ou Joystick) ou bien via des scripts Python, grâce aux objets logic.keyboard, logic.mouse ou logic.joysticks, ou encore par le protocole OSC (voir plus loin).
En Python, nous devrons donc importer en début de code les modules logic et events. Ce dernier contient les constantes correspondant à toutes les entrées possibles du clavier et de la souris. Pour toutes les touches et tous les boutons, qu'il s'agisse du clavier ou de la souris, il existe trois statuts différents :
- logic.KX_INPUT_NONE : le statut par défaut, bouton au repos, non actif ;
- logic.KX_INPUT_JUST_ACTIVATED : si une touche ou un bouton vient juste d'être pressé ;
- logic.KX_INPUT_ACTIVE : si une touche ou un bouton est actuellement pressé.
XXVII-B. Le clavier▲
Le clavier est le périphérique qui contient le plus d'entrées. Si votre jeu nécessite un grand nombre de commandes différentes, comme souvent dans les jeux de stratégie ou les jeux à la première personne avec de multiples actions possibles du héros, vous aurez certainement besoin du clavier.
XXVII-B-1. Briques logiques▲
Dans la première section de ce manuel, pour déplacer notre personnage, nous avons vu comment récupérer les frappes clavier avec le sensor Keyboard. Référez-vous à cette partie si vous avez des doutes : section Créer son premier jeu, chapitre Déplacer le personnage.
XXVII-B-2. Scrypthon !▲
Pour écrire un script Python qui récupère les frappes au clavier, voici comment procéder:
from
bge import
logic, events
clavier =
logic.keyboard
if
clavier.events[ events.UPARROWKEY ] ==
logic.KX_INPUT_ACTIVE :
print
(
"La touche up active"
)
Étudions ce code ligne par ligne.
from
bge import
logic, events
L'import des modules Python est l'entête obligé de tout script Python dans le BGE. Nous aurons besoin de logic pour accéder au clavier et events pour les constantes du clavier.
clavier =
logic.keyboard
Cette technique classique en programmation Python consiste à assigner la référence d'un objet accessible par une expression complexe à variable locale pour plus de clarté et d'efficacité.
if
clavier.events[ events.UPARROWKEY ] ==
logic.KX_INPUT_ACTIVE :
L'objet clavier a un attribut events qui est un dictionnaire qui répertorie le statut courant de toutes les touches (actif, tout juste activé ou inactif). Le test vérifie si le statut de la touche flèche vers le haut (events.UPARROWKEY) est actif (statut logic.KX_INPUT_ACTIVE).
print
(
"La touche up active"
)
Et si c'est le cas, le test imprime ce message en console.
Notons que pour que ce simple script fonctionne, il faut au minimum l'attacher à un sensor Always dont le pulse mode TRUE level triggering est actif. Autrement dit, il faut que ce script soit exécuté à chaque frame.
XXVII-C. La souris▲
La souris est l'extension la plus importante et la plus courante après le clavier. Qu'elle se pende ou non au bout d'un fil, qu'on l'appelle trackpad ou mulot. Qu'elle possède un, deux ou trois boutons, c'est l'outil de base du joueur pour pointer des éléments sur un écran. C'est précis, sensible et encore bien ancré dans des pratiques de jeu.
XXVII-C-1. Briques logiques▲
La brique logique permettant de récupérer les événements liés à la souris est la bien nommée sensor Mouse. Elle est assez simple à configurer puisqu'il n'y a qu'une seule option : c'est l'action de la souris qui active ce sensor. Ce sont bien sûr les clics de boutons, mais aussi le simple fait de déplacer la souris (Movement).
L'autre type d'action intéressant est l'événement Mouse Over qui correspond au passage de la souris au-dessus de l'objet 3D auquel ce sensor est attaché. Cette option peut se révéler très utile pour créer un menu ou sélectionner des éléments dans une scène.
XXVII-C-2. Scrypthon !▲
Voici comment procéder en Python :
from
bge import
logic, events
souris =
logic.mouse
if
souris.events[events.LEFTMOUSE] ==
logic.KX_INPUT_JUST_ACTIVATED :
print
(
"Clic gauche"
)
# Affiche en console la position de la souris
print
(
souris.position )
# Afficher le curseur pendant le jeu
souris.visible =
True
Comme pour le clavier, nous assignons l'objet logic.mouse à une variable « souris » pour plus de clarté.
L'objet souris possède également un attribut events qui répertorie le statut de toutes les actions souris. Cette fois, nous détectons si le bouton de gauche (events.LEFTMOUSE) est « tout juste activé » (logic.KX_INPUT_JUST_ACTIVATED). Cette condition sera vérifiée lorsque le joueur aura tout juste appuyé sur le bouton gauche de sa souris. S'il maintient le bouton appuyé, cette condition ne sera plus vérifiée.
XXVII-D. Manette▲
La manette, le joystick, le volant, le gamepad, etc. sont autant d'accessoires bien connus des joueurs. Ils ont la particularité de renvoyer davantage d'informations que le simple fait de cliquer sur tel ou tel bouton. Ces accessoires ont des formes multiples, parfois en lien étroit avec le type de jeu auxquels ils sont apparentés (les volants pour les courses de voiture par exemple). En revanche, ils partagent tous la caractéristique d'envoyer des informations précises de direction, parfois variables en intensité et souvent représentées sur des axes x et y.
Dans la suite de ce chapitre, nous utiliserons le terme manette pour désigner la manette de jeu dans son ensemble et joystick pour désigner un manche en particulier. Les joysticks peuvent être à axe simple (par exemple avant-arrière) ou multiple (par exemple avant-arrière, gauche-droit). Contrairement à la souris qui renvoie une position dans un rectangle (la fenêtre de jeu). Ici, il s'agit plutôt de déterminer ici l'angle du joystick par rapport à sa position au repos.
Nous pouvons utiliser ce .blend pour tester si notre manette préférée est supportée par le BGE: [référence vers blends/SCA_PythonJoystick/joystick_test.blend + 8bitoperator.ttf]
XXVII-D-1. Briques logiques▲
Pour utiliser une manette, nous nous servirons de la brique logique sensor Joystick.
Plusieurs manettes peuvent être connectées à l'ordinateur. Le BGE numérote automatiquement les manettes en partant de 0. C'est ce numéro que nous indiquons dans le champ Index pour spécifier quelle manette nous intéresse. Lorsqu'il n'y a qu'une seule manette connectée, elle aura d'office l'index 0. Mais dans le cas de plusieurs manettes, nous ne pouvons malheureusement pas savoir à l'avance quels seront leurs numéros.
Cette question est importante lorsque nous publions un jeu : quel index choisir dans les sensors et comment permettre à l'utilisateur de choisir la manette qui sera utilisée dans le jeu ? La solution de facilité est de fixer l'index à 0 dans tous les sensors, ce qui force l'utilisateur à prendre une manette en particulier, ou l'oblige à débrancher ses autres manettes pour utiliser sa manette préférée. La solution complexe consiste à proposer un choix par menu à l'utilisateur et à modifier avec Python le champ index des sensors, voire de se passer complètement des sensors, car tous les événements manette sont récupérables par Python (voir plus bas).
L'option event Type détermine le type d'événement que nous allons récupérer : simple bouton pressé (Button), action sur une croix directionnelle (Hat), déplacement d'un joystick à axe simple ou multiples (Axis). Malheureusement le même problème se pose pour les boutons et les joysticks des manettes: ils sont numérotés automatiquement et l'ordre peut varier fortement d'un fabricant de manettes à l'autre.
XXVII-D-2. Scrypthon !▲
Voici un extrait de script permettant de récupérer facilement les indices des manettes connectées.
from
bge import
logic
# Affichons les manettes connectées et leur indice
indice =
0
for
manette in
logic.joystick :
if
manette !=
None
:
print
(
manette.name, " a l'indice "
, indice )
indice +=
1
L'objet logic.joystick contient la liste de manettes connectées à l'ordinateur dans l'ordre où elles ont été numérotées par le BGE. La boucle for passe en revue les éléments de cette liste. Si un élément n'est pas vide (None), il représente une manette actuellement connectée. Nous pouvons accéder à ses propriétés, comme son nom (ici par l'affichage en console de manette.name). La variable indice sert à connaître l'index de chaque manette, car elle est incrémentée à partir de 0. En affichant le nom de la manette et son index, nous savons maintenant quelle valeur nous devons mettre dans le champ Index des sensors Joystick.
Voici les propriétés d'une manette accessible par Python :
- name : le nom de la manette.
- activeButtons : une liste d'entiers représentant les boutons actuellement pressés.
- axisValues : une liste contenant les valeurs des axes du joystick.
- hatValue : statut de la croix directionnelle (0:None, 1:Up, 2:Right, 4:Down, 8:Left, 3:Up-Right, 6: Down-Right, 12: Down-Left, 9:Up-left).
En modifiant notre code précédent; nous affichons les données envoyées par les joysticks lorsqu'on appuie sur leurs boutons :
from
bge import
logic
for
manette in
logic.joysticks :
if
manette !=
None
:
# Affichons les boutons actifs
for
bouton in
manette.activeButtons :
print
(
bouton )
En plus de la boucle sur les joysticks, nous bouclons sur la liste active Buttons de chaque joystick et nous imprimons son contenu. Les éléments de cette liste sont de simples nombres entiers qui représentent le numéro des boutons actuellement pressés. Ce sont ces numéros que nous utiliserons dans le champ Button Number des sensors pour détecter tel ou tel bouton. La liste est vide si aucun bouton n'est pressé.
Pour les autres propriétés, la démarche sera similaire.
- AxisValue : retourne une liste de nombres décimaux compris entre -1 et +1 qui représentent l'inclinaison d'un axe de joystick (0 est la position de repos). À noter qu'un joystick double axe prend deux positions successives dans cette liste.
- HatValues : retourne une liste de nombres entiers qui représentent la valeur de chaque croix directionnelle.
XXVII-E. Périphériques exotiques▲
Il est tout à fait possible d'utiliser d'autres périphériques avec Blender que ceux cités plus haut. Seulement, il n'existe pas de brique logique pour cela. C'est grâce à l'utilisation de scripts Python et du protocole OSC que nous pourrons étendre à l'infini ces possibilités d'interface. Par exemple, la télécommande Wiimote, le senseur Kinect, des capteurs sur Arduino, des contrôleurs provenant d'autres logiciels comme Pure-Data, un clavier MIDI, etc. peuvent tous servir comme interface de contrôle.
Pour la Kinect360, l'usage de OSCeleton permet de facilement récupérer les données des squelettes des utilisateurs détectés par le senseur. Ces données pourront ensuite être traitées dans le BGE. Il existe plusieurs méthodes pour utiliser le protocole OSC dans le Game Engine :
- soit en important le module pyOSC ;
- soit en faisant appel à la bibliothèque pylibloc, qui elle-même dépendant de liblo (pour GNU-Linux ou OSX seulement).
Étant donné la diversité des possibilités à ce niveau, nous nous contenterons d'un exemple simple avec pyOSC. Pour les besoins de l'exemple, nous utiliserons 2 instances du Blenderplayer plutôt qu'un contrôleur physique externe, de sorte que n'importe qui puisse comprendre le fonctionnement, sans dépendre d'un matériel spécifique. Le principe est de contrôler le mouvement d'un cube dans un player à partir d'un autre player.
<OSC_send.blend>
Dans ce premier fichier, on se contente de récupérer les événements du clavier, et d'assigner des commandes à certaines touches. Ces commandes sont envoyées par le script en OSC sur le réseau. Ici en envoyant les infos vers l'ordinateur lui-même, en utilisant l'adresse interne ou localhost (127.0.0.1). On peut également spécifier une autre adresse réseau pour envoyer les messages OSC vers une machine distante.
<OSC_receive.blend>
Dans ce fichier, le script Python appelle le module pyOSC et écoute les messages qu'il reçoit pour ensuite utiliser ces données pour ajuster la position du cube en xyz. Une subtilité importante est la fonction quit() qui éteint le serveur à la sortie du jeu (sinon, à la prochaine relance, il risque d'y avoir une erreur de port réseau resté ouvert). La touche clavier Echap est ici également reliée explicitement à une action Game > Quit, de sorte à quitter le Game Engine (sinon, la touche est réassignée à l'action du script et le jeu ne quitte plus…).
XXVIII. Gestion de scène▲
Blender permet de travailler sur plusieurs scènes qui peuvent se succéder ou se superposer. Dans le cadre d'un jeu, cette méthode est idéale pour séparer plusieurs niveaux ou chapitres d'une histoire, ou créer des menus et informations à l'écran. Créer, supprimer ou sélectionner des scènes se fait dans la fenêtre Info, par défaut tout en haut de l'écran.
La scène qui est active dans Blender sera également la première scène du jeu. Pour gérer d'autres scènes durant le jeu, il faut passer par l'Actuator Scene. Il possède différents modes que nous allons expliquer.
XXVIII-A. Changer de scène▲
Pour changer de niveau durant le jeu, l'actuator doit être en mode Set Scene. L'option Scene détermine vers quelle scène basculer (ou plutôt, quelle sera la nouvelle scène active). Il est nécessaire de placer une caméra dans la scène cible pour obtenir une vue de la scène une fois le changement effectué. La scène qui a provoqué le changement est détruite ainsi que tous les objets qu'elle contient. Ce mode est idéal pour charger un niveau donné avec tous les objets dans leur état initial.
XXVIII-B. Lancer plusieurs scènes▲
Il est parfois nécessaire d'avoir plusieurs scènes fonctionnant simultanément : par exemple, afin d'afficher une interface graphique par-dessus l'écran (cette technique sera décrite en détail au chapitre suivant). Les modes Add Overlay Scene et Add Background Scene permettent respectivement d'ajouter une scène à l'avant ou à l'arrière de la scène active. Comme une scène en Background est affichée avant la scène active, elle détermine le fond de l'écran par ses objets et par ses paramètres de l'onglet World. Une scène en Overlay est affichée après la scène active et ses objets sont visibles par-dessus tous les objets de la scène. Seul le fond de la scène en Overlay sera transparent et laissera apparaître les éléments de la scène active.
Le mode Overlay est utile pour afficher des éléments en surimpression de la scène principale, comme un menu (voir chapitre suivant), des informations comme les points de vie ou le score (voir le chapitre Point de vue du joueur de cette même section).
XXVIII-C. Agir sur une scène déjà lancée▲
Le mode Suspend Scene sert à mettre en pause la scène visée. Attention, une scène en pause ne peut se sortir elle-même de pause, car sa logique interne est à l'arrêt ! La mise en pause et la sortie de pause d'une scène doivent provenir de commandes présentes dans une autre scène, par exemple, le bouton pause d'un menu présent sur une scène en Overlay.
Le mode Remove Scene permet de supprimer une scène et tout ce qu'elle contient. On utilise habituellement cette commande pour enlever les scènes placées en Overlay ou en Background d'une scène active. Si la scène active se retire elle-même, cela provoque la fin du jeu.
Le mode Restart redémarre la scène courante comme si le jeu venait de démarrer : elle retrouve tous ses paramètres initiaux.
XXVIII-D. Scripthon !▲
Pour ajouter une scène, nous utiliserons la fonction addScene() de bge.logic. Cette fonction prend deux arguments, le premier obligatoire est le nom de la scène que l'on veut ajouter, le deuxième indique si la scène doit être ajoutée en mode Overlay (argument à 1) ou en Background (argument à 0).
Récupérons la scène courante et remplaçons-la par une autre :
from
bge import
logic
# Récupère un objet
KX_Scene scene =
logic.getCurrentScene
(
)
scene.replace
(
"Nom de la nouvelle scene"
)
Récupérons une autre scène et mettons-la en pause :
from
bge import
logic
# Liste les scènes uniquement en cours d'exécution
scene_list =
logic.getSceneList
(
)
# Parcours de la liste
for
scene in
scene_list:
if
scene.name ==
"Game"
:
scene.suspend
(
)
Réactiver une scène en pause :
if
scene.suspended:
scene.resume
(
)
Fermer une scène :
scene.end
(
)
XXIX. Réaliser un menu▲
Démarrer une nouvelle partie, quitter le jeu, consulter les crédits : ce type d'actions passe souvent par des boutons regroupés dans un menu.
Nous découvrirons dans ce chapitre quelques techniques pour créer un menu grâce aux briques logiques. Notez que nous détaillerons le fonctionnement des options communes à tous les sensors (Tap, Level, Invert, Pulse) dans le chapitre suivant.
XXIX-A. Un bouton fonctionnel▲
Les menus classiques fonctionnent généralement avec la souris. Nous devons détecter si une souris survole un objet et détecter un événement souris. La première nécessité est d'afficher le cursor : Properties > Render > Display > Mouse Cursor.
Pour notre premier bouton, nous ajoutons un plan, sur lequel nous mettrons une texture de bouton avec du texte indiquant son rôle, dans cet exemple ce sera quitter le jeu. Nous ajouterons un actuator de type Game, le but étant de l'activer avec la souris.
Le premier sensor Mouse configuré enMouse Over envoie True lorsque l'objet (ici le bouton) est survolé par la souris. Le deuxième sensor Mouse, configuré en Left Button, envoie Truelorsqu'il détecte l'événement clic gauche de la souris. Le contrôleur logique lié aux deux sensors Mouse est ici de type And : parce qu'il faut que tous ses sensors soient True pour que les actuators auxquels il est connecté soient activés. Ces deux conditions sont en effet nécessaires pour que cela corresponde à un clic du bouton. L'actuator termine le jeu.
Nous pouvons ensuite dupliquer ce plan (changer sa texture) et modifier l'actuator pour choisir une autre action, par exemple changer la scène courante vers une scène appelée Options.
XXIX-B. Embellir le menu▲
Nous souhaitons améliorer l'aspect visuel du menu et le rendre plus interactif, avec par exemple des boutons qui changent d'aspect lorsqu'on les survole ou qui jouent des animations (ils sautillent, tournent, grossissent, etc.). La façon la plus économe et de faire un bouton qui change de couleur est de placer deux plans dos à dos, et faire tourner l'ensemble de 180° lorsqu'il est survolé par la souris. Pour obtenir une rotation précise et reproductible, nous utiliserons une animation.
Notons que nous avons choisi l'interpolation constante de sorte que le flip soit instantané sans étape intermédiaire.
Le BGE tourne avec un fps de 60, soit bien plus que le fps habituel de 24 ou 25 des animations en cinéma ou vidéo. Pour que les animations se jouent néanmoins à la vitesse normale, le BGE affiche des frames intermédiaires. Avec une interpolation de type Bézier, nous verrions le bouton brièvement tourner, ce qui n'est pas l'effet recherché.
Ensuite avec une brique Action en mode Flipper, nous connectons simplement cet actuator au sensor Mouse Over.
Vous pouvez télécharger cette démo ici: [menu_bouton_flip.blend]
L'explication courte est la suivante : le mode Flipper joue l'animation dans le sens normal lors de l'activation par le controller et dans le sens inverse lors de la désactivation : le bouton retrouve sa rotation normale lorsque la souris quitte le bouton. Dans le chapitre suivant, nous décrirons comment faire la même chose avec Python après avoir expliqué la mécanique interne du moteur logique.
XXIX-C. Faire apparaître un menu pendant le jeu▲
Comme nous l'avons vu dans le chapitre précédent avec la gestion de plusieurs scènes, il suffit d'ajouter une scène en Overlay (la scène menu) et de mettre en pause la scène courante. Ensuite, lorsqu'on supprime le menu, il ne faut pas oublier de faire un resume scene pour relancer la scène courante mise en pause précédemment.
XXX. Logique de jeu▲
Lors de la création d'un jeu, la mise en place d'un système d'interaction complexe entre des éléments du jeu et le joueur arrive rapidement. Par exemple, on cherche à mettre en place un ennemi montant la garde, et tentant d'attraper le joueur ou de lui tirer dessus à son approche. Bien entendu, le garde peut adopter d'autres états et adopter d'autres réactions. Imaginons un jeu de cuisine avec des plats à mettre au four. Cela demande la gestion d'état de la cuisson du plat, comprenant des états cru, légèrement cuit, bien cuit, trop cuit, totalement brûlé. Nous pouvons tout aussi bien imaginer un changement en cascade, comme avoir dans un jeu, un interrupteur qui s'actionne par le joueur et ouvre une porte au loin. Ce chapitre va être l'occasion d'étudier deux mécanismes permettant de mettre en place ce principe en utilisant les machines à états et l'envoi de message.
XXX-A. Les machines à états▲
Les objets avec lesquels le joueur doit interagir auront assez régulièrement besoin de stocker leur état. Les machines à états formalisent cela.
XXX-A-1. Principes généraux d'une machine à états▲
Une machine à états est composée de deux choses, les états et les transitions permettant de passer d'un état à un autre. Représentons une machine à états sous la forme d'un graphe : les états sont les nœuds du graphe et les événements sont les flèches qui relient les nœuds.
Pour que la machine à états soit cohérente, il faut bien sûr éviter les états orphelins (aucune flèche n'y mène) et les états cul-de-sac (aucun chemin n'en sort), sauf s'il s'agit d'un état terminal qui termine le jeu ou détruit l'objet. En principe, un seul état est actif dans une machine à état. Le système d'états du BGE permet d'avoir plusieurs états actifs en même temps, mais nous n'aborderons pas cette possibilité, car cela se résume souvent à considérer plusieurs machines à états indépendantes.
XXX-A-2. Machine à états dans le BGE▲
Pour ce comportement complexe, nous aurons besoin d'utiliser le système des états du BGE. Cette technique avancée que nous n'avons pas abordée jusqu'à présent consiste à regrouper les briques par état. Ensuite, en activant tel ou tel état, nous obtenons différents groupes de briques actives, et donc différents comportements. Dans le BGE, les états sont portés par les controllers : nous choisissons l'état auquel appartient un controller grâce à ce bouton.
Les sensors et actuators qui sont connectés à un controller font automatiquement partie de l'état auquel appartient le controller. Le changement d'état se fait grâce à l'actuator State.
Chacun des 30 petits boutons correspond à un état. L'actuator permet de manipuler plusieurs états simultanément. Les états peuvent être actifs ou inactifs et l'actuator permet de changer cela. Le type de manipulation est déterminé par l'option Operation. Nous nous limiterons à l'opération la plus simple qui consiste à activer un état et désactiver tous les autres. Par exemple pour activer l'état 2, nous utiliserons l'actuator en mode Set State.
Pour info, les autres opérations sont : inversion de certains états (Change State), activation de certains états sans changer ceux qui étaient déjà actifs (Add State), désactivation de certains états (Remove State). Ces opérations sont utiles dans le cas ou plusieurs machines à états coexistent pour un objet.
Pour rappel, la brique qui permet de changer l'état étant un actuator, il faut nécessairement un controller et un sensor pour l'activer. Dès qu'un état est activé, toutes les briques de cet état deviennent fonctionnelles comme si le jeu venait de démarrer. En particulier les sensors Always et Delay produisent une impulsion logique, ce qui permet de démarrer la logique de l'état.
Les briques appartenant à un état inactif n'utilisent aucune ressource du BGE. Une machine à états bien pensée est donc également un outil d'optimisation.
XXX-B. Communication entre objets, utilisation des messages▲
Le Blender Game Engine permet de faire communiquer les objets entre eux grâce à des messages. Il faut voir un message comme un email que l'on pourrait envoyer. Un message se décompose en plusieurs éléments. Tout d'abord un message est émis par un objet précis, son émetteur. Ensuite un message contient un sujet et un corps de message. Enfin un message peut être envoyé à quelqu'un ou bien envoyé à tout le monde.
Pour illustrer l'utilisation de messages, nous allons mettre en place un mécanisme interrupteur - porte. Nous allons donc avoir un objet représentant le joueur (le cube gris), un objet représentant l'interrupteur (le cube bleu) et un pavé représentant la porte (le mesh rose).
Expliquons maintenant les Briques que nous utilisons. En Sensor, nous utilisons tout d'abord un détecteur de collision branché sur l'interrupteur. Nous ajoutons ici un filtre sur les propriétés. En effet, l'interrupteur est en collision perpétuelle avec le sol et nous ne voulons ouvrir la porte que si le joueur touche à l'interrupteur. Bien entendu, nous avons pris soin d'ajouter une propriété de type string et ayant pour valeur joueur à notre cube gris représentant le joueur. Le contrôleur est très classiquement un contrôleur de type And. C'est dans l'actuator que nous mettons en place l'envoi du message. Il y a pour cela un Actuator spécial, l'actuator de type message. On définit le récepteur du message avec la partie To (nous aurions très bien pu ne pas renseigner cette partie, le message aurait alors été envoyé à tous les objets). Définissons ensuite un sujet avec la partie Subject et le corps du message avec la partie Body. Ici nous laissons vide la partie body dont nous n'avons pas l'utilité. Maintenant que notre interrupteur sait envoyer un message à notre porte, il faut que notre porte soit capable de recevoir le message et de le comprendre afin de s'ouvrir. Voici la suite de briques logiques qui permet de faire en sorte que la porte reçoive les messages qu'on lui envoie.
Pour la porte, les choses sont encore plus simples que pour l'interrupteur. Il faut que la porte détecte un message. Nous allons donc utiliser un nouveau sensor spécifiquement dédié à cela, le sensor Message. On peut filtrer sur un seul critère, le sujet du message. Ici nous voulons réagir sur la réception d'un message d'ouverture de porte, donc nous filtrons sur le sujet Open.
XXX-B-1. Scrypthon !▲
Intéressons-nous à l'envoi d'un message en Python. Comme vous pouvez le voir sur la capture d'écran suivante, la connexion entre le sensor de collision et le controller And précédent a été supprimée. Un contrôleur Python a ensuite été rajouté puis relié au sensor de collision. Le script Python s'occupera d'envoyer le message.
Comme vous pouvez le constater dans la capture, le code est vraiment très court. Le revoici ici pour plus de lisibilité.
import
bge
cont =
bge.logic.getCurrentController
(
)
button =
cont.owner
bge.logic.sendMessage
(
"Open"
)
La première ligne est toujours celle de l'import du BGE. Nous récupérons ensuite l'objet relié au contrôleur (dans cet exemple ce n'est pas obligatoire, mais c'est souvent utile). Pour l'instant, la ligne suivante est celle qui envoie réellement le message. La fonction sendMessage() fournie par le BGE est utilisée. Ce code se contente d'envoyer un message avec uniquement un sujet : Open. La fonction sendMessage() nous permet toutefois de faire bien mieux que cela. Les autres arguments de cette fonction sont body, to et message_from. Pour dire explicitement que ce bouton envoie un message, le code aurait été :
bge.logic.sendMessage
(
"Open"
, message_from=
button)
D'où l'intérêt d'avoir récupéré le button précédemment.
Nous avons vu un exemple relativement simple des messages dans le Blender Game Engine. Cependant, ils sont surtout intéressants dans des usages plus poussés, comme envoyer des informations variant en fonction de l'émetteur et de son état dans les corps de message.
XXXI. Comprendre les briques logiques▲
Dans ce chapitre nous étudierons en détail le fonctionnement du moteur logique du BGE. Jusqu'à présent, nous avons utilisé les briques sans trop nous soucier de la mécanique interne, mais il nous faut en savoir plus si nous voulons faire des machines à états élaborées et efficaces. Commençons par décrire les caractéristiques communes à toutes les briques.
XXXI-A. Principes de base▲
Les sensors ont tous un état interne qui est un simple booléen : Vrai (True) si la condition qu'ils détectent est réalisée ou Faux (False) dans le cas contraire. Les sensors produisent en outre des impulsions (Trigger) qui activent les controllers auxquels ils sont connectés.
Par la suite nous parlerons d'impulsion positive si l'impulsion est générée alors que le sensor est dans l'état Vrai et d'impulsion négative dans le cas contraire. Cependant il faut bien comprendre que les controllers sont activés de la même manière par une impulsion, qu'elle soit positive ou négative.
Un sensor produit toujours une impulsion lorsque son état change. D'autres impulsions peuvent être générées en fonction des options communes à tous les sensors, nous y reviendrons.
Prenons comme exemple le sensor Keyboard, car il est facile à utiliser et représentatif de tous les sensors. Voici le comportement de base du sensor :
Cette image représente l'état du sensor au cours du temps depuis le démarrage du jeu (ou l'activation de l'état auquel appartient le sensor). Les boules noires représentent les moments ou une impulsion est générée. Nous voyons qu'une impulsion (positive) est générée lorsque la touche est pressée et une autre (négative) lorsqu'elle est relâchée. Un controller connecté à ce sensor sera donc activé deux fois.
Un controller activé sous l'effet d'une impulsion examine l'état de tous les sensors auxquels il est connecté (qu'ils aient ou non généré une impulsion) et selon les conditions logiques qui lui sont propres peut envoyer aux actuators auxquels il est connecté une impulsion positive ou négative.
Les controllers logiques envoient une impulsion positive à tous les actuators si la condition logique est vérifiée, sinon ils envoient une impulsion négative. Cette liste donne les conditions logiques pour chaque type de controller :
- And : tous les sensors doivent être positifs ;
- Nand : au moins un sensor doit être négatif ;
- Or : au moins un sensor doit être positif ;
- Nor : tous les sensors doivent être négatifs ;
- Xor : un seul sensor doit être positif ;
- Xnor : zéro, deux ou plus de sensors doivent être positifs. ;
- Expression : l'expression logique est écrite en python.
Le controller Python n'est pas un controller logique : le script doit explicitement indiquer quels actuators reçoivent une impulsion et s'il s'agit d'une impulsion positive ou négative. Le script peut même se passer entièrement d'actuator en agissant directement sur les objets via l'API* Python.
Une fois transmise à un actuator, une impulsion positive l'active et une impulsion négative le désactive (ou ne fait rien, s'il était déjà inactif). Le temps pendant lequel un actuator reste actif en l'absence d'impulsion varie d'un type à l'autre. Nous distinguons trois types d'actuators.
-
Ceux qui restent actifs tant qu'ils ne reçoivent pas d'impulsion négative. Les actuators suivants appartiennent à cette catégorie :
- Armature (en mode Run)
- Camera
- Constraint
- Motion
-
Ceux qui restent actifs un certain temps et se désactivent automatiquement sous certaines conditions :
- Action
- Steering
- Sound
-
Ceux qui ne restent pas actifs : ils exécutent une opération et se désactivent immédiatement.
- Edit Object
- Filtre 2D (le filtre, une fois en place, reste actif)
- Game
- Message
- Mouse (dans le cas de Mouse Look, il faut envoyer constamment des impulsions pour actualiser l'orientation de la caméra)
- Parent
- Property
- Random
- Scene
- State
- Visibility
XXXI-B. Mode avancé des sensors▲
Nous pouvons voir que tous les sensors ont une ligne de boutons communs.
Ces boutons influencent le nombre et la polarité des impulsions. Voici ce qui se passe lorsque le bouton Tap est sélectionné :
Nous voyons que l'impulsion négative est générée immédiatement après l'impulsion positive, indépendamment du fait que la touche soit toujours enfoncée. L'écart de temps entre les deux impulsions est toujours de un frame. Ce mode est souvent utilisé pour obtenir des déplacements ponctuels : un actuator Motion qui reçoit ces deux impulsions s'activera pendant exactement un frame.
Le bouton Level fonctionne comme le mode normal excepté qu'une impulsion est toujours générée au démarrage du jeu ou lorsqu'un controller devient actif suite à un changement d'état. Dans ce cas, seuls les controllers nouvellement actifs reçoivent l'impulsion.
Ce mode est utile pour la programmation de machine à états, car il permet une initialisation correcte de l'état (les controllers sont garantis de recevoir une impulsion au démarrage de l'état). Le bouton Invert inverse la logique du sensor. Par exemple, dans le cas du Keyboard, le sensor sera positif tant que la touche n'est pas enfoncée et négatif sinon :
Une impulsion est générée au début du jeu (ou de l'état), car le sensor passe directement positif (nous supposons que la touche n'est pas pressée initialement), ce qui constitue une transition.
Les boutons pulse + et pulse - produisent des impulsions supplémentaires tant que le sensor est respectivement positif ou négatif. Le paramètre Freq est l'intervalle, exprimé en frame, inséré entre deux impulsions, 0 signifiant qu'une impulsion est envoyée à chaque frame.
Il ne faut pas abuser du mode pulse, car il augmente rapidement la charge de travail du BGE.
Voici ce qui se passe quand le bouton pulse + est sélectionné avec Freq = 1 :
Et avec pulse - :
Les boutons peuvent être combinés à l'exception de Tap qui est incompatible avec Level et Pulse. Voici par exemple ce qui se passe si Tap et Invert sont sélectionnés en même temps :
XXXI-C. Scrypthon !▲
Pour reproduire le bouton du chapitre précédent nous utiliserons deux attributs communs à tous les sensors :
- triggered : True si le sensor vient d'envoyer une impulsion
- positive : True si le sensor est positif, False s'il est négatif
Nous conservons les deux seniors, car il est plus efficace de demander au BGE de surveiller les boutons de la souris que de le faire en Python. Nous remplaçons les deux controllers par un seul controller Python et nous nous passerons d'actuator pour montrer que nous pouvons agir sur le jeu directement par Python :
Nous utilisons le script suivant :
from
bge import
logic
import
math
# Récupère le controller, l'objet et les sensors
cont =
logic.getCurrentController
(
)
obj =
cont.owner
sOver =
cont.sensors["sOver"
]
sClick =
cont.sensors["sClick"
]
# Triggered est True si la souris entre ou sort du bouton
if
sOver.triggered:
# Calcule la rotation selon l'axe X
rot =
math.pi if
sOver.positive else
0
# Écrire un vecteur d'Euler change la rotation de l'objet
obj.orientation =
[rot, 0
, 0
]
# Logique equivalente au controller And
if
sOver.positive and
sClick.positive:
logic.endGame
(
)
Expliquons la partie qui concerne la rotation du bouton. Nous utilisons l'attribut triggered du Mouse Over pour détecter le moment où la souris entre ou sort du bouton : ce sensor ne génère une impulsion qu'à ces moments-là. Nous modifions directement la rotation de l'objet en écrivant dans l'attribut orientation qui accepte différents types de données. Ici, nous passons un vecteur dont les composantes sont les angles d'Euler. Comme les angles sont en radian, nous utilisons math.pi(3,1415) pour indiquer une rotation de 180 degrés.
XXXII. Point de vue du joueur▲
La vue dans un jeu est définie par des caméras virtuelles. Ces caméras peuvent être contrôlées de bien des façons pour donner des effets scénaristiques. La gestion de la (ou des) caméra(s) est tellement importante qu'on catégorise même les jeux par ce critère. Nous allons voir ici comment définir le point de vue dans différents contextes.
XXXII-A. Point de vue à la 1re personne ou caméra subjective▲
Popularisé par des jeux comme Doom (ou Open Arena), souvent utilisé en visualisation architecturale, ce mode de jeu est très immersif puisqu'il place le joueur selon le même point de vue que le personnage qu'il incarne. L'immersion est habituellement renforcée en ajoutant à l'écran des éléments comme les bras du personnage.
XXXII-A-1. Mise en place▲
Reprenons notre exemple du chapitre Créer son premier jeu, et positionnons la caméra exactement à l'emplacement des yeux du personnage. L'objet qui représente notre personnage gêne évidemment la vue, nous allons donc le rendre invisible (panneau Physics, cocher Invisible). Il nous reste à « parenter » la caméra au personnage pour qu'elle colle à ses déplacements : sélectionnons la caméra, puis le personnage, Ctrl + p, Set Parent to Object.
XXXII-A-2. Contrôler le point de vue avec la souris▲
Dans ce type de jeu, la direction dans laquelle regarde le personnage est généralement contrôlée à la souris. Un script bien pratique est dédié à résoudre cette problématique, il s'agit de mousemove.py, partagé sous licence Creative Commons (CC-BY-3.0) par riyuzakisan. Nous allons voir comment le mettre en place en quelques instants.
- Téléchargeons le script à cette adresse : http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Game_Engine/FPS_Mouselook
- Copions le contenu du fichier .py dans l'éditeur de texte de Blender, et nommez le bloc de texte « mousemove.py »
- Dans le panneau Game Logic de notre objet « joueur », ajoutons deux briques logiques :
un sensor Always, en Pulse Mode (True) un controller Python, lié au script mousemove.py - Adaptons nos déplacements latéraux pour qu'ils conviennent mieux à ce type de contrôles : réinitialisons les valeurs de nos actuators « moveLeft » et « moveRigth ». Remplaçons-les par un mouvement de type Simple Motion sur l'axe X, respectivement de -0.1 et 0.1.
La méthode de déplacement est maintenant comparable à celle d'un jeu à la première personne.
Depuis la 2.72, un actuator Mouse a fait son apparition.
En mode Look, connectée à un sensor Always, elle a le même effet que le script.
On peut modifier sa réactivité aux mouvements de la souris en modifiant sa sensibilité Sensitivity et le seuil de déclenchement de l'action Thresold et imposer des limites de rotation.
XXXII-A-3. Éléments proches de la caméra▲
De la même manière, plaçons l'objet qui affiche le score quelques centimètres devant la caméra, et « parentons-le » à la caméra.
Passons en vue caméra (Num0), lançons le jeu et vérifions que tout se passe bien. C'est normalement le cas, à une exception près : lorsque l'on s'approche d'un mur, le score disparaît ! En réalité, il traverse le mur.
Une parade efficace est de créer une nouvelle scène contenant simplement une caméra avec notre objet score, puis de l'ajouter en mode Overlay à notre scène principale, comme décrit dans le chapitre précédent sur la gestion de scènes.
XXXII-A-4. Communication entre scènes▲
Il reste à régler la communication entre les deux scènes, car elles n'ont rien en commun à l'exception de deux choses :
- le système de messagerie interne : un message envoyé d'une scène peut être reçu dans une autre, il suffit que le nom de l'objet destinataire corresponde ;
- le répertoire global du moteur logique (logic.globalDict). Les objets Python qui sont stockés dans ce dictionnaire persistent tout au long du jeu et sont accessibles de toutes les scènes.
Nous pourrions utiliser le dictionnaire global pour stocker le score et envoyer un message à la scène Overlay pour ordonner le rafraichissement de l'affichage, mais nous pouvons aussi envoyer le score directement dans un message grâce à l'actuator Message :
Lorsque l'option Body est sur Property, nous spécifions le nom d'une Game Property de l'objet et la valeur de cette propriété est automatiquement envoyée, sous forme de texte, dans le corps du message.
Voici notre logique de jeu pour la mise à jour du score :
Nous voyons que le contact du joueur avec un objet bonus modifie le score, cela n'a pas changé. Mais nous avons ajouté une logique qui détecte toute modification du score et envoie un message à l'objet scoreDispavec le nouveau score. C'est grâce au sensorProperty > Changedque cela est possible : ce type de sensor surveille une propriété de l'objet et génère une impulsion dès qu'il détecte un changement.
L'objet scoreDisp est dans la scène hud que avons ajoutée au début du jeu. Pour récupérer le message, nous utilisons un sensor Message :
Tel qu'il est configuré, le sensor détecte les messages qui sont envoyés à l'objet (plus précisément au nom de l'objet, ici scoreDisp) et dont le sujet est « score ». Comme la nouvelle valeur du score est dans le corps du message, nous avons besoin de Python pour le lire, car les briques ne permettent pas d'y avoir accès. Le script suivant transfère le contenu du message dans la propriété Text de l'objet, ce qui a pour effet de modifier l'affichage.
from
bge import
logic
cont =
logic.getCurrentController
(
)
obj =
cont.owner sMsg =
cont.sensor["sMsg"
]
for
body in
sMsg.bodies:
obj["Text"
] =
int(
body)
Notons que le score est stocké à l'origine dans une propriété de type entier (Integer), car c'est nécessaire pour l'arithmétique du score. Il se transforme cependant en texte une fois copié dans le message, car c'est le type obligé pour tous les messages (String). Comme la propriété Text de l'objet scoreDisp est aussi un entier, nous devons reconvertir le message en entier avec l'expression int(body). Nous aurions pu tout aussi bien choisir une propriété de type String pour scoreDisp de façon à éviter la conversion.
Grâce à la scène en Overlay, le score est toujours visible :
XXXII-A-5. Scrypthon !▲
Comme nous l'avons vu au chapitre Logique du jeu, il est possible d'envoyer un message directement d'un script Python. Nous pourrions remplacer l'actuator Message par un controller Python qui exécute ce script :
from
bge import
logic
cont =
logic.getCurrentController
(
)
obj =
cont.owner
# arguments: sujet, corps, destinataire
logic.sendMessage
(
"score"
,str(
obj["score"
]),"Score"
)
XXXII-B. Point de vue à la 3e personne▲
Popularisé par des jeux comme Tomb Raider (ou YoFrankie!), ce mode est peut-être moins immersif, mais permet au joueur d'admirer le personnage, ses animations, ses interactions avec le décor. La caméra flotte à quelques mètres du personnage, ce qui implique qu'il faudra porter un soin particulier à ce qu'elle ne traverse pas un mur, qu'un objet ne se place pas entre les deux, que les mouvements ne soient pas saccadés.
L'actuator Camera fournit une première approche. Sélectionnons notre caméra, ajoutons-lui un actuator Camera déclenché par unsensor Always. Renseignons le nom de la cible (l'objet qui sera suivi par la caméra). Les noms des paramètres sont explicites, mais leurs réglages demandent quelques tâtonnements.
Toutefois on s'aperçoit vite que, quels que soient les réglages, plusieurs problèmes restent présents :
- La caméra vise le centre de l'objet. Dans notre cas, elle cadre les pieds du personnage.
- La caméra passe à travers les murs
- Il arrive qu'un mur s'intercale entre la caméra et le personnage, bouchant la vue.
Voyons comment contourner ces petits soucis. Il s'agit souvent d'utiliser des techniques déjà abordées dans des chapitres précédents.
XXXII-B-1. Contrôler le cadrage▲
On peut parenter un objet quelconque (un Empty convient parfaitement) à notre personnage, et déclarer cet objet dans l'actuator Camera. Il est ensuite facile de le déplacer, pour régler finement le cadrage de la caméra.
XXXII-B-1-a. Éviter de traverser les murs▲
On peut activer les collisions dans l'onglet Physics de la Caméra. Le mode Dynamic devrait convenir. Ces points sont détaillés dans la section Comportement des objets et personnages au chapitre Le comportement des objets de notre monde. Attention, la caméra devient alors sensible à la gravité, ce qui n'est généralement pas souhaitable. Pour pallier cela, il est courant de lui donner constamment une force contraire (un actuator de type Motion avec une Force de 0.098 en Z).
XXXII-B-1-b. Déboucher la vue▲
On peut utiliser les nœuds de matériaux, sur le matériel des murs, pour rendre transparent les parties proches de la caméra. Cette technique est détaillée dans le chapitre Matériaux dynamiques et textures animées de la section Développer l'univers du jeu.
XXXII-C. Changement de point de vue▲
L'idée ici est que plusieurs caméras ont été placées à des endroits clés d'une scène de jeu. Différentes fonctions, règles de jeu ou commandes de l'utilisateur déclenchent le passage de l'une à l'autre. Par exemple, il est courant, dans un jeu de voiture de pouvoir choisir sa vue, alors même que la voiture roule déjà, ou de rapidement regarder en arrière, lorsqu'on n'a pas de rétroviseurs. Il y a d'autres types de jeux qui activent le changement de caméra en fonction de la place du joueur sur la scène de jeu.
XXXII-C-1. Mise en place▲
Nous avons placé dans notre scène un certain nombre de caméras. Par souci de clarté, nous avons donné à chacune un nom qui la caractérise bien. L'avantage de cette méthode est qu'on peut, dans le cas de caméras fixes, régler finement leurs points de vue.
XXXII-C-2. Briques logiques▲
Pour passer d'une caméra à une autre, il faut juste créer un actuator Scene et le mettre en mode Set Camera. Très simplement, il faut juste alors lui indiquer le nom de la nouvelle caméra à partir de laquelle on désire voir la scène. L'événement qui déclenchera cet actuator peut être une collision, une frappe au clavier ou le changement d'une Game Property, tout cela dépend des règles du jeu et de la jouabilité que l'on cherche à développer, mais le résultat activera la vue de la scène à partir de la caméra dont le nom est inscrit dans l'actuator.
XXXII-C-3. Scrypthon !▲
Voici un code pour lister toutes les caméras présentes dans la scène et passer à la suivante dans la liste des caméras disponibles lorsque le code est activé :
from
bge import
logic
scene =
logic.getCurrentScene
(
)
# Quel est l'indice de la camera active
indice =
scene.cameras.index
(
scene.active_camera )
# Incrémenter cet indice
indice +=
1
# Vérifier que l'indice n'est pas hors limites
if
indice >=
len(
scene.cameras ) :
indice =
0
# Changer la caméra active
scene.active_camera =
scene.cameras[indice]
Après import des modules et récupération de la scène courante, nous recherchons l'indice de la caméra active (scene.active_camera) dans la liste de toutes les caméras présentes dans la scène (scene.cameras). Nous incrémentons ensuite cet indice. Et nous vérifions que cette nouvelle valeur n'est pas hors limite par rapport au nombre de caméras disponibles dans la scène. Sinon, nous remettons l'indice à 0. En bref, nous bouclons sur toutes les caméras disponibles. Pour modifier la caméra courante, il suffit d'assigner la nouvelle caméra à l'attribut scene.active_camera.
XXXIII. Afficher plusieurs points de vue▲
En fonction du type de jeu et du type d'interactivité, il est possible et parfois même souhaitable d'avoir plusieurs points de vue affichés dans la même fenêtre. Par exemple, lorsque le jeu propose à deux joueurs de s'affronter dans un mode appelé écran partagé ou que nous désirons afficher plusieurs points de vue sur une même scène pour créer des effets graphiques intéressants.
Dans les faits, nous installerons deux caméras dans notre scène avec l'intention d'afficher ces deux points de vue dans la fenêtre principale, qui pour l'occasion sera divisée verticalement en deux parties égales.
XXXIII-A. Écran partagé▲
Le terme technique qui définit la zone d'affichage du jeu dans l'écran est le viewport. Il n'existe malheureusement pas de logic brick pour activer ces fonctionnalités, nous utiliserons donc un script Python.
Le script peut être activé à partir de n'importe quel objet et doit être exécuté juste une fois : une fois en place les viewport persistent jusqu'à ce qu'ils soient supprimés explicitement. Le script doit désigner les caméras qui se partageront l'écran et définir les zones d'affichage pour chacune.
Voici le script, que nous détaillerons ensuite ligne par ligne.
from
bge import
logic, render
# Récupérer la scène
scene =
logic.getCurrentScene
(
)
# Récupérer les cameras a partir de leur nom
cam1 =
scene.objects["Camera1"
]
cam2 =
scene.objects["Camera2"
]
# Récupérer la taille de la fenêtre de jeu
largeur =
int(
render.getWindowWidth
(
))
hauteur =
int(
render.getWindowHeight
(
))
# Activer le viewport sur la Camera1
cam1.useViewport =
True
cam1.setViewport
(
0
, 0
, int(
largeur/
2
), hauteur)
# Activer le viewport sur la Camera2
cam2.useViewport =
True
cam2.setViewport
(
int(
largeur/
2
), 0
, largeur, hauteur)
Après l'import des modules Python nécessaires, nous récupérons la scène dans laquelle se trouve l'objet.
scene =
logic.getCurrentScene
(
)
Cet objet scene nous permet ensuite de récupérer les deux caméras grâce à leurs noms. Dans notre cas, nous avons au préalable choisi d'appeler les caméras Camera1 et Camera2.
cam1 =
scene.objects["Camera1"
]
cam2 =
scene.objects["Camera2"
]
Il faut ensuite récupérer la taille de la fenêtre dans laquelle s'affiche le jeu. Que le joueur soit en plein écran ou pas et que nous ayons défini une taille de fenêtre fixe ou pas, il est préférable de récupérer cette valeur directement par du code Python. C'est une méthode plus souple et plus sûre pour avoir toujours un écran partagé proportionné.
Dans l'exercice, nous cherchons à diviser l'écran verticalement en deux parties égales. Camera1 utilise le système de viewport et affiche son point de vue dans un rectangle qui occupe la moitié gauche de l'écran. Camera2 suit le même principe, mais dans la moitié droite.
La méthode setViewport() ne tolère que des nombres entiers dans ses paramètres, c'est-à-dire les mesures en pixel des limites du rectangle dans lequel s'affiche la caméra. Attention, ces paramètres doivent suivre un ordre très précis : gauche, bas, droite, haut. Ce script indique que la première caméra prend toute la moitié gauche de l'écran.
cam1.useViewport =
True
cam1.setViewport
(
0
, 0
, int(
largeur/
2
), hauteur)
La deuxième caméra remplit l'espace restant.
cam2.useViewport =
True
cam2.setViewport
(
int(
largeur/
2
), 0
, largeur, hauteur)
En utilisant les valeurs de hauteur et de largeur de notre fenêtre de jeu, nous nous assurons ainsi de remplir tout l'espace de cette même fenêtre.
Voici quelques détails techniques à propos des viewports qu'il est utile de connaître.
- Si nous laissons la caméra courante inchangée et que nous activons une caméra secondaire en mode viewport, la caméra principale continue de remplir tout l'écran et la caméra secondaire vient en surimpression dans la zone du viewport.
- Il n'est pas obligatoire que les viewports soient jointifs comme dans notre exemple. Ils peuvent se chevaucher, mais dans ce cas l'ordre de chevauchement n'est pas prévisible. Heureusement l'objet caméra possède une méthode setOnTop qui permet de placer cette caméra au sommet de l'empilement de viewports. En appliquant cette méthode sur toutes les caméras en commençant par celle qui doit être le plus en arrière, nous pouvons forcer un ordre de chevauchement.
- Les viewports n'effacent pas l'image déjà présente dans le frame buffer; ils s'affichent toujours en surimpression. Dans le cas de viewports jointifs comme dans notre exemple, cela ne porte pas à conséquence. Mais en cas de chevauchement, avec la caméra principale ou avec un autre viewport, l'image sous-jacente sera visible partout où il n'y a pas d'objet dans le viewport.
Pour supprimer un viewport, il suffit de mettre la propriété useViewport de la caméra correspondante à False.
XXXIII-B. Le miroir▲
Le miroir est un cas particulier qui peut être utile par exemple dans le cas d'un jeu de voiture pour simuler un rétroviseur. L'idée est d'afficher le point de vue d'une caméra de la scène comme une texture d'un objet.
Pour cela, nous utiliserons un script Python écrit comme suit :
from
bge import
texture
from
bge import
logic
def
createMirror
(
cont):
obj =
cont.owner
scene =
logic.getCurrentScene
(
)
observer =
scene.objects["Camera"
]
# Obtention de l'index de la texture initiale
ID =
texture.materialID
(
obj, 'IMoriginal.png'
)
# Création de la nouvelle texture
object_texture =
texture.Texture
(
obj, ID)
# il faut garder une référence permanente
obj.attrDict["tex"
] =
object_texture
# créons le miroir
mirror_source =
texture.ImageMirror
(
scene,observer,obj,ID)
# changeons la couleur de fond du miroir (RGBA)
mirror_source.background =
[50
,0
,0
,255
]
# échange de texture
object_texture.source =
mirror_source
# remplacement dans le GPU
object_texture.refresh
(
True
)
def
runMirror
(
cont):
obj =
cont.owner
if
"tex"
in
obj.attrDict:
# rafraichissons l'image du miroir
obj.attrDict["tex"
].refresh
(
True
)
def
removeMirror
(
cont):
obj =
cont.owner
try
:
del
obj.attrDict["tex"
]
except
:
pass
À deux différences près, ce script ressemble beaucoup à celui présenté dans le chapitre Matériaux dynamiques et textures animées de la section Développer l'univers du jeu.
L'objet image est de type ImageMirror :
mirror_source =
texture.ImageMirror
(
scene,observer,obj,ID)
Le constructeur requiert les paramètres suivants :
- la scène courante ;
- l'objet qui observe le miroir (l'image du miroir est prise de son point de vue), généralement ce sera la caméra courante pour que le reflet ait l'air naturel ;
- l'objet sur lequel l'effet miroir sera projeté ;
- l'ID du matériel dont la texture sera remplacée.
Il est nécessaire de rafraichir l'image du miroir fréquemment pour tenir compte du déplacement de l'observateur. Ceci est réalisé par la fonction runMirror qui doit être exécutée à chaque frame ou chaque fois que l'observateur bouge.
Pour réaliser cet effet miroir, le BGE effectue un rendu spécial à partir d'un point symétriquement opposé à l'observateur par rapport au miroir. Ce rendu est appliqué à l'objet via la texture. Pour que le reflet ne soit pas déformé, il faut que la texture recouvre au mieux la surface de l'objet miroir. Nous obtiendrons ce résultat grâce à la fonction UV mapping > Project from View (Bounds).
Fichier référence : mirroir-panda.blend