Home » Tutoriels ShiVa » TUTO : Déplacement fluide d’un objet grâce aux événements clavier

TUTO : Déplacement fluide d’un objet grâce aux événements clavier

touche-dir1

Cet article explique comment déplacer de manière fluide un objet grâce aux touches directionnelles du clavier.

Tout d’abord, il nous faut créer un projet. je ne vais pas détailler cette étape, il faudra vous référer à la documentation sur le site officiel des développeurs ShiVa.

Dans le projet, ajoutez une scène « myScene » avec une sphère positionnée en (0,0,0). Placez la caméra de manière à la centrer au dessus de l’objet, avec l’axe X du monde dirigé vers la droite, et l’axe Z du monde vers le bas. L’axe Y quand à lui sera donc dirigé face à la caméra.

Enfin, ajoutez une AIModel sur l’user, nous l’appellerons « Main ».

 

1 – Charger la scène au démarrage de l’application :

 

La première chose à faire est d’ajouter un handler « onInit » dans l’AIM « Main ». le handler onInit est appelé automatiquement à l’initialisation de l’AIM, comme l’AIM est sur l’user, le handler sera donc exécuté au lancement de l’application.
Dans ce handler, on va ajouter l’instruction suivante qui va dire à l’application de charger la scene « myScene »:

application.setCurrentuserScene ( "myScene" )

Compliez le script ( CTRL + F7 ) et lancez l’application (F9).

Vous devriez voir votre sphère au milieu de l’écran comme sur l’image ci dessous:

tuto_mouvement_fluide_clavier_sphere

2 – Récupérer l’état des flèches directionnelles :

 

Afin de déplacer notre sphère nous devons connaître l’état (pressée ou relâchée) de chacune des touches directionnelles. Deux handlers vont nous être utile: « onKeyboardKeyDown » et « onKeyboardKeyUp ». Comme vous l’aurez compris, le premier est appelé lorsqu’une touche est enfoncée et le second lorsqu’une touche est relâchée.

Ajoutez les deux handlers (fonction appelée automatiquement ou pas lors d’un évènement) pour que notre code reçoive les évènements d’utilisateur de type « touche pressée » et « touche relâchée »:

  • onKeyboardKeyDown
  • onKeyboardKeyUp

Nous devons également créer une variable par touche pour mémoriser l’état de ces dernières. Ajoutez donc 4 variables de type nombre dans l’AIM « Main »:

  • nLeft
  • nRight
  • nUp
  • nDown

On a maintenant les variables, il ne nous reste plus qu’à modifier leur valeur lorsque l’utilisateur presse ou relâche une touche. Je choisis toujours d’associer la valeur 0 et 1 respectivement pour la touche relâchée et enfoncée mais certains rebelles font l’inverse ! Hum.. pardon, je reprends :

Dans le handler « onkeyboardKeyDown » ajoutez le code suivant:

--------------------------------------------------------------------------------
function Main.onKeyboardKeyDown ( kKeyCode )
--------------------------------------------------------------------------------
if ( kKeyCode == input.kKeyLeft ) then
this.nLeft ( 1 )
elseif ( kKeyCode == input.kKeyRight ) then
this.nRight ( 1 )
elseif ( kKeyCode == input.kKeyUp ) then
this.nUp ( 1 )
elseif ( kKeyCode == input.kKeyDown ) then
this.nDown ( 1 )
end
--------------------------------------------------------------------------------
end
--------------------------------------------------------------------------------

Comme vous pouvez le voir, en fonction de la touche qui est enfoncée ( kKeyCode est le numéro de la touche) on passe la variable correspondant à la touche a 1.

Dans le handler « onKeyboardKeyUp » ajoutez le code suivant :

--------------------------------------------------------------------------------
function Main.onKeyboardKeyUp ( kKeyCode )
--------------------------------------------------------------------------------
if ( kKeyCode == input.kKeyLeft ) then
this.nLeft ( 0 )
elseif ( kKeyCode == input.kKeyRight ) then
this.nRight ( 0 )
elseif ( kKeyCode == input.kKeyUp ) then
this.nUp ( 0 )
elseif ( kKeyCode == input.kKeyDown ) then
this.nDown ( 0 )
end
--------------------------------------------------------------------------------
end
--------------------------------------------------------------------------------

 

Ainsi, lorsque l’utilisateur relâchera une touche, on passera la variable correspondant à la touche à 0.

