Accueil Date de création : 08/06/07 Dernière mise à jour : 28/01/10 21:47 / 17 articles publiés
 

Tutorial 6 Memory coloré (2/2)  posté le jeudi 14 juin 2007 21:46

 

Lors du dernier tutorial je vous ai présenté un jeu de memory. Nous allons maintenant en analyser le code. Commençons par observer les variables globales de ce script :

 

// Nombre de paires de couleurs
integer NombrePaires;
// Liste des etats des plots
list Visible = [];
// Liste melange des plots
list Melange;
// Liste des couleurs
list Couleurs = [];
// Premier plot touche
integer Premier;
// Second plot touche
integer Second;
// Constante de communication
integer GO = 10;
// Constante pour les plots
integer NON_TOUCHE = -1;

 

La première concerne le nombre de plots. L'exemple que j'ai réalisé sur SL en comporte 16, donc 8 paires. En programmation une règle important est d'aller du général au particulier. Il est toujours judicieux de prévoir un paramétrage complet pour ne pas avoir par la suite à modifier plusieurs lignes de code. Avec ce script il est facile de changer le nombre de plots, il suffit d'ajuster la valeur de la variable NombrePaires. Nous avons ensuite trois list déclarés. Les plots sont repérés simplement par leur numéro d'ordre dans la liaison des prims. Si vous réalisez le même ensemble que celui que j'ai présenté au dernier tuto (en respectant l'ordre pour le link) vous avez 18 prims en tout. Le root est numéroté 1, le bouton 2, les plots de 3 à 18. La liste Melange est destinée à conserver les numéros des plots dans un ordre aléatoire (pour avoir les couleurs mélangées), elle est donc composée de 16 valeurs de type integer. Les paires sont constituées pas les numéros d'ordre qui se suivent. Autrement dit les deux premiers dans la liste constituent la première paire, les deux suivants la paire suivante, et ainsi de suite. Cette liste peut donc être par exemple composée ainsi :

 

Plot
15
3
7
13
9
16
4
8
14
17
10
5
11
18
6
12
Paire
1
2
3
4
5
6
7
8

 

La liste Visible est destinée à mémoriser l'état d'un plot : couleur visible ou non visible. Elle contient donc autant de valeur que le nombre de plots, en l'occurrence 16. Au départ toutes ces valeurs sont initialisées à 0. La liste Couleurs sert à mémoriser la couleur des plots, elle est remplie de vector (une couleur est représentée par un vecteur avec ses trois composantes additives rouge, vert et bleu) et contient aussi autant de valeurs que de nombre de plots. Il aurait été évidemment possible de limiter le nombre de valeurs des listes Visible et Couleurs au nombre de paires, mais le code aurait été un peu plus complexe alors j'ai préféré privilégier la simplicité. Que les esprits chagrins ne m'en tiennent pas trop rigueur.

Les variables Premier et Second sont destinées à mémoriser à chaque instant les numéros des premiers et deuxièmes plots touchés. Au départ elles seront initialisées à la valeur -1 qui signifient qu'aucun plot n'a été touché. J'ai enfin prévu deux constantes destinées à améliorer la lisibilité du code mais j'y reviendrai en temps utile.

Voyons maintenant ce qui se passe au démarrage du script :

 

default
{
   // Attente action du bouton
   link_message(integer sender_number, integer number, string message, key id)
   {
      // Depart du jeu
      if(number == GO)
      {
         initialisations();
         state PremierPlot;
      }
      return;
   }
}

 

Vous ne connaissez pas encore cet événement link_message. Il concerne la communication entre prims liées. Pour mieux comprendre allons voir le code du bouton :

 

integer GO = 10;

default
{
   touch_start(integer total_number)
   {
      llMessageLinked(LINK_ROOT, GO, "", NULL_KEY);
   }
}

 

Dès que celui-ci est touché il est mis en oeuvre la fonction llMessageLinked qui est destinée à envoyer un message aux prims liés. Le wiki vous parle un peu de cette fonction :

 

http://www.lslwiki.net/lslwiki/wakka.php?wakka=llMessageLinked

 

Le premier paramètre indique à qui s'adresse le message, ici nous visons le root, puisque c'est lui qui contient le script du jeu. Le second paramètre est un integer que nous allons utiliser pour distinguer les messages. ici nous déterminons que la valeur 10 (constante GO) signifie qu'on veut démarrer le jeu. C'est totalement arbitraire bien sûr. Les deux autres paramètres ne nous sont pas utiles.

Maintenant si nous en revenons au code d'entrée du script principal, l'événement link_message attend justement un message. Le premier paramètre indique le numéro du prim qui envoie le message, les trois autres paramètres correspond aux trois de la fonction, ce qui est rassurant ! Vous voyez donc que le test pour savoir si number est égal à GO signifie "est-ce qu'on doit démarrer le jeu ?". Si c'est le cas on procède aux initialisations nécessaires avec la procédure initialisations que nous allons voir plus loin et nous passons à l'état PremierPlot qui sert à attendre qu'on touche un premier plot. Si ce n'est pas GO (par exemple on a touché un plot ou le support) alors le return sort de l'événement et on se remet à l'écoute.

Voyons donc de plus près ces initialisations :

 

initialisations()
{
   // Initialisation du nombre de paires
   NombrePaires = 8;
  
// Initialisation des touches
   Premier = NON_TOUCHE;
   Second = NON_TOUCHE;
   // Nombre total de plots
   integer NombrePlots = NombrePaires * 2;
   // Index du debut des plots
   integer Debut = llGetNumberOfPrims() - NombrePlots + 1;
   // Creation et remplissage de la liste des plots
   integer x;
   list Plots = [];
   for(x = Debut; x < NombrePlots + Debut; x++)
   {
      Plots += x;
      // Plots noirs au depart
      llSetLinkColor(x, <0,0,0>, ALL_SIDES);
   }
   // Creation d'une liste melange des plots
   Melange = llListRandomize(Plots, 1);
   // Creation de la liste des couleurs des plots par paires
   for(x = 0; x < NombrePaires; x++)
   {
      vector col = <llFrand(1),llFrand(1),llFrand(1)>;
      Couleurs += col;
      Couleurs += col;
   }
   // Remplissage de 0 de la liste Visible
   for(x = 0; x < NombrePlots; x++)
      Visible += [0];
   // Avis de depart
   llSay(PUBLIC_CHANNEL, "Jeu en cours");
}

 

Les deux variables Premier et Second sont initialisées à la valeur par défaut NON_TOUCHE qui signifie qu'aucun plot n'est encore touché. Ensuite on calcule le nombre de plots à partir du nombre de paires, ce qui ne nous est pas très difficile, cette valeur va nous servir pour les calculs ultérieurs. Ensuite on veut savoir à quel numéro commence le premier plot. Dans le cas où on a juste prévu un support et un bouton on sait que le premier plot est le 3. Mais il est toujours prudent de systématiser ce genre de chose. La fonction llGetNumberOfPrims permet de connaître le nombre total de prims liés. Il suffit alors d'enlever le nombre de plots et d'ajouter 1 pour avoir le numéro du premier.

Ensuite nous allons remplir la liste des plots. Dans une première liste Plots nous allons mettre dans l'ordre les numéros de plots. Pour cela nous utilisons une boucle for. Ici aussi le wiki peut vous aider au niveau de la syntaxe :

 

http://www.lslwiki.net/lslwiki/wakka.php?wakka=for

 

Une variable (x) est utilisée, elle a une valeur initiale (Debut), elle s'incrément (x++ signifie qu'on ajoute 1 à chaque fois, c'est équivalent à x = x +1 mais avec une écriture plus concise) et elle a une valeur limite (NombrePlots + Debut). Dans notre cas x commence à 3 et finit avant 19 (16 + 3), donc à 18. Chaque valeur de x est ajoutée à la liste Plots avec l'instruction Plots += x. Encore une syntaxe simplifiée de Plots = Plots + x. Au passage on en profite pour noircir le plot avec la fonction llSetLinkColor qui permet de colorer une prim liée en indiquant son numéro, la couleur représentée par un vector et les faces qui doivent être colorées. Dans notre cas la constante ALL_SIDES indique que nous intervenons sur toutes les faces, ce qui est convenable puisqu'une seule est apparente.

Nous savons que nous voulons les plots mélangés dans la liste Melange. C'est la fonction llListRandomize qui fait cela automatiquement (pourquoi je me plains de LSL moi finalement ? . Ensuite une autre boucle for est destinée à remplir la liste Couleurs. Puisque les couleurs marchent par paires on se contente de boucler sur celles-ci. Pour avoir une génération de couleur aléatoire on ut lise la fonction llFrand qui avec le paramètre 1 générer une valeur entre 0 et 1, pile ce qui nous faut pour les éléments de notre vecteur de couleur.

Il ne nous reste plus qu'à remplir la liste Visible avec des 0. C'est ce que nous faisons avec une dernière boucle for. Vous remarquez que lorsqu'il n'y a qu'une instruction dans un bloc on peut se passer des accolades, ce qui simplifie un peu la syntaxe. On termine avec un petit message sur le Chat pour dire que le jeu est en cours... Nous pouvons maintenant passer à l'analyse de l'état PremierPlot :

 

state PremierPlot
{
   // Reception d'un message des plots
   link_message(integer sender_number, integer number, string message, key id)
   {
      // Nouveau depart du jeu ?
      if(number == GO)
      {
         initialisations();
         return;
      }
      // Plot deja visible ?
      if(TestVisible([sender_number]))
         return;
      else
      {
         // Affectation premier plot
         Premier = sender_number;
         // Colorisation du plot
         ColorePlot(sender_number);
         // Passage au choix du second plot
         state SecondPlot;
      }
   }
}

 

Ici aussi nous attendons un message. Si nous recevons celui du bouton (GO) nous procédons aux initialisations pour redémarrer le jeu. sinon nous allons voir si le plot touché n'est pas déjà visible avec la fonction TestVisible que nous verrons plus loin. Si le plot n'est pas visible alors nous affectons Premier avec son numéro et nous colorons le plot avec la procédure ColorePlot que nous verrons également plus loin. Enfin nous passons à l'état SecondPlot pour attendre le second plot touché. Mais avant de voir cet état explorons les méthodes que nous avons vues passer :

 

// Renvoie la valeur de visibilite TRUE (1) ou FALSE (0)
integer TestVisible(list sender_number)
{
   integer Index = IndexPlot(sender_number);
   return llList2Integer(Visible, Index);
}

 

La fonction TestVisible a pour objet de nous renseigner sur le fait qu'un plot est déjà coloré ou pas. Nous connaissons le numéro d'ordre du plot mais pas son emplacement dans la liste Melange (puisque nous avons tout mélangé !). Une autre fonction nous est donc nécessaire pour connaître l'index du plot dans cette liste :

 

// Renvoie l'index du Plot
integer IndexPlot(list sender_number)
{
   return llListFindList(Melange, sender_number);
}

 

On utilise la fonction llListFindList dont je vous ai parlé au tuto précédent et qui a une liste comme paramètre, d'où le type de paramètre de la fonction. On en revient à TestVisible qui est maintenant renseignée sur l'index du plot. Il suffit alors d'aller lire avec la fonction llList2Integer la valeur correspondante. Un 0 nous dit que la couleur n'est pas visible et un 1 l'inverse.

Nous pouvons maintenant voir l'état SecondPlot :

 

state SecondPlot
{
   // Reception d'un message des plots
   link_message(integer sender_number, integer number, string message, key id)
   {
      // Nouveau depart du jeu ?
      if(number == GO)
      {
         initialisations();
         state PremierPlot;
      }
      // Deja un second plot touche ?
      if(Second != NON_TOUCHE)
         return;
      // Deuxieme plot choisi
      Second = sender_number;
      if(TestVisible([Second]) || Second == Premier)
         return;
      else
      {
         // Colorisation du plot
         ColorePlot(Second);
         // Test paire trouvee
         if(TestePaire(Premier, Second))
         {
            // Affecte visible pour les deux plots
            SetVisible(Premier);
            SetVisible(Second);
            // Reinitialisation
            Premier = NON_TOUCHE;
            Second = NON_TOUCHE;
            // Test de fin du jeu
            if(TestFin())
               llResetScript();
            // Retour au choix du premier plot
            state PremierPlot;
         }
         else
            // Timer 2 secondes
            llSetTimerEvent(2);
      }
   }

   // Temporisation
   timer()
   {
      // Remise a noir de la couleur des deux plots
      llSetLinkColor(Premier, <0,0,0>, ALL_SIDES);
      llSetLinkColor(Second, <0,0,0>, ALL_SIDES);
      // Reinitialisation des touches
      Premier = NON_TOUCHE;
      Second = NON_TOUCHE;
      // Reset du timer
      llSetTimerEvent(.0);
      // Retour au choix du premier plot
      state PremierPlot;
   }
}

 

Un peu plus de code là parce que le traitement est plus complexe. Le départ est identique à l'état PremierPlot. On attend un message. Si c'est un GO on réinitialise. Sinon on se demande si le plot touché est déjà touché (pour éviter une sélection multiple pendant la temporisation). Ensuite un double test : le plot est-il déjà visible ? Correspondit au premier plot sélectionné ? Le but est d'éliminer toutes les actions parasites. Si on est satisfait de ces tests on colore le plot et on se demande si on est tombé sur une paire. Pour cela on a prévu une fonction TestePaire :

 

integer TestePaire(integer a, integer b)
{
   if(llList2Vector(Couleurs, IndexPlot([a])) == llList2Vector(Couleurs, IndexPlot([b])))
      return TRUE;
   return FALSE;
}

 

Elle attend en paramètres les numéros des deux plots. Ensuite on précède à un test pour comparer les deux couleurs. On sait que les couleurs sont dans la liste Couleurs. La fonction permet d'extraire un vector d'une liste en indiquant son index. On utilise à nouveau la fonction IndexPlot. La valeur de retour est TRUE ou FALSE selon le résultat de la comparaison. Revenons alors à la suite du code de l'état SecondPlot. S'il s'agit d'une paire on renseigne la liste Visible avec la méthode SetVisible :

 

// Affecte la visibilite au Plot
SetVisible(integer plot)
{
   integer Index = IndexPlot([plot]);
   Visible = llListReplaceList(Visible, [1], Index, Index);
}

 

Le but est de mettre un 1 à la place du 0 à l'index qui correspond au plot donc le numéro est passé en index. La fonction llListReplaceList permet de remplacer une ou plusieurs valeurs dans une liste. Si nous en revenons à l'état on réinitialise les variable Premier et Second. On se demande ensuite si le jeu est fini. Est-ce que la paire trouvée est la dernière ? A nouveau une fonction est destinée à nous renseigner :

 

// Test de fin
integer TestFin()
{
   if(llListFindList(Visible, [0]) == -1)
      return TRUE;
   return FALSE;
}

 

Comment savons-nous que c'est terminé ? Tout simplement que la liste Visible est remplie de 1. On fait donc un test dans cette liste pour savoir si elle contient encore un 0. A noter au passage que cette fonction aurait pu être simplifiée en return (llListFindList(Visible, [0]) == -1); mais elle est peut-être plus explicite ainsi. La même remarque vaut pour le test des paires. Si on est arrivé à la fin du jeu on fait un reset du script, sinon on retourne à l'état PremierPlot.

Maintenant que se passe-t-il si ce n'est pas une paire ? Dans ce cas nous démarrons un timer avec la fonction llSetTimerEvent dont le seul paramètre indique le nombre de secondes à attendre. Ceci est destiné à laisser le temps de voir les deux couleurs suffisamment avant de les effacer. Le délai est réglé à deux secondes, vous pouvez évidemment le changer s'il vous paraît trop court ou trop long. Au bout du délai l'événement timer est déclenché. Dans un premier temps on remet du noir sur les deux plots puis on réinitialise les variables Premier et Second. On prend aussi la précaution de réinitaliser le timer sinon toutes les deux secondes il va continuer à se déclencher même si on change d'état ! Enfin on retourne à l'état PremierPlot pour attendre la première couleur choisie.

 

 

 

lien permanent

Tutorial 5 Memory coloré (1/2)  posté le jeudi 14 juin 2007 18:39

 

Pour ce nouveau tutorial nous allons être plus ambitieux. Je vous propose de réaliser une version colorée du célèbre memory. Vous savez ce jeu qui consiste à retourner les cartes par deux pour trouver les paires. Ici ce ne sont pas des cartes à retourner mais des plots à toucher qui se colorent. Le jeu consiste à retrouver les paires de couleurs. Comme vous pouvez le voir sur l'image j'ai réalisé une version très épurée composée de 16 plots (8 paires) placées sur un support. J'ai aussi prévu un bouton en bas pour la mise en marche. Sur l'illustration le jeu est en cours et quelques paires de couleurs ont déjà été trouvées. J'ai simplifié au maximum le fonctionnement pour ne pas trop charger le script. Un seul bouton pour démarrer ou redémarrer à n'importe quel moment. Au niveau de la construction j'ai commencé par créer les 16 plots en les alignant soigneusement, J'ai ensuite réalisé le support et le bouton. Pour les liaisons il faut respecter l'ordre suivant (pour que le script fonctionne !) : sélectionnez d'abord les plots, puis le bouton et en dernier le support. Ainsi ce dernier ce retrouve à la racine (root).

Voici le code à mettre dans le support. Ne vous laissez pas intimider par sa longueur, nous détaillerons tout ça...

 

//
//
   Memory Version 1.1
//
      par bestmomo
//

// Nombre de paires de couleurs
integer NombrePaires;
// Liste des etats des plots
list Visible = [];
// Liste melange des plots
list Melange;
// Liste des couleurs
list Couleurs = [];
// Premier plot touche
integer Premier;
// Second plot touche
integer Second;
// Constante de communication
integer GO = 10;
// Constante pour les plots
integer NON_TOUCHE = -1;

initialisations()
{

   // Initialisation du nombre de paires
   NombrePaires = 8;
   
// Initialisation des touches
   
Premier = NON_TOUCHE;
   
Second = NON_TOUCHE;
   
// Nombre total de plots
   
integer NombrePlots = NombrePaires * 2;
   
// Index du dbut des plots
   
integer Debut = llGetNumberOfPrims() - NombrePlots + 1;
   
// Creation et remplissage de la liste des plots
   
integer x;
   
list Plots = [];
   
for(x = Debut; x < NombrePlots + Debut; x++)
   
{
      
Plots += x;
      
// Plots noirs au depart
      
llSetLinkColor(x, <0,0,0>, ALL_SIDES);
   
}
   
// Creation d'une liste melange des plots
   
Melange = llListRandomize(Plots, 1);
   
// Creation de la liste des couleurs des plots par paires
   
for(x = 0; x < NombrePaires; x++)
   
{
      
vector col = <llFrand(1),llFrand(1),llFrand(1)>;
      
Couleurs += col;
      
Couleurs += col;
   
}
   
// Remplissage de 0 de la liste Visible
   
for(x = 0; x < NombrePlots; x++)
     
Visible += [0];
   // Avis de depart
   llSay(PUBLIC_CHANNEL, "Jeu en cours");
}

// Renvoie l'index du Plot
integer IndexPlot(list sender_number)
{
   return llListFindList(Melange, sender_number);
}

// Renvoie la valeur de visibilite TRUE (1) ou FALSE (0)
integer TestVisible(list sender_number)
{
   integer Index = IndexPlot(sender_number);
   return llList2Integer(Visible, Index);
}

// Affecte la visibilite au Plot
SetVisible(integer plot)
{
   integer Index = IndexPlot([plot]);
   Visible = llListReplaceList(Visible, [1], Index, Index);
}

// Met en couleur le plot
ColorePlot(integer sender_number)
{
   llSetLinkColor(sender_number, llList2Vector(Couleurs, IndexPlot([sender_number])), ALL_SIDES);
}

// Test de paire
integer TestePaire(integer a, integer b)
{
   if(llList2Vector(Couleurs, IndexPlot([a])) == llList2Vector(Couleurs, IndexPlot([b])))
      return TRUE;
   return FALSE;
}

// Test de fin
integer TestFin()
{
   if(llListFindList(Visible, [0]) == -1)
      return TRUE;
   return FALSE;
}

default
{
   // Attente action du bouton
   link_message(integer sender_number, integer number, string message, key id)
   {
      // Depart du jeu
      if(number == GO)
      {
         initialisations();
         state PremierPlot;
      }
      return;
   }
}

state PremierPlot
{
   // Reception d'un message des plots
   link_message(integer sender_number, integer number, string message, key id)
   {
      // Nouveau depart du jeu ?
      if(number == GO)
      {
         initialisations();
         return;
      }
      // Plot deja visible ?
      if(TestVisible([sender_number]))
         return;
      else
      {
         // Affectation premier plot
         Premier = sender_number;
         // Colorisation du plot
         ColorePlot(sender_number);
         // Passage au choix du second plot
         state SecondPlot;
      }
   }
}

state SecondPlot
{
   // Reception d'un message des plots
   link_message(integer sender_number, integer number, string message, key id)
   {
      // Nouveau depart du jeu ?
      if(number == GO)
      {
         initialisations();
         state PremierPlot;
      }
      // Deja un second plot touche ?
      if(Second != NON_TOUCHE)
         return;
      // Deuxieme plot choisi
      Second = sender_number;
      if(TestVisible([Second]) || Second == Premier)
         return;
      else
      {
         // Colorisation du plot
         ColorePlot(Second);
         // Test paire trouvee
         if(TestePaire(Premier, Second))
         {
            // Affecte visible pour les deux plots
            SetVisible(Premier);
            SetVisible(Second);
            // Reinitialisation
            Premier = NON_TOUCHE;
            Second = NON_TOUCHE;
            // Test de fin du jeu
            if(TestFin())
               llResetScript();
            // Retour au choix du premier plot
            state PremierPlot;
         }
         else
            // Timer 2 secondes
            llSetTimerEvent(2);
      }
   }

   // Temporisation
   timer()
   {
      // Remise a noir de la couleur des deux plots
      llSetLinkColor(Premier, <0,0,0>, ALL_SIDES);
      llSetLinkColor(Second, <0,0,0>, ALL_SIDES);
      // Reinitialisation des touches
      Premier = NON_TOUCHE;
      Second = NON_TOUCHE;
      // Reset du timer
      llSetTimerEvent(.0);
      // Retour au choix du premier plot
      state PremierPlot;
   }
}

En ce qui concerne ce code je dois avouer que j'ai un peu galéré. Je suis habitué aux langages évolués et à la programmation objet. Les contraintes de LSL me renvoient bien des années en arrière. J'ai un peu pesté contre les limitations de ce langage. J'ai aussi utilisé à forte dose le programme LSLEditor et j'ai passé plusieurs heures à chercher un bug dans mon programme alors qu'il était dans l'éditeur ! Le côté positif c'est que j'ai participé au débogage de cet excellent éditeur (en fait le seul qui existe pour LSL !) et j'ai fait la connaissance de son auteur qui est fort sympathique. Je vous proposerai d'ailleurs un de ces jours une vidéo pour illustrer son fonctionnement en mode débogage.

Voici maintenant le code pour le bouton :

 

integer GO = 10;

default
{
   touch_start(integer total_number)
   {
      llMessageLinked(LINK_ROOT, GO, "", NULL_KEY);
   }
}


 

Et celui pour les plots (il faut mettre ce script dans chaque plot !) :

 

integer TOUCHE = 1;

default
{
   touch_start(integer total_number)
   {
      llMessageLinked(LINK_ROOT, TOUCHE, "", NULL_KEY);
   }
}


 

Commencez par essayer ce code. J'ai mis pas mal de commentaires, ce qui peut déjà vous expliquer un peu son fonctionnement.

Ce script va nous permettre de voir le dernier type de LSL qui est un type un peu particulier puisque c'est le seul qui peut contenir plusieurs valeurs, de type différents en plus. C'est le type list. Comme son nom l'indique c'est une liste de valeurs empilées. C'est bien pratique pour stocker et manipuler un ensemble de valeurs liées fonctionnellement. J'en fait une utilisation intensive dans ce script. Tout ce que vous voulez savoir sur les listes est situé ici :

 

http://www.lslwiki.net/lslwiki/wakka.php?wakka=list

 

On peut faire à peu près tout ce qu'on veut, même si ce n'est pas toujours d'une simplicité biblique. En particulier il manque une fonction du genre ListIndexOf qui renverrait l'index d'une valeur. Au lieu de cela il faut se contenter de llListFindList qui donne l'index d'une liste dans une autre liste. Évidemment il suffit de mettre une seule valeur dans la liste de recherche pour obtenir l'index d'une seule valeur mais bon... J'ai sans doute pris de mauvaises habitudes avec la pléthore de fonctions intégrées aux langages que j'utilise habituellement.

Dans le prochain tutorial je commencerai à commenter le code. En attendant vous pouvez déjà exercer votre mémoire en jouant avec ce memory !

lien permanent

Tutorial 4 Porte (3/3)  posté le dimanche 10 juin 2007 23:06

 

Nous allons pour une dernière fois nous intéresser à notre porte. Cette fois le but est d'obtenir un script fonctionnel. Voici les éléments que nous devons prendre en compte :

  • la porte doit pivoter sur un axe, comme une porte classique,
  • toutes les dispositions doivent être opérationnelles (axe à droite ou à gauche),
  • les deux sens d'ouverture doivent fonctionner (pousser ou tirer),
  • la porte doit être commandée "à la voix" par l'intermédiaire du Chat avec les commande "ouvre" et "ferme",
  • seul le propriétaire doit pouvoir commander la porte,
  • un son doit accompagner le mouvement.

Nous allons analyser tous ces éléments pour déterminer les fonctions du LSL dont nous allons avoir besoin. La rotation de la porte représente notre première difficulté, déjà au niveau de la construction. En effet pour un prim l'axe de rotation est central alors que nous voulons que cet axe se situe à l'extrémité droite ou gauche de la porte. Il existe une façon simple de résoudre cette difficulté en utilisant un second prim qui sert d'axe. Il suffit ensuite de créer une liaison entre les deux. Mieux qu'un grand discours j'ai trouvé une video qui montre cette technique :

 

http://www.slbuilding.com/Open-Close_door.html

 

Maintenant que la question de la construction est réglée intéressons-nous à la rotation. Nous avons déjà rencontré un certain nombre de types avec "integer", "float", "string" et "vector". Un autre type est "rotation". Sans doute pensez-vous que la rotation est comme la position et la dimension et qu'on peut s'en sortir avec un vecteur. Après tout avec trois valeurs ont peut très bien gérer une rotation dans l'espace. Mais c'est compter sans les fantaisies des mathématiciens et leur invention des quaternions. L'explication de ces derniers dépasse largement le cadre de ce tutorial mais le type de base "rotation" de LSL représente un quaternion, autrement dit un ensemble de quatre valeurs qui décrit une rotation dans l'espace. cette approche est loin d'être intuitive mais est cohérente sur le plan mathématique. Pour ceux qui veulent tout savoir voici la page du wiki dédiée à ce type :

 

http://www.lslwiki.net/lslwiki/wakka.php?wakka=rotation

 

D'un point de vue pratique vous pouvez raisonner en coordonnées dites "eulériennes" avec la quantité de rotation par rapport aux trois axes x, y et z, dans un vecteur. Mais les problèmes arrivent lorsque vous voulez combiner des rotations, dans ce cas il faut passer aux quaternions. Heureusement il existe dans LSL des fonctions qui permettent un aller-retour entre ces représentations avec llEuler2Rot et llRot2Euler. Un autre élément à prendre en compte est que les fonctions qui concernent les rotations attendent des valeurs en radian alors que vous êtes habitué à entrer des valeurs en degrés lors de la construction. Il suffit de se rappeler 360° est égal à 2PI radians, 180° égal à PI radians... Pour notre porte un angle de 90° correspond donc à PI/2 radians. Pour ceux qui sont mal à l'aise avec les radian il existe des constantes pour la conversion : DEG_TO_RAD et RAD_TO_DEG. Mais elles ne présentent d'intérêt que pour des valeurs exotiques, dans notre cas nous pouvons raisonner directement en radian. Il existe une fonction qui nous donne la rotation actuelle d'un objet : llGetRot qui renvoie un type "rotation", donc un quaternion. Et une fonction qui applique une nouvelle rotation : llSetRot qui réclame aussi un quaternion.

J'ai dit que seul le propriétaire devait pouvoir manoeuvre la porte. Il faut savoir, même si cela doit blesser notre orgueil virtuel, que sur second life nous ne sommes que des numéros. Il existe une fonction qui renvoie la clef d'identification du propriétaire d'un objet : llGetOwner. Ce qui nous donne l'occasion de rencontrer un nouveau type : 'key", qui est un type "string" spécialisé, chaque chose dans second life possède une clef unique qui l'identifie. Nous avons ainsi déjà vu 6 des 7 types de LSL !

En ce qui concerne les sons un certain nombre de fonctions permettent de faire un peu ce que l'on veut. Vous trouvez tout ça dans le wiki :

 

http://www.lslwiki.net/lslwiki/wakka.php?wakka=sound

 

Pour notre porte nous pouvons choisir indifféremment llPlaySound ou llTriggerSound. La différence entre ces deux fonctions ne saute pas aux yeux spontanément. Elle réside simplement dans le fait que dans le cas de la première fonction le son suit l'objet si celui-ci est en mouvement alors que pour la seconde il continue d'être émis de la même localisation même si l'objet a bougé entre temps. Notre porte n'ayant pas pour mission d'aller bien loin cette subtilité ne nous concerne pas. Par contre il vous faut mettre un fichier son dans votre objet pour que ça fonctionne et faire correspondre le nom écrit dans le code !

Pour les autres éléments je les décrirai avec les commentaires sur le code :

 

// Position de l'axe
integer AxeADroite = TRUE;
// Porte poussée ou tirée
integer Pousser = TRUE;
// Porte ouverte ou fermée
integer PorteOuverte = FALSE;

bougerPorte(string message)
{
   float angle;
   // Détermination du sens de la rotation pour la fermeture
   if((AxeADroite && Pousser) || (!AxeADroite && !Pousser))
   {
      angle = PI_BY_TWO;
   }
   else
   {
      angle = -PI_BY_TWO;
   }
   // Pour l'ouverture on inverse
   if(message == "ouvre")
   {
      angle = -angle;
      PorteOuverte = TRUE;
   }
   else
   {
      PorteOuverte = FALSE;
   }
   // Rotation
   llTriggerSound("grincement", .5);
   rotation rot = llEuler2Rot(<0,0,angle>);
   llSetRot(llGetLocalRot()* rot);
}

default
{
   state_entry()
   {
      // Récupération de la clef du propriétaire
      key proprio = llGetOwner();
      // Mise en place de l'écoute
      llListen(PUBLIC_CHANNEL, "", proprio, "ouvre");
      llListen(PUBLIC_CHANNEL, "", proprio, "ferme");
   }

   listen(integer channel, string name, key id, string message)
   {
      // Test pour voir s'il faut bouger la porte
      if((message == "ouvre" && !PorteOuverte) || (message == "ferme" && PorteOuverte))
      {
         bougerPorte(message);
      }
   }
}

 

Ce code que je vous propose là est une possibilité parmi plein de possibles. Je l'ai rédigé pour faire apparaître un certain nombre d'éléments nouveaux. Il n'est pas forcément optimisé, considérez le surtout sous son aspect didactique. Le code débute par la déclaration de trois variables globales pour mémoriser les données structurelles de la porte (axe à droite ou à gauche, action pousser ou tirer) et la position de la porte 'ouverte ou fermée). Nous voyons pour la première fois apparaître les constantes TRUE et FALSE. Dans les langages évolués existe un type "boolean" qui peut adopter deux valeurs : vrai (TRUE) et faux (FALSE). Ce type n'existe pas dans LSL. Nous sommes obligés d'utiliser un type "integer" et ne retenir que deux valeurs : 0 (qui correspond à FALSE) et 1 (qui correspond à TRUE). Nous pourrions très bien dans le code écrire :

