Recherche

Recherche personnalisée

dimanche 29 mars 2009

Construction hors-ligne

Il est possible de créer des objets ou prims en étant déconnecté de SL grâce à un outil créé par Jeffrey Gomez, Offline Builder. Offline Builder est en fait un jeu de scripts qui s’ajoute à Blender, un logiciel libre disponible sur blender.org.

Bien que Blender soit un logiciel de création 3D très avancé et compliqué, Offline Builder est très simple, et reproduit les fonctions disponibles sur l’éditeur de Second Life. Ce tuto vous explique comment installer Blender et Offline Builder, et obtenir l’outil d’importation vers Second Life.

Téléchargements et installations

Premièrement, téléchargez Blender sur http://www.blender.org/download/get-blender/, et installez-le en suivant les indications données sur le site (prendre la version Blender 2.45 Installer).

Ensuite, il vous faut Offline Builder. Il est disponible au téléchargement ici : http://sourceforge.net/project/showfiles.php?group_id=149111. Sur la page en question, teléchargez « Prim.Blender » vers le dossier de votre choix. Extrayez le contenu de l’archive. Offline Builder est prêt !

Script Offline BuilderPour lancer Offline Builder, il vous suffit de double cliquer sur le ficher Prim.Blender.blend du dossier extrait de l’archive. Ce double clic lance Blender, et affiche un panneau sur la gauche.

Offline BuilderFaites un clic droit, puis cliquez sur Execute Script. Le panneau se « transforme », et devient un panel qui ressemble étrangement à celui disponible dans Second Life (vous le verrez rapidement : toutes les fonctions ne sont pas disponibles).

L'outil se limite à la création d'objets, de transformations basiques, et déplacements et rotations. Pas de couleur, de flexibilité, de texture. Offline Builder reste toutefois un outil très pratique pour préparer les bases d'une création, d'un objet complexe, sans devoir se connecter à Second Life (et donc au réseau Internet).
Créer et importer

Exemple sur BuilderVoici un exemple : j’ai tout simplement ajouté quelques prims sur la surface de travail. J’ai légèrement modifié le cube, et fait faire quelques rotations au cylindre. A votre tour, créez quelques objets pour essayer.

Builder SavePuis cliquez sur le bouton « Save » en bas à gauche de l’écran. Choissez un nom de fichier (conservez l’extension .blend), et le dossier où vous désirez sauvegarder le fichier, puis cliquez sur « Save ».

Maintenant, importons cela dans Second Life.

Connectez-vous à Second Life, et rendez-vous sur Brithys : secondlife://Brithys/111/234/54, ou sur Albata : secondlife:/Albata/21/37/45.

Cliquez sur le cube pour obtenir les instructions, et la license. Cliquez ensuite sur le cube avec le bouton droite, et cliquez sur Pay (c’est gratuit !).

