Gérer le clavier

Ce didacticiel suppose que vous connaissiez la définitions des différents noeuds, events et spécifications de base du VRML97.
Vous pourrez trouver toutes ces infos (en français) sur le site de 3Dnet.
N'oubliez pas que tous les ROUTE doivent se trouver tout à la fin du fichier.

COURS

Avertissement :

Cet exercice est basé sur l'utilisation du noeud KeySensor dévellopé par Blaxxun. Ce noeud fonctionne exclusivement sur les plugins Blaxxun Contact et BSContact de Bitmanagement. En conséquence, vous devrez disposer d'un de ces plugins pour pouvoir visualiser correctement les scènes d'exemple. Si vous testez ces scènes avec un autre plugin (Cortona, Cosmo Player, Pivoron, etc... ), il ne se passera tout simplement rien du tout.

But :

Apprendre à utiliser le clavier pour déclencher des interactions ou manipuler des objet dans une scène.
Voici plusieurs cours et exercices ou vous apprendrez :

Inclure l' EXTERNPROTO :

Le KeySensor est un EXTERNPROTO qui doit être déclaré pour éviter l'affichage de messages d'erreur. Voici la syntaxe de la déclaration :

EXTERNPROTO KeySensor[
	eventIn SFBool eventsProcessed
	exposedField SFBool enabled
	eventOut SFInt32 keyPress
	eventOut SFInt32 keyRelease
	eventOut SFInt32 actionKeyPress
	eventOut SFInt32 actionKeyRelease
	eventOut SFBool shiftKey_changed
	eventOut SFBool controlKey_changed
	eventOut SFBool altKey_changed
	eventOut SFBool isActive
	]
[
	"urn:inet:blaxxun.com:node:KeySensor",
	"http://www.blaxxun.com/vrml/protos/nodes.wrl#KeySensor",
	"nodes.wrl#KeySensor"
]

RAPPEL

Les scènes comportant cette déclaration seront lisibles par d'autre plugins sans afficher de message d'erreur.
Je rappel toutefois que le clavier ne sera pris en compte que sur les plugins suivant : Blaxxun Contact et BSContact

Pour ces exercices, nous créons un nouveau fichier. Nous y copions tout de suite la déclaration de l'EXTERNPROTO KeySensor.

Nous rajoutons un noeud KeySensor que nous nommerons clavier :

DEF clavier KeySensor {
}

Code de la touche:

Il nous faut un moyen de savoir sur quelle touche l'utilisateur a appuyé.
Nous ajoutons donc un script gestionnaire avec un eventIn de type SFInt32 nommé Kpressed.
Puis nous routons l' eventOut KeyPress de notre noeud clavier vers l'eventIn Kpressed du script gestionnaire :

DEF	gestionnaire Script {
	eventIn	SFInt32	Kpressed
}
ROUTE clavier.keyPress	TO gestionnaire.pressed

Nous ajoutons à ce script une fonction Kpressed() qui nous servira à afficher la valeur de la touche enfonçée. Nous récupèrons la valeur de l'eventIn Kpressed et nous l'affichons dans la console via la fonction print() :