integer AxeADroite = 1;
integer Pousser = 1;
integer PorteOuverte = 0;

Mais il est plus explicite d'adopter l'écriture avec TRUE et FALSE pour une question de lisibilité du code. Je rappelle que ces variables sont globales et donc accessible de tout le code. Je passe directement à l'explication du bloc default. L'événement state_entry permet comme d'habitude de faire les initialisations. Ici on récupère la "key" du propriétaire de la porte. Ensuite on met en place l'écoute sur le canal public, pour le proprio, avec les deux commandes "ouvre" et ferme". Ensuite nous avons l'événement listen que vous connaissez déjà. Par contre le test pour la nécessité de manoeuvrer la porte comporte pas mal d'éléments nouveaux. D'abord le if. Comme vous devez vous en douter il introduit une condition, c'est un élément fondamental de la programmation. A ce niveau se situe une décision du genre si on a ça alors on fait ça, sinon on fait autre chose. Ce qui est dans la parenthèse qui suit le if est évalué pour savoir si sa valeur est vraie ou fausse. Nous faisons connaissance ici avec les opérateur booléens. Voici ce qu'en dit le wiki :

 

http://www.lslwiki.net/lslwiki/wakka.php?wakka=boolean

 

Vous pouvez voir que le signe && correspond à un "et" logique alors que le signe "||" correspond quant à lui à un "ou" logique. Enfin le signe "!" considère l'inverse de la valeur. Tout ce qu'il faut pour évaluer des conditions ! Dans notre cas nous devons savoir si la porte est ouverte ou fermée et si notre intention est de l'ouvrir ou de la fermer. Il est évident que nous n'allons pas ouvrir ou fermer deux fois la porte ! C'est l'objet de de test. S'il est positif alors le code nous renvoie à la méthode bougerPorte dont je n'ai pas encore parlé. J'aurais bien sûr pu mettre tout le code ici sans cet appel de méthode mais c'est une saine attitude de séparer le code en unités fonctionnelles. En voici un premier pas. La méthode bougerPorte est déclarée avant le bloc default. Elle comporte un paramètre de type "string" qui permet la transmission du message qui peut être "ouvre" ou "ferme". Dans cette méthode nous devons considérer les éléments structurels de la porte et son sens d'ouverture. Le résultat est un angle de rotation qui est soit PI/2, soit -PI/2. LSL propose une constante pour cette valeur : PI_BY_TWO. Je vous laisse analyser ce code ! Il est on ne peut plus logique. Lorsque l'angle a été déterminé, et après avoir au passage déclenché le son, nous créons un quaternion en partant d'un vecteur qui comporte l'angle sur l'axe z. Pour obtenir la rotation correcte de la porte il suffit de composer la rotation actuelle qui est donnée pas llGetRot avec la rotation déterminée. Pour obtenir cela il suffit d'une simple multiplication. Finalement c'est bien pratique les quaternions !