Un objet appelé « Offline Builder Importer » (ou similaire) est ajouté à votre inventaire. Il suffit de le faire glisser au sol (d’un terrain où vous pouvez le faire, et où les scripts sont autorisés, et l’outil d’import est prêt.

Maintenant, à l’aide de votre éditeur de texte (comme Notepad sur Windows), ouvrez le fichier .blend sauvegardé auparavant sur Blender. Le fichier devrait commencer par "". Copiez l’ensemble du texte sans exception.

Nouvelle noteDe nouveau sur Second Life, ouvrez votre inventaire, et créez une nouvelle note. Collez-y le code copié du fichier « .blend », et enregistrez. Puis, faites glisser la note en question de votre inventaire sur le cube représentant l’outil d’import.

RésultatAttendez quelques instants : vos prims apparaîtront peu à peu. Voici ci-contee le résultat de mon exemple sur Blender.

Ci-desous, quelques raccourcis claviers fournis par Jeffrey Gomez.
Commandes clavier

* G -> "Grab" -- déplacer
* R -> "Rotate" -- pivoter
* S -> "Scale" -- réduire ou agrandir

Appuyez sur les touches X,Y, ou Z une fois après avoir choisi une des trois fonctions précédentes de façon à déplacer, faire pivoter ou changer les dimensions d'un objet en suivant une direction précise.

Par exemple, cliquez sur le bouton de la souris pour sélectionner une prim. Cliquez sur S pour agrandir l'objet. Cliquez sur X pour suive l'axe X, et déplacer la souris pour agrandir. Enfin, cliquez sur le bouton gauche de la souris lorsque l'objet est aux dimensions requises.

Commandes de sélection :

* Clic gauche, pour placer le curseur et créer une nouvelle prim
* Clic droite, pour sélectionner un objet
* MAJ + clic droite, pour séctionner plusieurs objets

Pour effacer une prim, séctionnez-la, puis utilisez la touche X (et confirmez).

Commandes Camera :

* Roulette de la souris : Zoom (si vous n'avez pas de roulette, utilisez les touches 2 et 8)
* Maj + roulette : Déplacer la camera (haut-bas)
* Ctrl + roulette : Déplacer la camera (gauche-droite)
* CTRL + Alt + roulette : tourner autour des objets
* Bouton 3 de la souris (ou clic sur la roulette) + déplacement de la souris : mouvement libre

Tuto Video menu apparence

Tutoriel créer une animation Second Life avec QAnimator

e billet à pour but d'expliquer ce qu'est une animation pour Avatar, comment en créer une, et surtout comment l'importer dans Second Life.
Pour plus de détail, on se reportera au lien vers le forum second life, que vous trouverez à la fin du billet.##

Qavimator et les fichiers .bvh

De nombreux logiciels existent pour créer une animation, en dehors de second life. Celui que nous avons retenu est Qavimator. Voir notre billet à ce sujet :Créer des animations pour votre avatar avec AVIMATOR! .
Ces logiciels d'animation générent des fichiers à l'extension .bvh. Ce fichier va contenir les différentes images (frames) constituant l'animation, la longueur et les angles séparant chaque articulations de votre Avatar.
Ces fichier .bvh vont pouvoir être importés dans second life afin de pouvoir créer un objet « animation » qui se retrouvera dans votre inventaire, dans le dossier « animation ».

Création de l'animation

Le principe est simple :
A l'import du fichier dans second life, Second Life va définir ainsi votre animation : les parties du corps de l'avatar faisant parti de l'animation seront les parties du corps sur lesquels existe une différences entre la première frame de l'animation et la deuxième frame de l'animation.
Ainsi cette animation une fois importée ne polluera pas les autres animations appliquées constemment à votre avatar (voler, marcher, sauter, courir, applaudir...).
Dans l'exemple qui suit, on verra que l'animation « porter une caméra » ne se servira que du bras droit, les autres parties du corps étant disponible pour d'autres animations.

Exemple « porter une caméra »
Dans cette exemple, l'animation comportera en tout et pour tout 2 Frame : la frame 0 (non lue), la frame 1 : Avatar avec position en T (Tous les axes de rotations de toutes les parties du corps sont à 0), et la frame 2 : posture du portage de la camera.
Dans Qavimator il sera important de positionner le nombre total de frame à 2, sinon la frame 2 sera recopiée autant de fois qu'il y a de frames définies.


Dans Second Life : file / upload Animation :
Définir les paramètres ainsi :

import1

Priority 2 (Ne pas enlever l'animation losque l'on tchat – priorité 3 l'enlève)
Cocher loop (animation en boucle pour que le bras reste en l'air)
in 100 %, out 100% - Si quelqu'un sait à quoi sa sert ==> commentaire !!
Hand pose : « fist rigth » pour que le point droit soit fermé
La durée de l'animation (la durée de la levé du bras droit) : 0,7 sec. pour lever le bras, et 0,7 sec pour le descendre

voilà il ne reste plus qu'a uploader l'animation, et dans le même temps se délester de 10 L$ !.

Il restera encore pour la suite à intégrer l'animation dans un objet, accompagné d'un script pour que l'avatar prenne la pose automatiquement.
Cette procédure fera l'objet d'un futur billet.

Si vous avez des critiques et des améliorations à apporter à ce tutoriel, n'hésiter pas, les commentaires sont là pour ça !.

Bonne création.

Tutoriel Second Life: raccourcis claviers, trucs et astuces

Quelques trucs et astuces donc pour nous faciliter la vie!

J'ai réuni ici des petites notes quotidiennes et des tuyaux que les amis me donnent in game où que je découvre au fil du temps, je fais tourner!!##

Le menu caché de SL

Ctrl Alt D, vous permet d'accéder au menu « caché de l'interface » soit : Client, server ou vous trouverez de nombreuses options indipensables.

CAMERA

ALT permet de prendre le contrôle de la caméras

ALT + CTRL de redresser la caméra
Maintenant les touches enfoncés tous en glissant votre souris.(ne pas selectionner d'objet en meme temps)

Si vous désirer élargir votre champs de vision, activer le menu Client serveur, comme décrit ci-dessus (CTRL ALT D)
Dans client cocher : Disable Camera Contraints (Merci à Doleragon)

CTRL SHIFT ALT 9 (d'en haut, pas du clavier numérique) permet de voir ce qui se passe de l'autre coté des murs , refaire la commande por reprendre la vue normale

CTRL ALT T, permet de voir tous les objets invisibles,vous les verrez apparaître en rouge. Il suffit de recommencer la commande pour remettre la vision normale.
TCHAT

Pour faire une conférence de tchat:

Sélectionner dans la liste de vos amis, en maintenant la touche ctrl enfoncée ceux que vous voulez inviter en tchat, et ensuite, demarrer en cliquant sur IM (Merci Zelie!)

Vous êtes occupé et non disponible pour répondre aux IM de vos amis ?
Vous pouvez activer le mode buzy
Dans l'onglet Monde (World) Set Busy.
Vous pouvez personnaliser le message qui sera délivrée quand elle vous contactera, en l'écrivant dans le menu "Edition , preference, IM,
TEXTURE

Menu client Character Rebake texture pour recharger les textures (merci Sun^^)

Pour sortit une texture du jeu quand elle est en mode copy modify
Fichier, enregitrer la texture sous (save the texture) à l'emplacement de votre choix

Et si vous êtes pressés:

CTRL R, toujours courir!!

Tutorial 15 toboggan 3/3

Voyons maintenant le code de la ball :

vector position; // Position de depart
string animation; // Nom de l'animation
key AgentKey; // Clef de l'avatar
integer ecoute; // ecoute du chat
integer canal; // canal d'ecoute
integer canal_de_ball; // Canal de reponse
rotation rot; // rotation initiale

initialisations () {

// Animation
animation = "sit knees up2";
// Changement du texte du menu
llSetSitText("Glisser");
// Positionnement de l'animation
llSitTarget(<0.0,0.0,.5>, ZERO_ROTATION);
// Canaux
canal = 37;
canal_de_ball = 38;
// Memorisation de la position
position = llGetPos();
rot = llGetRot();
}

depart ( ) {

// Boule visible
llSetAlpha(1.0, ALL_SIDES);
// Passage en non physique
llSetStatus(STATUS_PHYSICS, FALSE);
// Retour de la boule au depart
llSetPos (position );
// Rotation
llSetRot(rot);
}

default {

on_rez(integer start_param) {
// Initialisations
initialisations();
// Memorisation du canal d'ecoute
canal = start_param;
// Changement d'etat
state attente;
}
}

state attente {

// Entree dans l'etat
state_entry() {
// Activation de l'ecoute du toboggan
ecoute = llListen(canal,"",NULL_KEY,"");
// Mise en route du timer
llSetTimerEvent(120);
// Depart
depart();
}

// Atteinte du bas du toboggan determine
// avec la collision avec le sol
land_collision (vector position) {
// Avatar debout
llUnSit(AgentKey);
}

// Detection d'un changement
changed(integer change) {
// Test de changement dans les liaisons
if (change & CHANGED_LINK) {
// Determination de la clef de l'avatar
AgentKey = llAvatarOnSitTarget ();
// Un avatar est present ?
if (AgentKey) {
// Permission d'animation
llRequestPermissions (AgentKey, PERMISSION_TRIGGER_ANIMATION);
// Passage en physique et interdiction des rotations
llSetStatus (STATUS_PHYSICS , TRUE);
llSetStatus(STATUS_ROTATE_Y | STATUS_ROTATE_X | STATUS_ROTATE_Z, FALSE);
// Impulsion
llApplyImpulse(llGetMass()*<3.0,0.0,0.0>, TRUE);
// Boule invisible
llSetAlpha(0, ALL_SIDES);
}
else {
// Determination de l'animation en cours
string anim = llGetAnimation(AgentKey);
// Arret eventuel de l'animation
if(anim == "animation")
llStopAnimation(anim);
// Depart
depart();
}
}
}

// Permission accordee
run_time_permissions(integer permissions) {
if(permissions == PERMISSION_TRIGGER_ANIMATION) {
// Arret de l'animation par defaut
llStopAnimation("sit");
// Animation
llStartAnimation(animation);
}
}

// Ecoute
listen(integer channel, string name, key id, string message) {
if (message == "Meurs")
llDie();
else if (message == "Vis")
llSetTimerEvent(120);
else if (message == "Positions") {
// Memorisation de la position
position = llGetPos();
rot = llGetRot();
// Envoi des positions au toboggan
llSay (canal_de_ball, (string)position + "|" + (string)rot);
}
}

// Delai ecoule
timer ( ) {
// Suicide
llDie();
}
}

Une grande partie de ce code a déjà été décrit dans le tutorial précédent. Je vais donc me contenter de commenter les nouveautés.

On va s’intéresser en particulier l’événement on_rez qui se déclenche lors de la création de la ball. On procède alors aux initialisations, en particulier la mémorisation de la position et de la rotation. On récupère également le canal d’écoute qui se retrouve dans le paramètre de l’événement. Ensuite on passe à l’état attente.

A l’entrée dans cet état on active l’écoute du toboggan. On déclenche un timer réglé à 120 secondes. S’il ne se produit aucun changement au bout de ce délai l’événement timer se produit et la fonction llDie supprime la ball. Ensuite on appelle la méthode depart qui est identique à celle du précédent tutorial.

Comme nouveauté nous trouvons l’écoute du toboggan. Le message « Meurs » entraine l’exécution de la fonction llDie pour suicider la ball. Le message « Vis » réinitialise le timer pour une nouvelle durée de 120 secondes. Enfin le message « Positions » provoque l’envoi des coordonnées au toboggan dans le format qu’il s’attend à recevoir.

Mode opératoire : commencez par créer le toboggan et son script. Ensuite créez la ball, en la nommant bien « ball », et son script et mettez là dans votre inventaire. Faites enfin glisser la ball de votre inventaire dans le contenu du toboggan. A la première touche du toboggan activez l’option « Glissade » pour créer la ball. Ajustez ensuite celle-ci correctement. Touchez alors le toboggan et choisissez l’option « Position » pour mémoriser la position de la ball. Votre toboggan est prêt à l’emploi J.

Tutorial 14 Toboggan 2/3

Dans le précédent tutorial je vous ai proposé un toboggan et je vous avais promis une version plus élaborée qui pallie les défauts les plus évidents. En particulier le fait que la ball ne soit pas solidaire du toboggan peut poser quelques problèmes lors des déplacements. Sans parler de la perte éventuelle de la ball.

Je vous propose donc une version plus élégante dans laquelle la ball est contenue dans le toboggan. Un clic sur celui-ci fait apparaître un menu avec trois options :

Glissade


Stop


Position

Cette option permet de faire apparaître la ball


Cette option fait disparaître la ball


Cette option permet de mémoriser la position et la rotation de la ball

D’autre part la ball ne peut survivre sans le toboggan. Celui-ci envoie une information cyclique, sans cette information la ball se suicide.

On se retrouve avec deux scripts : un dans le toboggan et un dans la ball.

Le toboggan doit savoir effectuer les opérations suivantes :

Ø créer une ball en la positionnant correctement,

Ø supprimer la ball,

Ø demander à la ball sa position et sa rotation et mémoriser ces informations,

Ø envoyer une information cyclique à la ball pour lui demander de survivre.

Voici le code correspondant à ces objectifs pour le toboggan :

integer canal_ball ; // Canal pour parler a la ball
integer canal_de_ball ; // Canal pour parler au toboggan
integer canal_menu ; // Canal pour le menu
integer ecoute ; // Ecoute
list menu; // Options du menu
vector offsetPos ; // Offset de positionnement
rotation offsetRot ; // Offset de rotation

// Initialisation du script
initialisations () {

canal_ball = 37;
canal_de_ball = 38;
canal_menu = 47;
menu =["Glissade" , "Stop" , "Position" ];
offsetPos = <0.0,0.0,1.0>;
}

// Initialisation de la ball

rez_ball( ) {

// Suppression eventuelle
llSay ( canal_ball , "Meurs" );
// Calcul du deplacement et de la rotation
vector tobPos = llGetPos ();
rotation tobRot = llGetRot ();
vector DestPos = (offsetPos * tobRot ) + tobPos ;
rotation DestRot = offsetRot * tobRot ;
// Creation de la ball
llRezObject ( "ball", DestPos , ZERO_VECTOR , DestRot , canal_ball );
// Activation du timer
llSetTimerEvent ( 60.0);
}

// Etat par defaut
default {

// Entree de l'etat
state_entry( ) {
initialisations ();
}

// Reinitialisation
on_rez( integer start_param ) {
initialisations ();
}

// Touche
touch_start (integer total_number ) {
// Suppression de l'ecoute eventuelle
llListenRemove ( ecoute );
// Mise en place de l'ecoute
ecoute = llListen (canal_menu , "", NULL_KEY , "");
// Affichage du dialogue
llDialog ( llDetectedKey (0), "Choisissez une option", menu, canal_menu );
}

// Stimulation reguliere de la ball
timer ( ) {
// Message pour la ball
llSay ( canal_ball , "Vis" );
}

// Ecoute
listen (integer channel, string name, key id, string message ) {
// Affichage de l'option choisie
llWhisper ( 0, name + " a choisi l'option '" + message + "'." );
// Suppression de l'ecoute
llListenRemove ( ecoute );
// Selection selon l'option
if (message == "Glissade" ) {
// Affichage d'initialisation
llWhisper ( 0, "Initialisation du toboggan");
// Initialisation de la ball
rez_ball( );
}
else if (message == "Stop" ) {
// Suppression de la ball
llSay ( canal_ball , "Meurs" );
// Arret du timer
llSetTimerEvent ( 0.0);
}
else if (message == "Position" ) {
// Ecoute de la ball
ecoute = llListen (canal_de_ball , "", NULL_KEY , "");
// Demande positions
llSay (canal_ball, "Positions" );
}
else if (llGetSubString (message, 0, 0) == "<" ) {
// Reception des positions
list pos = llParseString2List (message, ["|"], []);
offsetPos = (vector )llList2String (pos , 0) - llGetPos ();
offsetRot = (rotation )llList2String (pos , 1) / llGetRot ();
llWhisper ( 0, "Memorisation de la position " + (string )offsetPos + " " + (string )offsetRot );
}
}
}

Sept variables nous sont nécessaires :

Variable


Type


Rôle

canal_ball


integer


Canal du toboggan vers la ball

canal_de_ball


integer


Canal de la ball vers le toboggan

canal_menu


integer


Canal pour le dialog

ecoute


integer


Mémorisation de l’écoute pour sa suppression

menu


list


Données du dialog

offsetPos


vector


Offset de positionnement de la ball

offsetRot


votation


Offset de rotation de la ball

La méthode initialisations permet de renseigner la valeur des canaux du chat et les données du Dialog. Les offset ont au départ une valeur nulle et l’écoute sera renseignée au moment nécessaire, c’est-à-dire lors de la création de l’écoute.

Une méthode intéressante est rez_ball qui permet de créer la ball. On commence par supprimer une ball éventuellement présente en envoyant un message de suicide. On calcule ensuite la position et la rotation de la future ball en se basant sur les informations de positionnement et de rotation du toboggan et de la valeur des offset. Je laisse les formules mathématiques aux amateurs de quaternions et autres fantaisies de ce genre pour en venir directement à l’importante fonction llRezObject. Celle-ci permet de créer un objet, ou plus exactement une instance d’un objet, parce que l’objet correspondant doit déjà exister dans l’objet qui contient le script. En d’autres termes dans notre cas la ball est déjà contenu dans le toboggan, on se contente d’en créer une instance. Observez les paramètres de cette fonction :

Paramètre


Type


Fonction

Inventory


string


Elément de l’inventaire à instancier

Pos


vector


Position de création

Vel


vector


Vélocité de l’objet créé

Rot


rotation


Rotation de création

Param


integer


Paramètre libre

Dans notre cas le premier paramètre correspond au nom de la ball contenue dans le toboggan. Le second et quatrième paramètres nous permettent d’ajuster la position et la rotation de la ball. Nous ignorons le troisième paramètre parce que nous voulons une ball immobile. Enfin nous profitons du dernier paramètre pour transmettre le canal d’écoute.

Tous les Rezzers que vous rencontrez sur sl sont fondés sur cette fonction ou sa sœur llRezAtRoot. Par exemple une maison imposante est découpée en morceaux et recréée sur place avec un Rezzer. Vous pouvez aussi imaginer un aménagement d’appartement modulable…

Je dois toutefois préciser que ce rezzer m’a passablement occupé ces derniers temps et mes amis sur sl s’amusaient de me voir en train de m’énerver sur mon toboggan. J’avais beau analyser mon code je ne trouvais pas la faille. Jusqu’au moment où j’ai eu l’idée de faire un essai avec des objets simples plutôt qu’avec les prims torturées de mon toboggan. Et là grosse surprise : tout fonctionnait parfaitement ! J’avais passé beaucoup de temps à chercher des erreurs qui n’existaient pas… Moralité : si vous voulez créer un rezzer utilisez plutôt une prim sans trop de modifications sinon vous risquer de galérer comme je l’ai fait avec des positionnements aléatoires. Il m’a suffit d’ajouter un cube à mon toboggan et de le définir comme root et tout s’est bien passé ensuite…

La dernière action de la méthode initialisations est de déclencher un timer réglé à 60 secondes pour envoyer une impulsion régulière à la ball pour lui demander de vivre. Ce que vous retrouvez dans la fonction timer avec le message « Vis » sur le canal de la ball.

Le reste du code ne présente pas de nouveauté. L’événement touch_start commence par supprimer une écoute éventuelle, mets en route l’écoute pour le dialog, et affiche le dialog.

L’événement listen récupère l’action de l’utilisateur :

Option


Action

Glissade


Mise en œuvre de la méthode rez_ball

Stop


Envoi du message « Meurs » à la ball

Position


Ecoute de la ball et envoi du message « Positions » à la ball

Lors de l’écoute de la ball le seul message qui peut revenir est celui qui contient les coordonnées. On teste le message avec la fonction llGetSubString qui permet d’extraire une sous-chaîne d’une chaîne en transmettant l’index du caractère de début et l’index du dernier à extraire. Pour extraire le premier caractère il faut mettre 0 pour les deux paramètres. Les coordonnées sont transmises sous le format string séparées par le caractère « | ». La fonction llParseString2List permet de récupérer dans une list les éléments séparés par le caractère passé en paramètre (en fait une liste de séparateurs au format string). Il suffit ensuite d’extraire les coordonnées avec un cast (transformation de type) et de les combiner aux coordonnées du toboggan pour obtenir les offset et les mémoriser.

Suite au tutorial suivant...

Tutorial 13 Toboggan 1/3

Les tutoriaux que je vous ai proposés jusque là n’incluaient pas trop d’éléments d’animation, juste quelques portes qui s’ouvrent ou se ferment et un avatar qui dort ou qui danse. Nous allons essayer maintenant de déplacer un avatar. Je me suis intéressé ces derniers temps aux toboggans. J’en ai rencontré plusieurs sur sl, la plupart situés au bord d’une piscine. La technique de déplacement de l’avatar me semblait être pratiquement toujours la même. Une ball en haut du toboggan prend en charge l’avatar pour initier une animation et permettre à l’avatar de glisser. Vous avez sans doute remarqué que la gravité sur sl agit sur tous les objets « physiques », dont les avatars font partie. Lorsque vous marchez sur un terrain en pente vous avez tendance à courir. Lorsque vous sautez d’un toit vous vous écrasez lamentablement au sol. On pourrait donc penser qu’un toboggan n’est après tout qu’un plan incliné et que l’avatar ne peut rien faire d’autre que glisser. Mais vous n’avez qu’à faire l’expérience pour constater que le problème n’est pas aussi simple. La première difficulté est qu’un avatar est en principe debout, il faut donc dans un premier temps l’obliger à s’asseoir. Nous avons déjà abordé cette question de l’animation dans de précédents tutoriaux. Il suffit de prévoir une ball avec un SitTarget et une animation., demander l’autorisation et lancer l’animation. Mais ce n’est pas parce qu’un avatar s’assoit qu’il va pour autant glisser dans la pente, il faut un peu l’aider. Pour cela il faut lui donner une impulsion vers l’avant. Il existe pour cela une fonction nommée llApplyImpulse qui va nous permettre de propulser l’avatar dans la pente. Avec cette pichenette l’avatar commence à glisser mais si nous lui laissons la totale liberté dans les rotations il y a de grandes chances pour qu’il bascule d’une façon pour le moins inélégante. C’est pour cette raison que nous allons verrouiller les rotations. De cette façon l’avatar n’a d’autre choix que de glisser sagement dans le toboggan. Mais que se passe-t-il lorsqu’il atteint le sol ? Il serait bien qu’arrivé là il se relève et que la ball aille sagement reprendre sa place en haut du toboggan pour une nouvelle glissade. Nous devons donc détecter l’arrivée au sol, ce qui se fait grâce à l’événement land_collision.

Pour ce tutorial il va vous falloir créer un toboggan, ce qui est après tout un bon exercice de build, vous pouvez voir sur la photo celui que j’ai réalisé pour l’occasion, en bois et en verre. Prévoyez une pente de 45% pour favoriser la glissade. Vous devez aussi créer une ball pour loger le script et l’animation. Pour cette dernière j’ai utilisé une free qui s’appelle « sit knees up2 », si vous voulez essayer la même vous devriez pouvoir la trouver facilement dans les boutiques free, je peux aussi vous la donner en ligne en m’envoyant un IM (Bestmomo Lagan), je peux aussi vous montrer le toboggan en action… Le positionnement de la ball doit être relativement bien ajusté pour que votre toboggan fonctionne. Voici le script à mettre dans la ball :

vector position; // Position de depart
string animation; // Nom de l'animation
key AgentKey; // Clef de l'avatar

initialisations()
{
// Memorisation position de depart;
position = llGetPos();
// Rotation à 0
llSetRot(ZERO_ROTATION);
// Animation
animation = "sit knees up2";
// Changement du texte du menu
llSetSitText("Glisser");
// Positionnement de l'animation
llSitTarget(<0.0,0.0,.5>, ZERO_ROTATION);
}

depart()
{
// Boule visible
llSetAlpha(1.0, ALL_SIDES);
// Passage en non physique
llSetStatus(STATUS_PHYSICS, FALSE);
// Retour de la boule au depart
llSetPos(position);
// Rotation à 0
llSetRot(ZERO_ROTATION);
}

default
{
// Entree dans l'etat
state_entry()
{
initialisations();
state attente;
}

// Reset du script
on_rez(integer start_param)
{
initialisations();
state attente;
}

}

state attente
{
// Entree dans l'etat
state_entry()
{
depart();
}

// Atteinte du bas du toboggan determine avec la collision avec le sol
land_collision(vector position)
{
// Avatar debout
llUnSit(AgentKey);
}

// Detection d'un changement
changed(integer change)
{
// Test de changement dans les liaisons
if (change & CHANGED_LINK)
{
// Determination de la clef de l'avatar
AgentKey = llAvatarOnSitTarget();
// Un avatar est present ?
if (AgentKey)
{
// Permission d'animation
llRequestPermissions (AgentKey, PERMISSION_TRIGGER_ANIMATION);
// Passage en physique et autorisation des rotations
llSetStatus(STATUS_PHYSICS, TRUE);
llSetStatus(STATUS_ROTATE_Y | STATUS_ROTATE_X | STATUS_ROTATE_Z, FALSE);
// Memorisation position de depart;
position = llGetPos();
// Impulsion
llApplyImpulse(llGetMass()*<3.0,0.0,0.0>, TRUE);
// Boule invisible
llSetAlpha(0, ALL_SIDES);
}
else
{
// Determination de l'animation en cours
string anim = llGetAnimation(AgentKey);
// Arret eventuel de l'animation
if(anim == "animation")
llStopAnimation(anim);
// Depart
depart();
}
}
}

// Permission accordee
run_time_permissions(integer permissions)
{
if(permissions == PERMISSION_TRIGGER_ANIMATION)
{
// Arret de l'animation par defaut
llStopAnimation("sit");
// Animation
llStartAnimation(animation);
}
}
}

Voyons le fonctionnement de ce code. Nous avons trois variables globales :

Ø position pour mémoriser la position initiale de la ball

Ø animation pour conserver le nom de l’animation

Ø AgentKey pour loger la clef de l’avatar.

J’ai prévu comme d’habitude une méthode d’initialisation qui permet de :

Ø mémoriser la position de départ de la ball grâce à la fonction llGetPos

Ø fixer la rotation par défaut de la ball

Ø déterminer l’animation à utiliser

Ø changer le texte du menu pour afficher « Glisser »

Ø positionner l’avatar pour l’animation

Après cette initialisation on passe dans l’état attente. Une méthode nommée depart qui est appelée au démarrage du script et à chaque fois qu’un avatar libère la ball en fin de glissade. Dans cette méthode nous prévoyons de :

Ø rendre la ball visible avec la fonction llSetAlpha

Ø passer la ball en mode physique avec la fonction llSetStatus

Ø positionner la ball à son emplacement initial

Ø mettre la rotation par défaut

Le mode physique est indispensable pour mettre en jeu la gravité et permettre la glissade. Par contre il faut désactiver cette propriété lorsque la ball attend en haut du toboggan sinon elle n’aura pas trop envie de rester en place J . Il est également judicieux de plus voir la ball lors de la glissade.

Arrivé à ce stade le script attend un changement. Celui-ci est détecté avec l’événement changed. Nous avons déjà abordé ce sujet précédemment et je n’insisterai pas. On récupère la clef de l’avatar avec la fonction llAvatarOnSitTaget, on demande la permission pour animer avec la fonction llRequestPermissions La fonction llSetStatus permet ensuite de passer en mode physique et de verrouiller les rotations. Ensuite on mémorise la position de départ avec llGetPos.

Ensuite on propulse l’avatar avec la fonction llApplyImpulse. Voyons ses paramètres. Le premier fixe la valeur de l’impulsion. Un vecteur permet de fixer sa direction, ici juste sur l’axe X. J’ai rendu la valeur dépendante de la masse de l’avatar (récupérée avec la fonction llGetMass) pour avoir une impulsion uniforme quel que soit son poids. Le second paramètre défini si les coordonnées sont globales ou locales. Dans notre cas nous choisissons évidemment local. Il ne reste plus qu’à rendre la boule invisible et c’est parti J.

Lorsque l’avatar arrive en bas il touche le sol, ce qui déclenche l’événement land_collision. L’avatar se remet alors debout grâce à la fonction llUnSit. Ce qui a pour effet également de déclencher l’événement changed mais cette fois aucun avatar n’est détecté. Nous stoppons alors l’animation en cours et appelons à nouveau la méthode depart pour réinitialiser le toboggan. Et ainsi de suite…

Tel quel ce toboggan fonctionne très bien. Mais il est présente quelques imperfections. La ball est forcément non liée au toboggan. En cas de déplacement de celui-ci il ne faut pas oublier cette particularité J. En cas de perte de la ball il faut également en recréer une a la main, la ball perdue ne se détruit pas automatiquement. Mais je réserve ces améliorations pour le prochain tutorial. Vous pouvez aussi faire des suggestions sur la direction que pourrait prendre cette réalisation. En attendant bonnes glissades J.

Tutorial 12 Les Dialog

Ce blog a pris son rythme de croisière pour les vacances. Les possibilités d'interaction dans SL sont un peu limitées. On peut entrer des données par l'intermédiaire du Chat. Mais si aucune donnée aléatoire est attendu il existe un moyen simple et élégant d'activer les scripts, les dialogues, ces menus au joli fond bleu qui apparaissent en haut a droite de l'écran pour créer une interactivité avec un jeu de boutons. Une seule fonction pour créer ces dialogues llDialog avec quelques paramètres dont vous trouvez la description ici :



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



Pas mal d'explications ici, mais en anglais bien entendu... En résumé la fonction permet d'afficher le dialogue. Le premier paramètre représente l'id de l'avatar pour lequel ce dialogue doit être affiché. Le second représente le message qui doit être inscrit en tête. Le troisième contient les textes a afficher sur les boutons. C'est ce troisième paramètre le plus important, il a comme type une list. Nous avons déjà eu l'occasion d'en utiliser une. Je rappelle qu'il s'agit d'un groupe de données, dans notre cas un ensemble de string qui représentent ce qui doit être écrit sur les boutons, et pas la même occasion le nombre de boutons. Le dernier paramètre mérite une petite explication préliminaire, à savoir ce qui se passe lorsque l'avatar clique sur un bouton. Tout simplement le texte du bouton est envoyé sur un canal du chat, et c'est justement ce dernier paramètre qui définit le numéro du canal. Je rappelle le le canal général est le 0, surtout n'utilisez pas cette valeur sinon ça va s'afficher pour tout le monde



Une fois le dialogue mis en place, puisque le résultat du clic aboutit sur un canal du chat il faut mettre en place une écoute de ce canal avec la fonction llListen que vous connaissez déjà et définir l'action à accomplir selon la valeur sélectionnée. Pour l'exemple j'ai choisi une commande pour des vitres avec réglage de la transparence et de la couleur, tout simplement parce que j'en ai parlé récemment avec une amie qui a fait un superbe script sur le sujet. J'en reprends la trame principale en le simplifiant par souci pédagogique, entre autre... J'ai prévu un sous-dialogue pour avoir une approche complète. Le premier dialogue permet le réglage de la transparence, le second de la couleur avec possibilité de retour en arrière. Voici le code :



integer channel;
list menu;
list menu_couleurs;
vector blanc;
vector rouge;
vector vert;
vector bleu;

initialisations()
{
channel = 27;
menu = [ "Transparent", "20%", "40%", "60%", "80%", "Opaque", "Couleur..." ];
menu_couleurs = [ "blanc", "rouge", "vert", "bleu", "...retour" ];
blanc = <1.0,1.0,1.0>;
rouge = <1.0,0.0,0.0>;
vert = <0.0,1.0,0.0>;
bleu = <0.0,0.0,1.0>;
teinte(0);
}

teinte(float alpha)
{
integer c;
for (c = 1; c <= llGetNumberOfPrims(); c++)
llSetLinkAlpha(c, alpha, ALL_SIDES);
}

couleur (vector coul)
{
integer c;
for (c = 1; c <= llGetNumberOfPrims(); c++)
llSetLinkColor(c, coul, ALL_SIDES);
}

default
{
state_entry()
{
initialisations();
llListen(channel, "", NULL_KEY, "");
}

touch_start(integer total_number)
{
llDialog(llDetectedKey(0), "Choisissez une option", menu, channel);
}

listen(integer channel, string name, key id, string message)
{
if (llListFindList(menu + menu_couleurs, [message]) != -1)
{
if (message == "Couleur...")
llDialog(id, "Choisissez une couleur", menu_couleurs, channel);
else if (message == "...retour")
llDialog(id, "Choisissez une option", menu, channel);
else if (message == "Transparent")
teinte(0.0);
else if (message == "Opaque")
teinte(1.0);
else if (message == "20%")
teinte(.2);
else if (message == "40%")
teinte(.4);
else if (message == "60%")
teinte(.6);
else if (message == "80%")
teinte(.8);
else if (message == "blanc")
couleur(blanc);
else if (message == "rouge")
couleur(rouge);
else if (message == "vert")
couleur(vert);
else if (message == "bleu")
couleur(bleu);
} else
llSay(0, name + " a choisi une option non valide '" + llToLower(message) + "'.");
}
}



Ce code s'applique à un ensemble de prims liés, normalement des vitres. Si vous l'utilisé pour un prim isolé il ne se passera rien pour des raisons qui vont être explicitées plus loin. Voyons un peu l'utilité des différents champs :


Champ Type Fonction
channel integer canal du chat
menu list liste des options du menu général
menu_couleurs list liste des options du menu des couleurs
blanc vector vecteur du blanc
rouge vector vecteur du rouge
vert vector vecteur du vert
bleu vector vecteur du bleu



La méthode initialisations permet d'affecter les valeur de ces champs, elle est appelée au lancement du script, pour bien faire il faudrait aussi l'appeler en cas de reset mais je n'ai pas voulu alourdir le code.



La méthode teinte a pour objet de modifier l'alpha (transparence) des prims liés. Je rappelle que dans une liaison de prim il est affecté un numéro à chacun qui démarre à 1, alors qu'un prim isolé possède par défaut le numéro 0. Le paramètre de la méthode contient une valeur pour l'alpha qui doit se situer entre 0 (transparent) et 1 (opaque). Pour changer l'alpha de tous les prims nous avons besoin de connaître leur nombre, c'est la fonction llGetNumberOfPrims qui nous donne cette valeur. Ensuite il suffit d'utiliser une boucle for pour balayer toutes les valeurs et utiliser la fonction llSetLinkAlpha pour modifier l'alpha du prim dont le numéro correspond à la valeur de la variable c.



La méthode couleur suit le même principe mais le paramètre est un vecteur qui représente la couleur et la fonction utilisée est llSetLinkColor qui permet de changer la couleur de prims liés.



Cette intendance étant mise en place nous avons un seul état, celui par défaut default. Au départ du script (state_entry) nous activons l'initialisation et l'écoute sur le canal déterminé par la variable channel. C'est par ce canal que les informations vont passer du dialog au script. Dès qu'un avatar clique sur une vitre (touch_start) on active le menu principal avec la fonction llDialog. Pour déterminer quel avatar a cliqué nous utilisons la fonction llDetectedKey qui nous renvoie son id, nous prévoyons une phrase d'accueil, nous transmettons la list qui contient les options à afficher, en l'occurrence menu et enfin le canal par lequel transmettre l'information (channel). Lorsque l'avatar clique sur une des options l'événement listen est déclenché. C'est ici que nous allons trouver le coeur du script.



Il faut comprendre que cet événement sera déclenché aussi pour le sous-menu, celui des couleurs. Nous commençons par un test pour savoir si l'information transmise sur le canal du chat correspond à une des option des deux menus concernés. C'est l'objet de la fonction llListFindList qui renvoie l'index de la première instance de message dans les deux listes menu et menu_couleur. En cas d'échec la valeur -1 et renvoyée et on transmet une information d'erreur sur le chat. Dans le cas contraire on entame une longue série de tests pour savoir quelle option a été choisie. S'il s'agit d'un pourcentage (également transparent et opaque) on appelle la méthode teinte pour changer l'alpha. S'il s'agit d'un couleur on appelle la méthode couleur pour changer celle-ci. Par contre s'il s'agit de "Couleur..." il faut lancer le sous-menu en utilisant à nouveau la fonction llDialog mais cette fois avec les bons paramètres pour le dialogue des couleurs. Il est aussi prévu un bouton de retour au menu principal qui se contenter de relancer celui-ci.



Vous êtes maintenant parés pour faire de superbes dialogues avec un nombre quelconque de sous-dialogues. Comme vous pouvez le constater la technique à mettre en oeuvre est simple, un bon script pour cette fin de vacances...

Tutorial 11 Les Sensor

Dans Second Life un avatar peut transporter de multiples objets. Une façon simple de le faire est l'attachement. Comme ce nom l'indique l'objet est alors intimement lié à l'avatar et se déplace avec lui. Cet objet doit être NonPhysical. Qu'est-ce que cela veut dire ? Vous avez peut être remarqué lorsque vous créez des objets dans SL qu'il y a des attributs "physiques", c'est à dire liés à la gravité, en quelque sorte ces attributs permettent de donner un mode de déplacement réaliste à l'objet, comme s'il était dans le monde réel. Un tel objet ne peux pas être attaché à un avatar. Vous savez certainement attacher un objet à partir de votre inventaire. Mais vous pouvez aussi le réaliser par script avec la fonction llAttachToAvatar. Cette fonction à un seul paramètre qui détermine le lieu de l'attachement, vous trouvez tous les renseignement ici :



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



En particulier la liste des constantes pour les localisations. Pour détacher un objet vous avez la fonction inverse llDetachFromAvatar.

Une autre façon de déplacer des objets avec un avatar et de tout simplement leur demander de le suivre. Vous avez sans douté repéré certains avatars avec un objet qui flotte derrière eux : oiseau, dragon, papillon et autres bestioles. Pour réaliser cela il existe une fonctionnalité nommée Sensor. C'est à dire une façon pour un objet de détecter la présence d'un autre objet, en l'occurrence un avatar dans notre cas. Le wiki n'est pas très loquace sur ce sujet :



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



Seulement 3 fonctions et 2 événements. Le seul objet des 3 fonctions est de déclencher l'événement sensor qui est l'élément principal :



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



Pour ce tutorial je vous propose un script tout simple que vous positionnez dans l'objet qui doit suivre votre avatar. Voici le code :



// Offset
vector offset = <-1,0,0>;
// Delai de recherche du proprietaire en secondes
float delai = .4;
// Distance de recherche en metres
float distance = 20;
// Angle de recherche en radians
float angle = PI_BY_TWO;
// Delai du deplacement en secondes
float deplacement = .3;

default
{
state_entry()
{
// Passage de l'objet en Physique, il a donc une masse
// et subit les lois de la gravitation
llSetStatus(STATUS_PHYSICS, TRUE);
// Petite attente de 0,1 seconde
llSleep(0.1);
// Recupration de la Key du proprietaire
key proprio = llGetOwner();
// Cherche la position du proprietaire
llSensorRepeat("", proprio, AGENT, distance, angle, delai);
}

sensor(integer total_number)
{
// Determination de la position du proprietaire
vector pos = llDetectedPos(0);
// Test de validite de la position
if (pos != <0,0,0>)
{
// Offset de positionnement
pos += offset;
// Deplacement de l'objet
llMoveToTarget(pos, deplacement);
}
}
}



A l'entrée dans le script (state_entry) la première instruction utilise la fonction llSetStatus. Cette fonction modifie les propriétés d'un objet. Par exemple on peut empêcher la rotation d'un objet sur un ou plusieurs axes. Vous trouvez tous les paramètres ici :



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



Dans notre cas on se contente de définir l'objet comme physique pour que son déplacement soit réaliste. La fonction llSleep se contente d'introduire une temporisation. Nous avons déjà utilisé la fonction suivant qui récupère la Key de l'avatar. La fonction suivante llSensorRepeat est plus intéressante, c'est elle qui définit les paramètres du sensor :



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



Cette fonction déclenche le sensor de façon répétitive. Elle possède plusieurs paramètres. Les trois premier permettent de déterminer l'objet à détecter : son nom, sa Key et son type. Les deux suivants concernent la zone de recherche : distance (range) et angle (arc). Le dernier paramètre définit la fréquence de la recherche en secondes. La plupars de ces paramètres ont été définis dans des variables globales pour une meilleure lisibilité du script.

L'événement sensor permet d'opérer la gestion du déplacement. Le paramètre de cet événement donne le nombre d'objets détectés dans la zone définie. Il ne nous est pas utile dans notre cas. La fonction suivante llDetectedPos fait partie du lot qui donne des informations sur les objets détectés :



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



Vous constatez qu'il y en a un certain nombre. Nous nous contentons de déterminer la position de l'avatar puisque cette information nous suffit pour pouvoir suivre ses déplacements. Après avoir vérifié la validité de cette position on déplace l'objet avec la fonction llMoveToTarget qui permet de déplacer un objet physique. L'offset sert à déterminer le positionnement par rapport à l'avatar.

Tutorial 10 Les Particles

Je me suis intéressé aux Particles ces derniers jours. Je me suis demandé comment on créait tous ces effets particulièrement réalistes comme la fumée ou les feux d'artifice. Je suis tombé dans la documentation sur une seule fonction disponible avec un paramètre unique :



http://wiki.secondlife.com/wiki/LlParticleSystem



A la lecture de cette page on se dit que l'affaire n'est pas simple et qu'on est parti pour quelques heures de prise de tête, encore plus si l'anglais n'est pas vraiment une seconde nature, ce qui est mon cas. J'ai fait quelques essais, j'ai cherché des exemples. J'ai trouvé un peu de tout, avec deux axes principaux : soit une fonction à rallonge avec des variables à n'en plus finir qui alourdissent le script, ce qui est plutôt à éviter dans ce genre :



http://lslwiki.net/lslwiki/wakka.php?wakka=ExampleParticleScript



Soit une approche plus "à la demande" pour ceux qui savent déjà bien ce qu'ils veulent :



http://lslwiki.net/lslwiki/wakka.php?wakka=LibraryKeknehvParticles



Aucune de ces deux approches ne m'ont vraiment satisfait et je ne pense pas vraiment être le seul dans ce cas quand je lis les questions sur les particles dans les forums. Le problème vient de l'abondance des éléments constitutifs du paramètre de la fonction llParticleSystem. On a un peu du mal à s'y retrouver et certaine explication sont un peu "ésotériques" au premier abord. A part ça le reste est simple, on peut avoir qu'un seul système de particules par prim, il est aligné sur l'axe Z dans le sens positif et il est constitué de sprites en 2D orientés vers la caméra. Il faut aussi savoir que les particles sont générées côté client, autrement dit sur votre PC.

A force de tourner cette histoire dans tous les sens et de mettre au point du code qui ne me satisfaisait toujours pas j'ai changé complètement d'optique. J'ai créé un logiciel qui génère automatiquement la fonction avec son paramètre. Je pense que pratiquement tous les scipteurs sur SL codent dans un éditeur externe. Bien sûr il y a toujours des masos qui aiment s'esquinter les yeux et user leur patience avec l'éditeur intégré mais bon... Comme la plupart d'entre vous je code en externe avec l'éditeur de Alphons van der Heijden dont je vous rappelle l'adresse de téléchargement :



http://www.lsleditor.org/



Alors la solution que je vous propose est aussi un programme externe qui présente les éléments du paramètre de la fonction d'une façon conviviale et génère le code qu'il suffit de copier et coller dans votre éditeur. Comme ce blog ne permet pas le téléchargement de fichiers j'ai créé un petit site complémentaire ou je présente sommairement le logiciel et où vous pouvez vous le procurer :



http://script.lsl.free.fr



Donc pour les détails concernant ce programme et ses fonctionnalités je vous renvoie à cette adresse. Ici je me contente de vous proposer un script de test tout simple :



default
{
state_entry()
{
llParticleSystem([
PSYS_PART_FLAGS, 0
| PSYS_PART_INTERP_COLOR_MASK
| PSYS_PART_EMISSIVE_MASK,
PSYS_SRC_PATTERN,
PSYS_SRC_PATTERN_ANGLE,
PSYS_PART_START_ALPHA, 0.9,
PSYS_PART_END_ALPHA, 0.5,
PSYS_PART_START_SCALE, <0.1,0.1,0.0>,
PSYS_PART_END_SCALE, <0.6,0.6,0.0>,
PSYS_PART_START_COLOR, <0.7529412,0,0>,
PSYS_PART_END_COLOR, <1,0.627451,0.4784314>,
PSYS_PART_MAX_AGE, 10.0,
PSYS_SRC_MAX_AGE, 100.0,
PSYS_SRC_BURST_RATE, 0.1,
PSYS_SRC_BURST_PART_COUNT, 1000,
PSYS_SRC_ANGLE_END, 0.5,
PSYS_SRC_ANGLE_BEGIN, 0.0,
PSYS_SRC_BURST_RADIUS, 0.2,
PSYS_SRC_OMEGA, <0,0,0.2>,
PSYS_SRC_ACCEL, <0,0,0>,
PSYS_SRC_BURST_SPEED_MIN, 0.2,
PSYS_SRC_BURST_SPEED_MAX, 0.5]);
}

touch_start( integer num )
{
state off;
}
}

state off
{
state_entry()
{
llParticleSystem([]);
}

touch_start( integer num )
{
state default;
}
}



Vous pouvez l'utiliser pour vos essais. Il suffit de le poser dans un objet. Quand vous le touchez ça démarre, la fois suivante ça s'arrête. Il vous suffit de copier la partie de code générée par le programme Particles et de le coller dans ce script au bon endroit.

Tutorial 9 Balançoire

J'ai découvert lsl il y a 3 mois lorsqu'une amie m'a demandée de l'aider à écrire un script dans Second life. Le but était d'animer une balançoire. Je pensais la tâche aisée étant donnée mon expérience en programmation. Mais c'était sans compter sur les particularités de lsl et de Second Life. J'ai galéré pendant deux jours pour trouver les éléments nécessaires et j'ai fulminé contre l'hégémonie de la langue anglaise au niveau de la documentation. Il en est sorti un script fonctionnel mais pas vraiment esthétique. Avec le recul je me rends compte de ses imperfections. Récemment dans le forum de jeuxonline (je ne peux que vous conseiller ce forum en français avec des personnes très compétentes et vraiment serviables) j'ai proposé ce script suite à une question. Voici le lien :



http://forums.jeuxonline.info/showthread.php?t=803732



On m'a très justement fait remarquer que mon script ne fonctionne plus lorsque la balançoire subit une rotation. Il était donc temps que je reprenne ce script pour le rendre à la fois plus élégant et plus efficace. Voici le code qu'il en est sorti :



//
// Balancoire Version 1.0
// par bestmomo
//

// Periode du mouvement en secondes
float Periode;
// Amplitude du demi mouvement en degres
float Amplitude;
// Amplitude sur l'axe X
float AmplitudeX;
// Amplitude sur l'axe Y
float AmplitudeY;
// Pas de changement
float Pas;
// Rotation de depart
rotation Rotdepart;
// Gestion du temps
float Temps;

initialisations()
{
Periode = 4.0; // 4 secondes
Pas = 0.2; // 0,2 secondes
// Rotation de depart
Rotdepart = llGetRot();
// Rotation de depart Euler
vector RotdepartEuler = llRot2Euler(Rotdepart);
// Amplitude du mouvement en radians
Amplitude = 40.0 * DEG_TO_RAD;
// Amplitude sur l'axe X
AmplitudeX = Amplitude * llCos(RotdepartEuler.z);
// Amplitude sur l'axe Y
AmplitudeY = Amplitude * llSin(RotdepartEuler.z);
}

default
{
touch_start(integer total_number)
{
// Mise en marche
state marche;
}
}

state marche
{
state_entry()
{
initialisations();
// Demarrage du timer
llSetTimerEvent(Pas);
}

touch_start(integer total_number)
{
// Retour position initiale
llSetRot(Rotdepart);
// Reset du script
llResetScript();
}

timer()
{
// Incrementation du temps
Temps += Pas;
// Calcul des angles
float AngleX = AmplitudeX * llSin((TWO_PI / Periode) * Temps);
float AngleY = AmplitudeY * llSin((TWO_PI / Periode) * Temps);
// Calcul de la rotation
rotation Rot = Rotdepart * llEuler2Rot( );
// Application de la rotation
llSetRot(Rot);
}

}



Au niveau de la construction le script doit se trouver dans le root, au niveau de l'axe. Le prim correspondant doit être centré et ne doit pas subir de rotation sur les axes X et Y. Le plus simple est de prévoir un box que vous redimensionnez, quitte à le rendre transparent ensuite pour conserver uniquement les cordes. C'est ce que j'ai fait sur mon exemple que vous pouvez voir sur la photo. Cette fois la balançoire peut être déplacée sans souci à condition évidemment de la conserver horizontale ! Ce qui paraît très naturel pour une balançoire.

Le mouvement d'une balançoire est pendulaire, autrement dit c'est une fonction sinusoïdale. J'ai vu sur SL des balancelles avec un mouvement très fluide mais désespérément linéaire. Il est facile de réaliser ce type d'animation avec la fonction llTargetOmega :



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



Mais le soucis de réalisme doit nous éloigner de cette solution qu'il faut réserver à des mouvements linéaires du genre manège. LSL possède une librairie mathématique relativement complète :



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



On y trouve en particulier les fonctions trigonométriques qui vont nous servir pour la balançoire : llCos pour trouver le cosinus et llSin pour le sinus. Voyons un peu le code...

Il y a un certain nombre de variables globales. La Periode représente le rythme du balancement, c'est le temps mis pour accomplir un mouvement de balancement complet. L'Amplitude représente la dimension du balancement (en fait la moitié de celui-ci) en degrés. Ensuite viennent deux amplitudes : AmplitudeX et AmplitudeY. Ces deux valeurs sont nécessaires pour gérer les rotations de la balançoire lors de ses différents positionnements. Il s'agit de la projection de l'amplitude du mouvement par rapport aux axes X et Y. Le Pas de changement représente l'incrémentation temporelle, autrement dit à quel rythme nous allons calculer la nouvelle position. La rotation initiale est stockée dans la variable Rotdepart. La dernière variable Temps est consacrée à gérer le temps de fonctionnement, elle est intimement liée au Pas.

La méthode initialisations sert à initialiser toutes ces variables. Pour la Periode et le Pas vous pouvez essayer différentes valeurs pour voir les effets. Pour trouver la rotation de départ on utilise la fonction llGetRot que nous avons déjà vu pour le script de la porte. Vous savez qu'on obtient un quaternion, pour nous simplifier la vie nous transformons cette valeur en Euler avec la fonction llRot2Euler. J'ai pris une demie Amplitude de 40° que je transforme en radians grâce à la constante DEG_TO_RAD. Ensuite nous déterminons les composantes de l'amplitude sur les axes X et Y avec les fonctions cosinus et sinus.

L' organisation générale du script est très simple avec deux états : le classique default qui contient seulement l'événement touch_start dans l'attente qu'un avatar touche la balançoire, ce qui a pour effet de basculer dans l'état marche. Dans cet état l'événement state_entry lance l'initialisation et le timer avec le Pas.

Au niveau de l'événement timer nous trouvons l'animation proprement dite. On commence par incrémenter le Temps de la valeur du Pas. Ensuite nous calculons le nouvel angle par rapport aux axes X et Y. L'amplitude est simplement multipliée par une fonction sinusoïdale du temps. Les non matheux peuvent sauter ces lignes ! Ensuite on calcule la rotation et on l'applique avec la fonction llSetRot que vous connaissez déjà.

Si la balançoire est touchée lorsqu'elle est en fonctionnement c'est l'événement touch_start de l'état marche qui est déclenché. Il suffit alors de ramener la balançoire à sa rotation initiale et à faire un reset du script.

Tutorial 8 Animation (2/2)

Je tiens à rappeler que les scripts que je propose dans ces tutoriaux ont une vocation essentiellement didactique. Le but est de présenter chaque fois des éléments nouveaux tout en revoyant certaines choses déjà rencontrées dans les tutos précédents. Ces scripts ne sont pas forcément complets et sans bugs, parfois il existe d'autres manières de faire plus performantes mais moins pédagogiques. Je livre donc ces codes sans aucune garantie si ce n'est celle d'apprendre un peu à programmer avec LSL...

Continuons notre animation des avatars avec cette fois un Danceball. Au niveau du fonctionnement envisageons du classique : une boule avec un texte flottant "Danser", avec un clic droit le menu propose "Danser" à la place de "Sit", le danceball contient plusieurs animations et le choix est aléatoire, au clic sur "Danser" une première danse démarre et le DanceBall disparaît ainsi que le texte flottant. Au bout d'un certain délai l'animation change, et ainsi de suite jusqu'à ce qu'on clique sur "Se lever", à ce moment là l'animation s'arrête et le Danceball réapparaît avec son texte flottant. Vous avez juste à créer une boule de votre couleur favorite et de poser dedans le code suivant :



// Animation en cours
string Animation;
// Nombre total de danses
integer Total;

initialisations()
{
// Danceball visible au depart
llSetAlpha(TRUE, ALL_SIDES);
// Texte au-dessus du Danceball
llSetText("Danser", <1.0,1.0,1.0>, 1.0);
// Positionnement de l'animation
llSitTarget(<0,0,-.3>, ZERO_ROTATION);
// Nombre de danses
Total = llGetInventoryNumber(INVENTORY_ANIMATION);
if (Total == 0)
{
llWhisper(0, "Erreur: Pas d'animation...");
return;
}
// Changement du menu
llSetSitText("Danser");
}

animer(string anim)
{
// Arret de l'animation precedente
llStopAnimation(anim);
// Animation aleatoire nouvelle
string TempAnim = Animation;
do {
Animation = llGetInventoryName(INVENTORY_ANIMATION, (integer)llFrand(Total - 1));
} while (Animation == TempAnim);
// Animation en marche
llStartAnimation(Animation);
}

default
{
state_entry()
{
initialisations();
}

on_rez(integer start_param)
{
initialisations();
}

changed(integer change)
{
// Test de changement dans les liaisons
if (change & CHANGED_LINK)
{
// Determination de la clef de l'avatar
key AgentKey = llAvatarOnSitTarget();
// Un avatar present ?
if (AgentKey)
{
// Permission d'animation
llRequestPermissions(AgentKey, PERMISSION_TRIGGER_ANIMATION);
// Demarrage du timer
llSetTimerEvent(60.0);
// Effacement du Danceball
llSetAlpha(FALSE, ALL_SIDES);
// Effacement du texte flottant
llSetText("", <0,0,0>, 1.0);
}
else
{
// Arret de l'animation
llStopAnimation(Animation);
// Reinitialisation du script pour le prochain avatar
llResetScript();
}
}
}

timer()
{
// Changement de l'animation
animer(Animation);
}

run_time_permissions(integer permissions)
{
if(permissions == PERMISSION_TRIGGER_ANIMATION && Total > 0)
// Desactivation de l'animation par defaut "sit" et depart premiere animation
animer("sit");
}
}

Il faut aussi poser des animations de danse dans votre boule. Au final votre Danceball contient donc un script et des animations. Comme à notre habitude nous allons analyser ce code dans le détail.

Deux variables globales permettent de mémoriser l'animation en cours et le nombre total d'animations contenues dans le Danceball. A la phase d'initialisation le Danceball est rendu visible avec la fonction llSetAlpha qui possède deux paramètres : le premier est la valeur alpha, c'est-à-dire le degré de transparence souhaité (à 0 c'est complètement transparent et à 1 totalement opaque, entre les deux la transparence est plus ou moins importante), comme nous voulons parfaitement voir le Danceball il faut utiliser la valeur 1. Pourquoi avoir utilisé TRUE ? Vous vous souvenez sans doute que TRUE correspond à 1 et FALSE à 0. Le fait de mettre TRUE apporte plus de lisibilité au code, enfin c'est mon point de vue ! Le deuxième paramètre correspond aux faces (side) que vous désirez affecter avec votre valeur alpha. La constante ALL_SIDES signifie "toutes les faces", pour notre sphère de toutes façon il n'y a qu'une seule face et nous aurions tout aussi bien pu mettre la valeur 0. Mais si vous avez par exemple un cube alors vous avez 6 faces et la constante devient bien pratique pour affecter toutes les faces d'un coup.

Le texte flottante st créé avec la fonction llSetText. Le premier paramètre est le texte à afficher, le second sa couleur et le troisième sa transparence (alpha). Le vecteur <1.0,1.0,1.0> correspond aux valeurs maximales des primaires rouge, vert et bleu, donc à la couleur blanche. Quant à la transparence la valeur 1 signifie opaque comme nous l'avons déjà vu ci-dessus.

Nous avons déjà vu la fonction au précédent tuto, je passe donc à l'instruction suivante. Elle va me permettre d'introduire la notion d'inventaire (inventory). Jetez un coup d'oeil à cette page :



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



Vous pouvez y lire qu'il existe deux sortes d'inventaires; celui de l'avatar que vous connaissez bien dans lequel vous stockez vos habits, animations et autres merveilles, et celui des objets que vous connaissez moins. Vous pouvez mettre ce que vous voulez dans un objet, nous avons déjà vu que nous pouvons y mettre des scripts, des sons et des animations. Mais vous pouvez aussi y mettre des textures, des landmarks, des notecards... Il existe un certain nombre de fonctions pour gérer tout ce bazar. Ce qui nous intéresse pour le moment est le nombre d'animations contenues dans notre Danceball. C'est la fonction llGetInventoryNumber qui va nous le dire. Il suffit de lui passer en paramètre le type de contenu dont nous voulons connaître la quantité. Vous avez sur la page du WIKI toutes les constantes qui peuvent être utilisées. Dans notre cas il nous faut INVENTORY_ANIMATION puisque nous voulons connaître le nombre d'animations. Cette valeur est stockée dans la variable globale Total pour être utilisée ultérieurement dans le script.

Ensuite j'ai prévu un petit test pour vérifier que notre Danceball contient effectivement des animations. Si le Total est égal à 0 alors on envoie une message au niveau du Chat. Remarquez que l'utilisation de la fonction llWhisper à la place de llSay permet de limiter la portée du message à 10 mètres au lieu des classiques 20 mètres de llSay. Ensuite le return permet de sortir de l'initialisation sans accéder à l'instruction suivante. Par contre si le test est positif on change l'indication du menu comme nous l'avons fait au tuto précédent.

Arrivés à ce stade il ne se passe plus rien tant qu'un avatar n'a pas la bonne idée de s'asseoir sur le Danceball. A ce moment là l'événement changed est déclenché. Je passe rapidement sur les premières instructions qui sont identiques à celle du précédent tuto. On teste si une nouvelle liaison est présente, si c'est un avatar, et si c'est le cas on demande la permission de l'animer. On sait que cela déclenche l'événement run_time_permissions, au niveau de cet événement on vérifie la permission et aussi que le total des animation est bien supérieur à 0, donc que nous allons pouvoir lancer une animation. A ce moment là nous appelons la méthode animer avec comme paramètre l'animation par défaut sit :



animer(string anim)
{
// Arret de l'animation precedente
llStopAnimation(anim);
// Animation aleatoire nouvelle
string TempAnim = Animation;
do {
Animation = llGetInventoryName(INVENTORY_ANIMATION, (integer)llFrand(Total - 1));
} while (Animation == TempAnim);
// Animation en marche
llStartAnimation(Animation);
}



On commence par arrêter l'animation passée en paramètre, dans notre cas il s'agit de sit, mais au prochain appel de la méthode ce sera la première animation lancée, et ainsi de suite... l'instruction suivante crée une variable de type string pour mémoriser l'animation actuelle, dans notre premier passage celle-ci est vide mais pour les passages suivant elle contiendra l'animation en cours. Ensuite nous avons un nouvel élément avec le mot clef do. Il s'agit d'une boucle, on dit de faire (do) une chose tant qu'une condition est satisfaite (while). Que faisons-nous dans cette boucle ? Avec la fonction llGetInventoryName nous allons chercher une animation dans l'inventaire du Danceball. Cette fonction attend comme premier paramètre le type d'élément désiré, nous retrouvons notre constante déjà vue plus haut, et en deuxième paramètre l'index dans la collection des éléments concernés. Imaginons que nous avons mis 6 animations, elles sont indexées de 0 à 5. c'est cette valeur qu'attend la fonction. ici nous utilisons la fonction llFrand que nous connaissons déjà pour générer une valeur aléatoire dans la gamme des index possible en nous référant au nombre total d'animations. Mais à quoi sert donc la boucle ? Et bien le test de la boucle est constitué par la vérification que l'animation choisie au hasard n'est pas celle déjà en cours. Autrement dit on ne veut pas avoir deux fois de suite la même danse. A la sortie on lance la nouvelle animation sélectionnée.

Revenons maintenant à l'événement changed que nous avons un peu abandonné. Nous voyons qu'après la demande de permission nous lançons un timer réglé à 60 secondes. Délai choisi entre les différentes danses. Au niveau de l'événement du timer vous constatez qu'on se contente d'appeler notre méthode animer avec l'animation en cours en paramètres. Nous comprenons maintenant pourquoi. Après l'initialisation du timer on efface la Danceball avec la fonction llSetAlpha en mettant cette fois son premier paramètre à 0 (FALSE). Il ne reste plus après qu'à effacer le texte flottant avec la même fonction qui nous a servi pour l'afficher llSetText mais cette fois en passant une chaîne de caractères vide et la couleur noire <0,0,0>.

J'espère que ce tutorial vous donnera envie de vous lancer dans la création. N'hésitez pas à me faire part de vos réalisations.

Tutorial 7 Animation (1/2)

Blog de lsl :Les scripts de Second life, Tutorial 7 Animation (1/2)



Jusque là nous ne nous sommes intéressés qu'aux objets, voyons un peu ce que ça donne du côté des avatars. S'il est relativement facile de faire bouger des objets, de les redimensionner, de changer leur couleur et leur texture il n'en est pas de même pour les avatars, heureusement ! Un avatar dépend de son propriétaire et nous ne pouvons intervenir sur lui qu'avec son accord (sauf rare exception dont je ne parlerai pas). Commençons par quelques précisions syntaxiques. Second Life est constitué de simulateurs (simulator, on dit souvent sim ou region) dont chacun est chargé de gérer un espace de 256 m². Ces simulateurs sont mis en oeuvres par des PC, chacun chargé de gérer un certain nombre de simulateurs. Une page du wiki vous donne plus de renseignements sur les simulateurs :



http://lslwiki.net/lslwiki/wakka.php?wakka=simulator



Un utilisateur de SL, comme vous et moi est constitué d'un agent et d'un avatar. Un agent est géré directement par le simulateur comme une présence. Un avatar est l'aspect visuel d'un agent : un corps avec toutes ses caractéristiques morphologiques. Un avatar peut porter des habits, avoir des attachements et subit les lois de la physique. Dans le reste de ce tutorial je ne vous parlerai que d'avatar. Pour animer un avatar il faut utiliser des animations. Une animation est une petite séance de cinéma pour avatar... Il en existe un certain nombre incluses dans SL. Vous les avez toutes listées ici :



http://lslwiki.net/lslwiki/wakka.php?wakka=animation



Mais vous pouvez en trouver bien d'autres dans les boutiques free. Vous pouvez aussi en créer avec des logiciels comme Poser au format BVH. Pour ce tutorial je vous propose d'utiliser une animation intégrée : sleep. Comme son nom l'indique c'est pour faire dormir un avatar ! Le contexte est simple : un lit, un avatar, un clic droit sur le lit, à la place du classique "sit" apparaît "dormir" et lorsque l'avatar clique sur celui-ci il va gentiment se coucher sur le lit. Voici le code correspondant que je vais vous commenter :



// Animation
string Animation;

initialisations()
{
// Animation
Animation = "sleep";
// Changement du menu
llSetSitText("Dormir");
// Positionnement de l'animation
llSitTarget(<0,0,1>, ZERO_ROTATION);
}

default
{
state_entry()
{
initialisations();
}

on_rez(integer start_param)
{
initialisations();
}

changed(integer change)
{
// Test de changement dans les liaisons
if (change & CHANGED_LINK)
{
// Determination de la clef de l'avatar
key AgentKey = llAvatarOnSitTarget();
// Un avatar present ?
if (AgentKey)
{
// Permission d'animation
llRequestPermissions(AgentKey, PERMISSION_TRIGGER_ANIMATION);
}
else
{
// Arret de l'animation
llStopAnimation(Animation);
// Reinitialisation du script pour le prochain avatar
llResetScript();
}
}
}

run_time_permissions(integer permissions)
{
if(permissions == PERMISSION_TRIGGER_ANIMATION)
{
// Arret de l'animation par defaut
llStopAnimation("sit");
// Annimation
llStartAnimation(Animation);
}
}
}



Pour un peu vous reposer du tuto précédent j'ai fait tout simple cette fois. Une seule variable globale Animation chargée de mémoriser le nom de l'animation. Pour mon exemple j'ai choisi sleep mais vous pouvez évidemment prendre n'importe laquelle, enfin presque mais j'y reviendrai. Au niveau de l'initialisation, après avoir affecté la variable Animation, je change le texte du menu du clic droit. Par défaut on voit apparaître sit, ici je le remplace avec "dormir". C'est la fonction llSetSitText qui nous permet de faire ça. Attention vous n'avez droit qu'à 9 caractères ! Le dernier élément de l'initialisation est assuré par la fonction llSitTarget. Cette fonction a une double utilité. D'abord elle permet de définir l'emplacement exact de l'animation par rapport à l'objet (un vertor d'offset et une rotation). Ensuite elle permet d'utiliser la fonction llAvatarOnSitTarget qui détermine si un avatar vient de s'asseoir et nous renvoie sa clef (Key). En plus un événement changed est renvoyé lorsqu'un avatar s'assoit. Autrement dit à l'issue de cette initialisation on attend l'événement changed... Cet événement est important, regardons le de plus près :



http://lslwiki.net/lslwiki/wakka.php?wakka=changed



Vous constatez qu'il survient dès qu'il se passe quelque chose au niveau de l'objet : inventaire, couleur, forme, taille, texture, liaisons, possession, region, téléportation... Le seul paramètre de l'événement est un integer, autrement dit une variable de 32 bits. A chacun de ces bits correspond une valeur vraie ou fausse. Ce qui permet de stocker 32 valeurs booléennes. Pour extraire une de ces valeurs il faut utiliser un "et" logique. Considérons le CHANGED_LINK, qui nous concerne pour notre script puisque lorsqu'un avatar s'assoit sur un objet il se retrouvé lié à celui-ci en dernière position de la hiérarchie. Dans le tableau du WIKI nous voyons que cette constante a pour valeur 0x20. Cette valeur peut vous paraître étrange si vous n'êtes pas habitué à d'autre système de numération que la décimale ! Elle est en hexadécimal. Pour ceux qui ignorent de quoi il s'agit je vous renvoie à cette page :



http://fr.wikipedia.org/wiki/Syst%C3%A8me_hexad%C3%A9cimal



La valeur hexadécimale 20 (le "0x" placé devant sert juste à signifier qu'il s'agit d'un hexadécimal) correspond donc à 32 en décimal et à 10000 en binaire. Donc lorsqu'il survient un changement dans les liaisons des prims d'un objet l'événement changed renvoie la valeur binaire 100000. Si nous appliquons un "ET" logique entre la valeur du paramètre renvoyé par l'événement et la valeur 100000 (on appelle cela un masque en programmation) le résultat sera "VRAI". Voilà comment nous détectons la présence d'un avatar. Une fois que nous l'avons détecté nous devons savoir de qui il s'agit, pour cela la fonction llAvatarOnSitTarget nous donne sa clef (Key). Ensuite il faut quand même effectuer un test pour savoir si le lien supplémentaire est vraiment un avatar et pas un simple prim ajouté ! Si nous avons une valeur correcte de clef alors c'est bon, c'est bien un avatar. Nous devons alors lui demander la permission de lui appliquer une animation avec la fonction llRequestPermissions. Regardez cette page :



http://lslwiki.net/lslwiki/wakka.php?wakka=llRequestPermissions



Vous constatez qu'on l'interroge pour pas mal de choses ! Ce qui nous intéresse là est représenté par la constante PERMISSION_TRIGGER_ANIMATION qui est aussi un masque de valeur 16 en décimal, donc 1000 en binaire. On sait si l'avatar est d'accord par l'intermédiaire de l'événement run_time_permissions dont le paramètre unique détermine le type d'autorisation. Vous en avez la liste ici :



http://lslwiki.net/lslwiki/wakka.php?wakka=run_time_permissions



Vous constatez qu'on retrouve notre PERMISSION_TRIGGER_ANIMATION avec la même valeur. Dans l'interception de l'événement nous faisons un test pour savoir si nous recevons la bonne valeur de paramètre. Ensuite il faut désactiver l'animation par défaut sit avec la fonction llStopAnimation puis lancer notre animation avec la fonction llStartAnimation.

Le dernier point à prendre en compte c'est que nous recevons l'événement changed aussi lorsque l'avatar se lève. A ce moment là il faut stopper notre animation et faire un Reset du script.

Tutorial 6 Memory coloré (2/2)

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 = ;
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.

Tutorial 5 Memory coloré (1/2)

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 = ;
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 !