L’association de ces deux handlers nous permet donc de connaître à tout moment l’état d’une touche ( pressée ou enfoncée ) en récupérant la valeur de la variable correspondant à la touche ( 0: pas enfoncée, 1 : enfoncée).

3 – Utiliser les variables d’état des touches pour déplacer la sphère :

 

La sphère doit se déplacer à chaque fois que l’image est rafraîchie, nous devons donc créer un handler « onEnterFrame » sur l’AIM « Main ». Ce handler comme vous l’aurez sans doute compris est exécuté à chaque fois que le moteur envoie les instruction à la carte graphique pour qu’elle dessine la scène à l’écran.

Dans ce handler nous allons calculer la vitesse que la sphère doit adopter en fonction de l’état des touches. Ensuite à partir de cette vitesse nous devrons calculer le déplacement qu’elle devra effectuer. Et enfin nous devrons déplacer l’objet en fonction du temps qui s’est écoulé depuis la trame précédente. Voici l’équation de base que nous allons utiliser :

P’ = P + V * dt

  • P’ (x’,y’,z’ ) est la position de l’objet sur la trame actuelle, c’est cette position que nous devrons déterminer (exprimé en mètres)
  • P(x,y,z) est la position de l’objet sur la trame précédente (exprimé en mètres)
  • V (vx, vy, vz ) est le vecteur vitesse exprimé en m.s-1 (mètres par seconde), nous la choisirons arbitrairement.
  • dt est le temps écoulé depuis la frame précédente (exprimé en secondes)

 

Très important avant de continuer : nous allons tagger la sphère (clic droit sur l’objet dans la scene -> Ajouter un tag ) c’est à dire, lui donner une « étiquette », cet objet sera alors connu sous cette étiquette et il nous sera très simple d’agir sur l’objet taggé. Saisissez « sphere » comme tag.

Voici le code à insérer dans le handler « onEnterFrame »:

--------------------------------------------------------------------------------
function Main.onEnterFrame ( )
--------------------------------------------------------------------------------
-- Récupère l'objet taggué "sphere"
local s = application.getCurrentUserScene ( )
local hSphere = scene.getTaggedObject ( s, "sphere" )

-- Calcule la vitesse de l'objet en fonction de l'état des touches, dans ce cas la vitesse sur l'axe X et sur l'axe Z seront situées entre -1 et 1 :
local nSpeedX = this.nRight ( ) - this.nLeft ( )
local nSpeedZ = this.nDown ( ) - this.nUp ( )

-- Calcule le déplacement que l'on doit appliquer à la sphère en fonction de la vitesse et du temps de la dernière trame, on multiplie la vitesse par dt
local dt = application.getLastFrameTime ( )
local nDeplacementX = nSpeedX * dt
local nDeplacementZ = nSpeedZ * dt

-- Finalement, on déplace l'objet, on ajoute donc la valeur de déplacement à la position actuelle de l'objet.
object.translate ( hSphere, nDeplacementX, 0, nDeplacementZ, object.kGlobalSpace )

--------------------------------------------------------------------------------
end
--------------------------------------------------------------------------------

 

Compilez et testez ! Vous remarquez que la sphère se déplace en fonction des touches directionnelles.

Cependant, la vitesse de déplacement peut ne pas vous convenir. Afin de nous permettre de la régler nous allons ajouter une variable supplémentaire dans l’AIM « Main ». Elle sera de type nombre nous la nommerons nSphereSpeed elle va nous servir à régler précisément la vitesse de la sphère.

Modifions maintenant notre code pour prendre en compte cette valeur:

--------------------------------------------------------------------------------
function Main.onEnterFrame ( )
--------------------------------------------------------------------------------
-- Récupère l'objet taggé "sphere"
local s = application.getCurrentUserScene ( )
local hSphere = scene.getTaggedObject ( s, "sphere" )

-- Calcule la vitesse de l'objet en fonction de l'état des touches, en réalité cette vitesse peut prendre -1, 0 et 1 comme valeur.
local nSpeedX = this.nRight ( ) - this.nLeft ( )
local nSpeedZ = this.nDown ( ) - this.nUp ( )

-- on multiplie la vitesse par nSphereSpeed
nSpeedX = nSpeedX * this.nSphereSpeed ( )
nSpeedZ = nSpeedZ * this.nSphereSpeed ( )