Nous en avons enfin fini avec cette porte. Je vous laisse faire des essais en bougeant la porte dans tous les sens ! Dans le prochain tutorial c'est promis il n'y aura plus de porte !

lien permanent

Tutorial 3 Porte (2/3)  posté le samedi 09 juin 2007 20:43

 

Nous allons essayer d'améliorer un peu le fonctionnement de notre porte. Tout d'abord je ne trouve pas le fait de déclencher l'ouverture en la touchant très élégant. Il serait plus sympathique de disposer d'un bouton. Ensuite ce serait bien que la fermeture de la porte s'effectue automatiquement au bout d'un certain délai. Ces deux modifications vont un peu chambouler notre code et nous obliger à considérer de nouveaux éléments. D'abord le fait de disposer d'un bouton qui est un objet séparé de la porte nous impose d'avoir deux scripts : un pour le bouton et un pour la porte, et de les faire communiquer. Commençons par régler ce point. Il existe une fonction nommée llListen qui permet d'indiquer à un objet de se mettre à l'écoute d'un canal du Chat. Voici la page du wiki qui correspond à cette fonction :

 

http://www.lslwiki.net/lslwiki/wakka.php?wakka=llListen

 

Passons un peu en revue ses paramètres. Le premier est le numéro du canal du Chat. Jusqu'ici nous avons utilisé le canal général numéroté 0. Il est possible d'utiliser bien d'autres canaux puisque ce paramètre est un integer. Les deux autres paramètres servent de filtre en désignant le nom de l'objet à écouter (deuxième paramètre) ou l'avatar à écouter (troisième paramètre). Dans notre cas nous allons appeler notre bouton tout simplement "bouton" et nous le désignerons ainsi dans cette fonction. Le quatrième paramètre enfin est la phrase attendue. L'utilisation de la fonction llListen position le script dans un état particulier nommé listen, c'est-à-dire à l'écoute du canal désigné, avec les filtres prévus et dans l'attente de la bonne phrase.