url	"javascript:
function Kpressed(val){
	print ('le code de la touche enfoncee est '+val) ;
Voir la scène Voir le source

Comme vous le constatez, la scène est entièrement vide. Appuyez sur un touche du clavier. Dans la console, il doit s'afficher un nombre entier correspond au code de la touche. Par exemple, il doit s'afficher "32" si vous appuyer sur la barre d'espacement.
Si la console de s'affiche pas d'elle même, affichez là via le menu contextuel.

Vous pouvez de cette façon, retrouver le code de chaque touche de votre clavier.

Le relâchement d'une touche :

Nous avons vu comment savoir le code d'une touche enfoncée. Il est tout aussi intéressant de savoir si une touche est relâchée, et de récupérer sont code.

La procédure est similaire à la détection de l'enfoncement d'une touche. La seul différence, c'est que ici nous routons l'eventOut keyRelease du noeud clavier vers un eventIn Kreleased de type SFInt32 du gestionnaire :

DEF	gestionnaire Script {
	eventIn	SFInt32	Kreleased
	url	"javascript:
	function Kreleased(val){
		print ('le code de la touche relachee est '+val) ;
	}
	"
}
ROUTE clavier.keyRelease TO	gestionnaire.Kreleased

Voir la scène Voir le source

Ce principe de détection de relâchement de touches est semblable pour les touches "standarts" et les "actionKeys".

Les actions keys :

Sur le clavier de votre machine, il existe des touches spéciales qui ne sont pas détectées via l'eventout keyPressed. Ces touches sont par exemple les quatre fléches, page précedente et suivante, fin de ligne, etc.... Ces sont ce que nous appelons les actionsKeys.
Il y a un moyen de détecter si elles sont pressé ou relâchées via les eventOut actionKeyPressed et actionKeyReleased.
Pour récupérer le code de ces touches, nous allons simplement router l'eventout actionKeyPressed du noeud clavier vers l'eventIn Kpressed de notre script gestionnaire.

DEF	gestionnaire Script {
	eventIn	SFInt32	Kpressed
	url	"javascript:
	function Kpressed(val){
		print ('le code de la touche enfoncee est '+val) ;
	}
	"
}
ROUTE clavier.keyPress	TO gestionnaire.Kpressed
ROUTE clavier.actionKeyPress TO gestionnaire.Kpressed

Voir la scène Voir le source

Ctrl, Maj et Alt :

Il existe encore d'autres touches spéciales : Maj, Ctrl et Alt. Ces touches ont un numéro code comme toutes les touches, mais le noeud KeySensor dispose d'eventOut spécifique à chacune de ces clé.
En effet, les eventOut shiftKey_changed, controlKey_changed et altKey_changed sont de type SFBool. Nous pouvons là aussi très facilement mettre en place un sytème qui nous permet de savoir si la touch Ctrl par exemple est enfonçé :

DEF	gestionnaire Script {
	eventIn	SFBool ctrl
	url	"javascript:
	function ctrl(val){
		if (val) {
			print ('controle pressed') ;
		}
	}
	"
}
ROUTE clavier.controlKey_changed TO gestionnaire.ctrl

La condition if(val) vérifie que la touche Ctrl est enfonçée.Si c'est vrai, nous affichons un message.
Le principe est bien sûr similaire pour les touches Maj et Alt.

Voir la scène Voir le source

Nous pourrions router les trois eventOut shiftKey_changed, controlKey_changed et altKey_changed vers le même eventIn SFBool, mais celà ne présente pas beaucoups d'intérêt. En plus, nous ne pourrions même pas savoir sur laquelle de ces touches nous appuyons.

Pour la détection du relâchement des touches Ctrl, Maj et Alt , nous prenons simplement le cas où la condition est fausse :

DEF	gestionnaire Script {
	eventIn	SFBool ctrl
	url	"javascript:
	function ctrl(val){
		if (val) {
			print ('controle pressed') ;
		}
		else{
			print ('control released') ;
		}
	}
	"
}
ROUTE clavier.controlKey_changed TO gestionnaire.ctrl

Voir la scène Voir le source

Dans cet exemple, nous ne traitons que le cas de la touche Ctrl.

Instant d'action :

L'instant d'action est le moment ou se produit un évènement.

Dans le cas de notre KeySensor, il est intéressant de savoir quand est-ce que l'on appuis ou relâche une touche du clavier. Par exemple pour lancer ou arrêter un timeSensor.
Nous utilisons pour celà une des propriétés implicite de tout eventIn :

Note

Chaque eventIn, et ce quelque soit son type renvois deux informations :

  • la valeurs qu'il reçoit. Un nombre dans le cas d'un eventIn de type SFInt32, TRUE ou FALSE dans le cas d'un eventIn de type SFBool.
  • le temps absolu quand il reçoit l'évènement qui lui est routé.

En supposant ici que instant est un eventOut ou un field de type SFTime, celà nous donne une structure de fonction du type :

function mon_eventIn (value,time){
	instant = time ;
}

Les informations de l'eventIn sont récupérées via la fonction qui lui est associée.
Voici un exemple de ce qu'il nous faut :

DEF	gestionnaire Script {
	eventIn	SFInt32	Kpressed
	eventIn	SFInt32	Kreleased
	url	"javascript:
	function Kpressed(value,time){
		print ('le code de la touche enfoncee est '+value) ;
		print ('a l\'instant '+time) ;
	}
	function Kreleased(value,t){
		print ('le code de la touche relachee est '+value) ;
		print ('a l\'instant '+time) ;
	}
	"
}

Voir la scène Voir le source

Etat d'une touche :

L'état d'une touche définis son status. Celle-ci a deux états possibles : enfoncée ou relâchée.
Le but est au final d'émuler une propriété viruelle key_changed pous une touche quelconque à l'image de l'eventOut _changed des touches Ctrl, Maj et Alt.
Pour l'exemple, nous mémoriserons l'état de la touche P .

Le principe est simple. La fonction Kpressed() qui reçoit l'eventOut keyPressed du noeud clavier met un eventOut etatP de type SFBool à true.
La fonction Kreleased() qui reçoit l'enventOut keyreleased de clavier met l'eventOut etatP à false :

DEF	gestionnaire Script {
	eventIn	SFInt32	Kpressed
	eventIn	SFInt32	Kreleased
	eventOut SFBool etatP 
	url	"javascript:
	function Kpressed(val){
		if (val==80 ) {	
			etatP = true ;
		}
	}
	function Kreleased(val) {
		if (val==80){
			etatP = false ;
		}
	}
	"
}

Mémoriser l'état d'une touche :

Nous avons vu précédemment comment attribuer une propriété virtuelle _changed à n'importe quelle touche du clavier.
Il est très utile de mémoriser cette état car le field etatP deveint accessible pour d'autres fonctions.
Cela est aussi utile pour éviter le problème de répétition lors du maintien d'une touche.
Pour l'exemple, nous mémoriserons l'état de la touche P .
La fonction Kpressed() qui reçoit l'eventOut keyPressed du noeud clavier met un field pression de type SFBool à true.
La fonction Kreleased() qui reçoit l'enventOut keyreleased de clavier met le field pression à false :

DEF	gestionnaire Script {
	eventIn	SFInt32	Kpressed
	eventIn	SFInt32	Kreleased
	field SFBool pression FALSE
	url	"javascript:
	function Kpressed(val){
		if (val==80 ) {	
			pression = true ;
		}
	}
	function Kreleased(val) {
		if (val==80){
			pression = false ;
		}
	}
	"
}

Dorénavant, n'importe quelle fonction du script gestionnaire peut ainsi accéder à la variable pression. Nous pouvons donc savoir en permanence si une touche est enfoncée ou pas..

Revenir au cours
La F.A.Q.

Exercices

Exercice N° 1 : jouer une animation en maintenant une touche
Exercice N° 2 : lancer et arrêter une animation en pressant une touche

Exercice N° 1 : jouer une animation en maintenant une touche :

Dans ce premier exercice, nous allons déclencher l'animation d'une boite via un KeySensor.
La boite sera animée d'un va et vient sur l'axe X.
Le fonctionnement est le suivant : tant que nous maintenons la touche P enfonçée, l' animation se joue. Si nous relâchons la touche P, l' animation s'arrête.

La première étape consiste donc à inclure l'EXTERNPROTO KeySensor.
Puis, nous ajoutons à la scène un noeud KeySensor que nous nommons clavier :

DEF clavier KeySensor {
}

Ensuite, nous plaçons notre boite dans un noeud Transform que nous nommons obj :

DEF	obj Transform {
	children Shape {
		geometry Box {
		}
		appearance Appearance {
			material Material {
				diffuseColor 1 0 0
			}
		}
	}
}

Nous créons un timeSensor nommé timer d'un cycleInterval de 5 secondes. Il tourne en boucle mais sera désactivé par défaut.

DEF	timer TimeSensor {
	cycleInterval 5
	loop TRUE
	enabled	FALSE
}

Il nous faut un positionInterpolator pour animer la position de obj. Nous le nommons interpoleur. Le mouvement sera un va et vient de 10 unités sur l'axe des X :

DEF	interpoleur PositionInterpolator {
	key [0 .5 1]
	keyValue [-5 0 0 5 0 0 -5 0 0]
}

Nous routons le fraction_changed de timer vers le set_fraction de interpoleur.

ROUTE timer.fraction_changed TO	interpol.set_fraction

Puis le routage de value_changed de interpoleur vers le translation de obj :

ROUTE interpoleur.value_changed TO	obj.translation

Nous définissons maintenant un script gestionnaire qui va nous servir à activer ou desactiver timer, ce qui declenchera l'animation de obj. timer est activé lorsque nous enfoncons une touche, et desactivé lorsque nous relachons la touche. Il faut donc que nous routions le keyPressed et le keyReleased de clavier vers ce script. Je passe sur les détails qui ont été abordé ici et ici.

DEF	gestionnaire Script {
	eventIn	SFInt32	Kpressed
	eventIn	SFInt32	Kreleased
	url	"javascript:
	function Kpressed(){
	}
	function Kreleased(){
	}
	"
}
ROUTE clavier.keyPress TO gestionnaire.Kpressed
ROUTE clavier.keyRelease TO	gestionnaire.Kreleased

Nous ajoutons un eventOut active de type SFBool qui sera router vers la propriété enabled de timer :

eventOut SFBool active
......
ROUTE gestionnaire.active TO timer.enabled

L'appuis sur une touche appelle la fonction Kpressed() qui met active sur true.
Le relâchement de la touche appelle la fonction Kreleased() qui met active à false :

ROUTE interpoleur.value_changed TO	obj.translation

Voir la scène Voir le source

Quand on appuis sur une touche, l'animation se joue. Dès que l'on relâche la touche, l'animation s'arrête. Pour l'instant, n'importe quelle touche déclenche l'animation. Si nous voulons que l'animation soit lancée avec une touche particulière, il faut rajouter dans la fonction Kpressed() une condition qui vérifie que c'est bien la bonne touche qui est enfonçé. Et il faut la même condition dans la fonction Kreleased() sinon, e relâchement d'une autre touche stopperais l'animation..
Nous allons commander l'animation avec la touche P du clavier. Son code est 80 :

function Kpressed(val){
		if (val==80) {
			active = true ;
		}
	}
	function Kreleased(){
		if (val==80) {
			active = false ;
		}
	}

Voir la scène Voir le source

Cette fois, l'animation ne fonctionne que si l'on maintien la touche P du clavier.

Exercice N° 2 : lancer et arrêter une animation en pressant une touche :

Cet exercice est très similaire à l'exercice n°1. Cependant, le fonctionnement sera légèrement différent :
Le premier appuis sur la touche P lancera l' animation. Il faudra relâcher la touche et re-appuyer sur la touche P pour arrêter l'animation.

Les objet sont les mêmes qu'à l'exercie n°1. Nous retrouvons donc :

  • le KeySensor clavier
  • le timeSensor timer
  • le positionInterpolator interpoleur

Le script gestionnaire étant différent, nous repartirons d'une base épurée. Copiez et collez ce code dans un nouveau fichier.

Le nouveau script gestionnaire sera de cette forme :

DEF	gestionnaire Script {
	eventIn	SFInt32	Kpressed
	eventOut SFBool active 
	url	"javascript:
	function Kpressed(val){
	"
}
ROUTE clavier.keyPress TO gestionnaire.Kpressed
ROUTE gestionnaire.active TO timer.enabled

Nous retouvons le routage du keyPressed de clavier vers l'eventIn Kpressed de type SFInt32. Cet eventIn appelera naturellement la fonction Kpressed().
Il y a aussi l' eventOut active de type SFBool qui est routé vers la propriété enabled de timer.
Rien de nouveau donc.

Si nous voulons que l'appuis sur la touche P lance l'animation, il suffit de vérifier le code de la touche pressée est bien 80 (le code de la touche P) et de mettre active à true :

url	"javascript:
	function Kpressed(val){
		if (val==80 ) {			
			active = true ;			
		}
	}
	"

Voir la scène Voir le source

Quand on appuis sur la touche P, l'animation se lance. Mais si l'on rappuis sur la touche P, elle ne s'arrête pas. Il nous faut une donnée qui enregistre si nous avons déja appuyer sur P. Cette donnée est à 2 états : soit nous n'avons pas appuyer sur P, soit nous avons appuyer sur P. Il y a deux états, nous mettrons donc cette donnée sous forme de field de type SFBool dans notre script gestionnaire. Nous appelons cette donnée pression et elle sera à FALSE par défaut.

field SFBool pression FALSE

A chaque appuis sur la touche P, nous inversons etat :

function Kpressed(val){
		if (val==80 ) {
			pression = !pression ;
		}
	}

Si pression est true, cela veut dire que nous avons appuyer une première fois sur P. Nous lançons donc l'animation en mettant active à true. Si au contraire pression est false, nous avons appuyer deux fois sur P. Nous arrêtons donc l'animation en mettant active à false :

function Kpressed(val){
		if (val==80 ) {
			pression = !pression ;
			if (pression) {		
				active = true ;	
			}
			else {
				active = false ;
			}		
		}
	}

Voir la scène Voir le source

Cette fois c'est pas mal. On appuis sur la touche P une fois, ça lance l'animation. On rappuis sur P, ça l'arrête. Si on rappuis encore une fois, l'animation se relance et si l'on rappuis de nouveau, elle s'arrête.

Mais il y a encore quelque chose qui ne va pas. Si nous maintenons P enfonçé, l'animation se lance, puis devient saccadée, et le reste tant que nous maintenons P enfoncée.
Et quand enfin nous relâchons la touche P, soit l'animation s'arrête, soi elle continue...

Qu' est-ce qui se passe ? Pourquoi cela ?
Ceci est dût au délais de répétitions. Je vous invite à revenir sur cette patrie du cours si vous n'avez pas bien compris à quoi cela est dût et quel conséquence cela peut avoir.

Donc, le maintien de la touche P enfoncée inverse la valeur de pression à chaque appel de la fonction Kpressed(). Du coup, active passe de true à false à chaque appel de la fonction. Ce qui a pour conséquence de lancer et stopper l'animation. Il faut enlever ce système d'inversion de la valeur de pression. Nous le remplaçons par la mise à true de pression. Ceci ce fait dans la fonction Kpressed) :

function Kpressed(val){
		if (val==80 ) {	
			pression = true ;
			if (pression) {		
				active = true ;	
			}
			else {
				active = false ;
			}
		}
	}

Si nous conservons cette fonction tel quel, active vaudra toujours true, car la condition pression = true sera toujours vrai étant donné que pression est mis à true juste avant. Mais laissons cela pour l'instant...

Donc, si nous appuyons sur la touche P et si nous la maintenons appuyé, pression vaut true.
Mais il faut mettre pression à false dans le cas ou nous relâchons la touche P. Nous devons pour cela récupérer l'eventOut keyReleased de clavier et le router vers une fonction Kreleased() qui mettra pression à false :

function Kreleased(val) {
		if (val==80){
			pression = false ;
		}
	}
.....
ROUTE clavier.keyRelease TO	gestionnaire.Kreleased

Mais alors, quand et comment lancer et stopper l'animation maintenant ? L'animation est je le rappelle commander par la valeur de l'evenOut active.
Réfléchissons :
- pression vaut au départ false
- quand on appuis une première fois sur P, pression = true. Il faut aussi que active = true.
- quand on relâche P, pression = false, mais active doit toujours valoir true
- quand on appuis une deuxième fois sur P, pression = true , mais active = false
- quand on relâche P pour la deuxième fois, pression = false et active = false

Si nous observons bien ce raisonnement, nous nous apercevons qu'en fait la logique de commande de active est assez simple : à chaque mise à true de pression, nous inversons la valeur de active. Ce qui donnerait :

function Kpressed(val){
		if (val==80 ) {	
			pression = true ;
			if (pression) {
				active = !active ;
			}
		}
	}

Voir la scène Voir le source

Mais ça ne marche pas !!! Il y a toujours l'effet de saccade lorsqu'on maintien la touche P enfoncée !
Mais ceci est normal : pression est mise à true juste avant la condition qui justement vérifie si pression = true. La condition est donc toujours true, et donc à chaque appel de la fonction Kpressed(), la valeur de active est inverser. Notre fonction Kreleased() qui est appelé après la fonction Kpressed() ne sert donc à rien.

En fait, ce qu'il faut faire, c'est inverser la valeur de active quand pression = false, mais ceci AVANT de mettre pression à true. De cette façon, la fonction Kreleased() peut jouer son role de remise à false de pression.
Voici la fonction Kpressed() telle qu'elle doit être :

function Kpressed(val){
		if (val==80 ) {	
			if (!pression) {
				active = !active ;
			}
			pression = true ;
		}
	}

Voir la scène Voir le source

Lors de l'appuis sur la touche P, pression = false. D'une part parce que c'est sa valeur par défaut à l'ouverture de la scène, d'autre part parce qu'il faut forcément relâché la touche avant de pouvoir rappuyer dessus. Or, si nous relâchons la touche, la fonction Kreleased() met pression à false.
Vu que pression = false, la condition est vrai (la condition renvois true SI pression = false), la valeur de active est inverser. Donc l'animation se lance ou s'arrête.
Ensuite, pression = true. Lors du prochain appel à la fonction Kpressed(), pression = true. La condition n'étant pas vérifiée,on ne touche pas à la valeur de active.

Revenir au cours
Revenir aux exercices

F.A.Q.

Q : J'appuis une première fois sur la touche, et après mon KeySensor ne fonctionne plus. Pourquoi ?

R : Si dans le script qui est le lié au KeySensor, il y a une fonction print(), celle-ci ouvre la console du plugin. C'est alors la console qui a le focus système. Donc tout appuis sur une touche par la suite sera envoyé vers la console par le système.
Pour que tout fonctionne correctement, il faut que la scène reprenne le focus. Il suffit pour cela de cliquer dans la vue 3D de la scène. De cette façon, l'appuis sur une touche sera correctement envoyé vers le plugin, qui lui fera suivre le signal vers le KeySensor.

Q : Dans les cours, il est question d'un affichage dans la console. Mais je ne la vois pas ?

R : La console permet d'afficher des informations d'un script contenu dans la scène 3D. Lors de l'appel à la fonction print(), le résultat est envoyé vers la console. Si la console ne s'affiche pas d'elle même, faîtes un clic-droit dans la scène. Dans le menu contextuel, cliquez sur "settings" ou "configuration" et dans le sous-menu, cliquez sur "console". La console apparait alors par dessus la scène.
Vous pouvez faire en sorte que la console s'affiche automatiquement. dans le menu contextuel du clic droit, cliquer sur "Préférences" ou appuyer sur la touche F9. Dans l'onglet "general", cocher "Messages d'erreur VRML complets" ou "Verbose VRML warnings".

Si vous avez des questions, n' hésitez pas à me contacter à cette adresse : Athanaze@fr.st
Je vous répondrais et ajouterais votre questions à cette F.A.Q.


Revenir au cours
Revenir aux exercices

Valid XHTML 1.0 Strict Valid CSS!