-- Calcule le déplacement que l'on doit appliquer à la sphere en fonction de la vitesse et du temps de la dernière trame
local dt = application.getLastFrameTime ( )
local nDeplacementX = nSpeedX * dt
local nDeplacementZ = nSpeedZ * dt

-- Finalement, on déplace l'objet par rapport au repère du monde (kGlobalSpace)
object.translate ( hSphere, nDeplacementX, 0, nDeplacementZ, object.kGlobalSpace )

--------------------------------------------------------------------------------
end
--------------------------------------------------------------------------------

 

Vous pouvez ainsi très facilement changer la vitesse de la sphère en modifiant cette valeur, sans avoir à modifier votre code.

 

4 – Fluidité :

 

Afin de rendre plus souple le mouvement de notre sphère, on va y rajouter un peu d’inertie. pour cela, la fonction qui va nous aider c’est:

math.interpolate ( nValeurSource, nValeurCible, nFacteur )

 

Cette fonction nous permet de faire tendre une valeur source vers une valeur cible grâce à un facteur. Ce facteur est une valeur comprise entre 0 et 1. Plus le facteur s’approche de 1, plus la valeur renvoyée par la fonction sera proche de la valeur cible. Cette fonction interpole de manière linéaire.

On va utiliser cette fonction pour interpoler la vitesse de la sphère lentement plutôt que de la changer brutalement comme c’est le cas pour l’instant.

Pour continuer, nous avons aussi besoin de 3 nouvelles variables à placer dans l’AIM « Main »:

  • nLastSpeedX de type nombre
  • nLastSpeedZ de type nombre
  • nSmoothFactor de type nombre et de valeur 10.

Voila le code prenant en compte ces nouvelles variables :

--------------------------------------------------------------------------------
function Main.onEnterFrame ( )
--------------------------------------------------------------------------------

local dt = application.getLastFrameTime ( )

-- Récupère l'objet taggé "sphere"
local s = application.getCurrentUserScene ( )
local hSphere = scene.getTaggedObject ( s, "sphere" )

-- Calcule la vitesse de l'objet en fonction de l'état des touches
local nSpeedX = this.nRight ( ) - this.nLeft ( )
local nSpeedZ = this.nDown ( ) - this.nUp ( )
nSpeedX = nSpeedX * this.nSphereSpeed ( )
nSpeedZ = nSpeedZ * this.nSphereSpeed ( )

-- Smooth des valeurs
nSpeedX = math.interpolate ( this.nLastSpeedX ( ), nSpeedX, this.nSmoothFactor ( ) * nLastFrameTime)
nSpeedZ = math.interpolate ( this.nLastSpeedZ ( ), nSpeedZ, this.nSmoothFactor ( ) * nLastFrameTime)

-- mémorise les valeurs de vitesse
this.nLastSpeedX ( nSpeedX ) this.nLastSpeedZ ( nSpeedZ )

-- Calcule le déplacement que l'on doit appliquer à la sphère en fonction de la vitesse et du temps de la dernière trame
local nDeplacementX = nSpeedX * dt
local nDeplacementZ = nSpeedZ * dt

-- Finalement, on déplace l'objet
object.translate ( hSphere, nDeplacementX, 0, nDeplacementZ, object.kGlobalSpace )
--------------------------------------------------------------------------------
end
--------------------------------------------------------------------------------

 

Compilez et testez. Vous remarquerez que lorsque vous lâchez les touches directionnelles, la sphère ne s’arrête pas tout de suite, mais elle ralenti progressivement. Vous pouvez régler la valeur de nSmoothFactor à votre guise ( de 0.0001 à l’infini ).

Et voilà, ce tutorial touche à sa fin. J’espère qu’il vous a plu. A bientôt…

 

6 – Note additionnelle :

Nous aurions pu utiliser des variables booléennes afin de stocker l’état des touches. mais cela nous aurait un peu compliqué le code lorsque nous avons calculé la vitesse comme vous pouvez le constater :

local nSpeedX, nSpeedY

if ( this.bLeft () and not this.bRight () ) then
      nSpeedX = -1
elseif ( not this.bLeft() and this.bRight() ) then
      nSpeedX = 1
else
      nSpeedX = 0
end

if ( this.bUp() and not this.bDown() ) then
      nSpeedZ= -1
elseif ( not this.bUp() and this.bDown() ) then
      nSpeedZ = 1
else
      nSpeedZ = 0
end

 

Laisser un commentaire