Voyons maintenant un peu le problème de la temporisation. C'est une autre fonction qui va nous tirer d'affaire : llSetTimerEvent :

 

http://www.lslwiki.net/lslwiki/wakka.php?wakka=llSetTimerEvent

 

Une fonction toute simple qui ne comporte qu'un paramètre représentant le nombre de secondes à attendre. Chaque fois que le délai est atteint un événement "timer" est déclenché. Il est temps maintenant de voir le code de la porte :

 

float largeur; // largeur du déplacement
default
{
   state_entry()
   {
      vector dimensions = llGetScale(); // dimensions de la porte
      largeur = dimensions.y - (dimensions.y / 10); // détermination de la largeur du déplacement
      llListen(10, "bouton", NULL_KEY, "ouvrir porte");
   }

   listen(integer channel, string name, key id, string message) // Ouverture de la porte
   {
      vector position = llGetPos(); // position de départ
      llSetPos(position + <0, -largeur, 0>); // translation
      llSetTimerEvent(10); // On laisse ouvert 10 secondes
   }

   timer() // fermeture de la porte
   {
      vector position = llGetPos(); // position de départ
      llSetPos(position + <0, largeur, 0>); // translation
      llResetScript();
   }
}


 

Un certains nombre d'éléments de ce code vous sont déjà familiers, en particulier la logique de mouvement de la porte. Je vais donc me concentrer sur les éléments nouveau. Au niveau des initialisation dans le "state_entry" la ligne important est :

 

 llListen(10, "bouton", NULL_KEY, "ouvrir porte");

 

Je vous ai présenté cette fonction ci-dessus. Nous utilisons le canal 10, ce choix est parfaitement arbitraire et toute autre valeur pourrait convenir. Nous désignons le nom de l'objet qui sert de bouton, il ne faudra donc pas oublier de l'appeler ainsi si nous ne voulons pas attendre indéfiniment l'ouverture. La valeur NULL_KEY qui suit est équivalente à une chaîne de caractères vide. Enfin le message attendu est "ouvrir la porte". Dans le code suit un événement particulier nommé "listen" qui se déclenche dès que la phrase est entendu sur le bon canal prononcée le bon objet. Les deux premières lignes du code ne sont pas nouvelles pour vous, par contre la troisième mérite quelques commentaires. Je vous ai parlé ci-dessus le la fonction llSetTimerEvent. Ici nous l'utilisons après avoir ouvert la porte pour donner le départ du délai au bout duquel la porte doit se refermer. J'ai mis 10 secondes, mais vous pouvez évidemment mettre la valeur que vous voulez. Au bout du délai légèrement "timer" est déclenché. Nous fermons alors la porte avec le même code que le tutorial précédent. Par contre la dernière ligne comporte une nouvelle fonction dont le nom doit vous indiquer aisément la fonction. La fonction llResetScript remet le script dans son état initial, en quelque sorte la boucle est bouclée et on revient au "state_entry"...

Il ne nous manque plus que le code du bouton. Là ça va être on ne peut plus simple sans nouvelle fonction :

 

default
{
   touch_start(integer total_number)
   {
      llSay(10, "ouvrir porte");
   }
}

 

Un code qui se passe presque de commentaire puisque on se contente de dire ce qu'il faut sur le bon canal lorsque le bouton est touché (évenement "touch_start"). Vous n'avez plus qu'à passer à la création de la porte et du bouton pour essayer ces scripts...

lien permanent

Tutorial 2 Porte (1/3)  posté le vendredi 08 juin 2007 23:57

 

Nous allons maintenant nous attaquer à un projet légèrement plus ambitieux. Nous allons créer une porte à galindage pour éviter de nous confronter immédiatement aux rotations. Une porte à galindage se contente d'une translation, ce qui sera plus simple à gérer. Faisons un peu le point des éléments en jeu pour notre porte :

 

Etat porte fermée porte ouverte  
Événement porte fermée porte ouverte porte touchée
Fonction ouvrir la porte fermer la porte  

 

Je vais encore un peu insister sur la différence entre état, événement et fonction. Un état ici représente la position de la porte, celle-ci est ouverte ou fermé, on n'admet pas les états intermédiaires pour simplifier. Un événement nous indique un changement d'état, autrement dit la porte s'est ouverte ou s'est fermée. Un autre événement sert de déclencheur pour la commande de la porte, c'est le fait de toucher la porte. La fonction permet de commander l'ouverture ou la fermeture de la porte. Lorsqu'un avatar va toucher la porte "événement "porte touchée", selon son état "porte ouverte" ou "porte fermée", on va commander la fonction correspondante "fermer la porte" ou ouvrir la porte". Nous pourrions utiliser les événements "porte ouverte" et "porte fermée" pour afficher l'état au niveau du Chat, mais nous allons nous en passer parce qu'on voit très bien si la porte est ouverte ou fermée.

Maintenant que nous avons fait le point sur les modalités de quelles fonctions spécifiques avons-nous besoin pour commander la porte ? La fonction qui gère la position d'un objet est llSetPos. Vous trouvez toutes les informations sur cette fonction dans le wiki :

 

http://www.lslwiki.net/lslwiki/wakka.php?wakka=llSetPos

 

Vous constatez que cette fonction ne comporte qu'un paramètre de type vecteur. Peut-être le bon moment de s'attarder sur les types de données. Le wiki nous indique tous les types de LSL à cette page :

 

http://www.lslwiki.net/lslwiki/wakka.php?wakka=types

 

Il y a 7 types. Nous en avons déjà rencontré 2 dans le premier tutorial: le type "integer" qui est un entier compris entre -2,147,483,648 et 2,147,483,647, et le type "string" qui est une séquence de caractères que l'on place entre guillemets. Il nous faut à présent nous intéresser au type "float" qui représente une valeur décimale, autrement dit un nombre "à virgule", contrairement au type "integer". Un type "vector" est un ensemble de 3 "float" représenté sous la forme <x,y,z>. Il ne faut pas être trop étonné de trouver des vecteur dans un monde en trois dimensions. La position dans l'espace d'un objet est définie par 3 coordonnées rassemblées dans un vecteur. Vous trouvez plus de précision sur le type "vector" dans le wiki :

 

http://www.lslwiki.net/lslwiki/wakka.php?wakka=vector

 

Je fais souvent référence au wiki pour vous habituer à aller y chercher des informations. Un vecteur permet de situer la position d'un objet dans l'espace avec les coordonnées x, y et z. La fonction llSetPos permet de modifier cette position en changeant la valeur de l'une ou plusieurs de ces trois valeurs. Dans le cas de notre porte, en admettant qu'on utilise un simple prim, il nous suffira de modifier la valeur de y pour la faire glisser sur le côté. Mais la fonction llSetPos se réfère à des coordonnées régionales, comme quand vous construisez. Pour faire glisser notre porte nous avons besoin dans un premier temps de connaître ses coordonnées de départ. celles-ci nous sont fournies par la fonction llGetPos que vous trouvez ici :

 

http://www.lslwiki.net/lslwiki/wakka.php?wakka=llGetPos

 

C'est une fonction qui renvoie un vecteur contenant les coordonnées régionales de l'objet. Exactement ce dont nous avons besoin. Maintenant que nous savons obtenir la position initiale et que nous savons faire bouger notre porte que nous manque-t-il ? peut être de connaître la valeur du déplacement. l'idéal serait un peu moins que la largeur de la porte. Comment pouvons-nous la connaître ? Tout simplement avec la fonction llGetScale qui est l'équivalent de llGetPos mais pour la taille. Cette fonction renvoie un vecteur qui donne les dimensions de l'objet. Pour notre porte la largeur nous est donnée par la valeur de y. Il subsiste juste un problème pour connaître la position initiale de la porte. On va partir du principe qu'elle est fermée la première fois qu'on la touche.

Tout ceci déterminé je vous propose le code suivant que je vais vous commenter :

 

float largeur; // largeur du déplacement
default
{
   state_entry()
   {
      vector dimensions = llGetScale(); // dimensions de la porte
      largeur = dimensions.y - (dimensions.y / 10); // détermination de la largeur du déplacement
      llSay(0, "porte en action"); // information
      state ferme; // passe à l'état initial fermé
   }
}

state ouvert // état porte ouverte
{
   touch_start(integer total_number)
   {
      vector position = llGetPos(); // position de départ
      llSetPos(position + <0, largeur, 0>); // translation
      state ferme; // passage à l'état fermé
   }
}

state ferme // état porte fermée
{
   touch_start(integer total_number)
   {
      vector position = llGetPos(); // position de départ
      llSetPos(position + <0, -largeur, 0>); // translation
      state ouvert; // passage à l'état ouvert
   }
}

 

La première instruction du programme sert à déclarer une variable nommée largeur de type "float". Qu'est-ce qu'une variable ? Tout simplement une boîte qui contient une valeur d'un certain type. Comme j'ai placé cette variable en tête du code, à l'extérieur de tous les états cette variable est globale, c'est-à-dire qu'elle est accessible dans tout le code. Nous avons ensuite trois état, le "default" que nous avons déjà rencontré qui correspond à l'état initial, celui qui est sollicité au démarrage du script, et deux états qui correspondent à la porte ouverte et à la porte fermée. Dans les deux états "ouvert" et "ferme" j'utilise l'événement "touch_start" qui se déclenche lorsqu'un avatar touche la porte. Quant à l'état "default" j'ai prévu un state_entry() qui se déclenche systématiquement à l'entrée dans l'état, donc au démarrage du script. Nous avons là un bloc de 4 instructions. la première instruction :

vector dimensions = llGetScale();

place dans le vecteur "dimensions" les dimensions de la porte. La deuxième instruction :

largeur = dimensions.y - (dimensions.y / 10);

détermine la largeur du déplacement en prenant la largeur de la porte qui nous est donnée par la valeur y du vecteur et en lui enlevant de manière arbitraire le dixième de sa valeur. Je n'insiste pas sur le troisième instruction que vous connaissez déjà. La quatrième instruction permet de passer à l'état "ferme". Le code est alors arrêté. La fois suivant que la porte est touchée, comme nous sommes dans l'état "ferme" c'est l'événement "touch-start" de cet état qui est concerné. La première instruction :

vector position = llGetPos();

nous donne la position actuelle de la porte. la deuxième instruction :

llSetPos(position + <0, largeur, 0>);

effectue la translation. remarquez qu'on ajoute au vecteur de la position de départ un vecteur dont les valeur x et z sont à 0 et la valeur de y à celle qui correspond au déplacement. La troisième instruction sert à passer à l'état "ouvert". Le code de cet état est équivalent au précédent avec comme seule différence le signe négatif pour le déplacement.

Il ne vous reste plus qu'à créer une jolie porte dans SL et d'ajouter ce script...

 

lien permanent



 

fermer la barre

Vous devez être connecté pour écrire un message à lsl

Vous devez être connecté pour ajouter lsl à vos amis

 
Créer un blog