<%@LANGUAGE="VBSCRIPT" CODEPAGE="1252"%> 3e exercice - L'avion - étape 2 Deuxième étape

Télécharger le .dir commenté de cette étape
(bouton droit -> enregistrer la cible)

ATTENTION : cette étape est intermédiaire. Elle provoque une erreur de script, volontairement. Les explications sont données ci-dessous.

Dans cette étape, nous avons commencé à remplir le constructeur on beginSprite, et la méthode jouer() de notre sprite.

Rappelons le principe que nous avons retenu pour faire jouer (reconstituer) notre vidéo. Il s'agit tout simplement de la transcription du principe même du cinéma : lorsqu'une image est affichée, on affiche la suivante, puis la suivante, etc...
Pour cela, nous devons bien sûr disposer des images, puis connaître le nom de toutes ces images, et enfin connaître l'ordre dans lequel on doit les afficher.
Lorsqu'on dispose d'un morceau de pellicule, cet ordre est immuable puisque les images sont inscrites de manière fixe à la suite les unes des autres sur un support physique non modifiable. Du fait de cet ordre immuable, les images qui composent la séquence n'ont pas besoin d'être nommées puisqu'elles sont inscrites de manière fixe sur le support physique. Sur une bande vidéo, c'est la même chose sauf que les images sont inscrites cette fois sous forme d'un signal magnétique. Mais le support physique séquentiel est toujours là : c'est la bande magnétique.

Dans le cas d'une reconstitution par programmation, on est beaucoup plus libre puisque les images sont inscrites sur un support à accès aléatoire (le disque dur ou le cédérom, ou le dévédérom)(*).
En revanche, cette liberté a un prix, très modeste, nous allons le voir : il nous faut explicitement indiquer au programme quelle séquence d'images utiliser.

Pour cela, nous allons utiliser un outil fondamental des langages de programmation (et que nous aurons l'occasion d'utiliser à nouveau dans d'autres applications que de la vidéo image par image). Il s'agit de ce que Lingo appelle des listes, et que Javascript, Action Script ou Visual Basic appellent des tableaux (arrays).
Ce sont des entités qui permettent de stocker de grandes quantités d'information, regroupées sous un seul nom symbolique, et dans lesquelles on va pouvoir à notre tour, piocher selon un processus aléatoire.

Le processus de programmation que nous allons mettre en œuvre sera alors le suivant :
- déclaration / création d'une liste,
- peuplement de cette liste avec le nom des acteurs formant notre séquence, et dans le bon ordre,
- utilisation de la liste ainsi constituée pour faire défiler les images qui composent notre séquence, dans un seul sprite, en lui changeant sa propriété member selon les noms qu'on sera allé piocher dans la liste.

On aperçoit d'ores et déjà que si on change le contenu de notre liste, ou qu'on modifie les règles pour y accéder, ou encore qu'on modifie la fréquence avec laquelle on y accède, on pourra bénéficier d'une très grande souplesse et d'une très grande finesse pour piloter notre séquence.

Avant de regarder en pratique la manière de mettre cette liste en œuvre, il nous faut mener une petite discussion sur la gestion de notre séquence d'images, et la manière que nous allons choisir pour les mettre à la disposition du langage de programmation. Les considérations qui vont suivre sont spécifiques à Director, mais la question se pose quel que soit l'environnement, chacun selon ses spécificités.

(*) Rappelons que lorsque qu'on parle d'un support à accès séquentiel (bande magnétique, pellicule argentique...), on entend par là que pour accéder à un point donné du support et donc à une donnée spécifique, il faut parcourir tout ce qui précède.
Lorsqu'on parle d'un support à accès aléatoire, cela ne veut pas dire que le point de lecture se situe n'importe où (au sens de n'importe comment), mais que l'on peut choisir très exactement, au moyen d'un système d'adressage, à quel endroit, et donc à quelle donnée, on souhaite accéder sur le support.
Le type même d'un support à accès aléatoire est le disque dur, mais ce sont aussi les barrettes de RAM, les cartes mémoire, les memory sticks, etc... Il résulte de cet accès que l'on peut extraire les données que l'on souhaite dans l'ordre qu'on veut, avec un temps d'accès identique pour toutes les données (en première approximation), quel que soit l'endroit où elles sont écrites sur le support. Avec les supports utilisés aujourd'hui, ces temps d'accès sont très courts, et pour des médias d'une taille raisonnable, on peut considérer que l'accès est quasi instantané.

Une fois évoqués ces problèmes d'importation, que nous reste-t-il à faire pour compléter notre animation dans cette étape ?

Fidèle au système que nous avons utilisé jusqu'à présent, il est assez aisé de se rendre compte que le réservoir d'images dans lequel puiser pour reconstituer notre vidéo est propre au sprite que nous allons utiliser pour héberger ces images.
Nous allons donc déclarer notre liste d'image en propriété du sprite.
Une liste se stocke dans une propriété ou une variable, exactement comme n'importe quel autre type de données (numérique ou chaîne de caractères).

En réalité, il faut savoir qu'en interne, dans le logiciel, une liste est traitée comme un objet à part entière, et qu'à ce titre il sera lui-même doté de méthodes - fournies par les concepteurs du langage - , lesquelles nous permettront entre autres d'accéder à la valeur d'un élément dans la liste, de changer la valeur d'un élément, d'ajouter des éléments, de compter le nombre d'éléments...
Donc, contrairement à une variable où à une propriété simple (contenant par exemple une valeur numérique ou une chaîne de caractères, et pour laquelle on n'a pas d'autre traitement à faire que d'affecter ou lire une valeur), il faudra, dans le cas d'une liste, indiquer explicitement au langage que l'entité que nous créons est une liste, de manière à pouvoir lui appliquer les méthodes dont elle est dotée. Si nous ne faisions pas cela, nous obtiendrions des erreurs. Du reste, dans tous les langages orientés objet, les listes ou tableaux sont toujours traités comme des objets.

Pour faire cette déclaration explicite en lingo, c'est très simple.
Après avoir déclaré le nom de l'entité sur la ligne property comme nous savons le faire, il suffit, dans la constructeur on beginSprite, d'affecter à cette entité la valeur d'une liste vide, en utilisant les marqueurs de liste, que sont les crochets [ ].

Et nous écrirons donc, si nous appelons notre réservoir d'acteurs lstActeurs (comme liste des acteurs) :

property spriteNum, lstActeurs

on beginsprite me
lstActeurs = []
end

Cela suffit. Nous disposons donc maintenant d'une liste vide, c'est à dire qui ne contient rien, qui a 0 élément, mais qui en revanche, puisqu'elle a été déclarée pour telle, saura en emmagasiner quand nous le lui demanderons.

Et c'est ce que nous allons faire tout de suite.

Nous savons déjà que pour changer l'aspect d'un sprite, il suffit d'affecter une nouvelle valeur à sa propriété member, en lui indiquant le nom de l'acteur qu'il doit désormais afficher. Notre liste d'acteurs va donc devoir comporter, et dans l'ordre, le nom des acteurs qui composent notre séquence vidéo.

Et comme nous avons pris soin, au moment de l'importation de nos images, de respecter cet ordre justement, il va suffire de lire, toujours dans cet ordre, le nom des acteurs qui composent notre distribution, ici externe.

(Nous avons donné, dans la présentation de l'exercice, la manière de se constituer une distribution externe et nous avions choisi de ne pas la marquer comme "utilisée dans l'animation courante". Pour attacher une distribution externe existante sur le disque dur à une animation, il faut aller dans le menu modification -> animation -> distribution. Dans la boîte de dialogue qui s'ouvre, choisir "lier", et dans le sélecteur de fichier qui apparaît alors, choisir la distribution externe à lier. )

Ensuite, nous pourrions bien sûr taper et saisir nous-même le nom des acteurs. Mais ce serait fastidieux et long, sans intérêt, et nous aurions de gros risques d'erreurs de fautes de frappe.
Lorsque nous souhaitons nous occuper, comme ici, d'un grand nombre de choses, nous disposons de la structure de boucle, repeat / end repeat. Comme dans notre cas, nous sommes dans la phase d'initialisation, nous n'aurons pas à redouter les inconvénients de cette instruction (discutés à l'étape 4 de l'exercice 2).

Nous écrirons donc :

répéter avec un compteur qui va de 1 à 200 (puisque nous avons 200 acteurs)
lire le nom de l'acteur qui porte le numéro du compteur et ajouter ce nom à la liste des acteurs
fin répéter

et notre on beginsprite va donc devenir :

on beginSprite me
lstActeurs = []

repeat with i = 1 to 200
lstActeurs.add(member(i, 2).name)
end repeat

sprite(spriteNum).member = lstActeurs[1]
end

On découvre là la manière de désigner un acteur par son numéro quand il n'est pas dans la distribution interne de base. La distribution interne de base est toujours la distribution numéro 1, et le numéro des autres distributions s'incrémente ensuite à mesure qu'on les créé. Ici, nous lisons les noms des acteurs de la distribution numéro 2.

Puis nous découvrons également deux méthodes des listes :
- celle qui permet d'ajouter un élément à une liste : lstActeurs.add(leNomQuonVeutAjouter)
- celle qui permet de lire la valeur d'un élément : lstActeurs[leRangDeCetElementDansLaListe] (le rang est aussi appelé index)

En effet, à la dernière ligne du beginSprite, on demande au sprite d'afficher l'élément numéro 1 de la liste (puisque c'est la première image de la séquence).

Plusieurs remarques importantes à ce sujet :

- Le premier élément d'une liste lingo porte toujours le numéro 1 (on dit qu'elles sont base 1). C'est une différence par rapport à ActionScript ou javascript, où les listes et tableaux sont base 0 (c'est à dire que leur premier élément porte le numéro 0), et par rapport à VB, où on peut préciser la base qu'on veut pour les tableaux.

- Les listes Lingo sont à 1 dimension. C'est à dire que ce ne sont pas des tableaux. En revanche, elles permettent de construire des tableaux malgré tout, puisque les éléments d'une liste peuvent être eux-mêmes des listes. On peut donc avoir des listes de listes, et même des listes de listes de listes...

Considérons la liste suivante : maListe = [ [1,2,3] , ["a", "b", "c"] , ["chose", "bidule", "truc"] ]

On a en fait un tableau de trois lignes et de trois colonnes, chacune des listes qui composent la liste principale représentant une ligne du tableau. On peut représenter cette liste de listes de la manière suivante :

1 2 3
"a" "b" "c"
"chose" "bidule" "truc"

C'est un tableau dans lequel les lignes, au lieu d'être écrites les unes en dessous des autres, sont écrites les unes à la suite des autres. Dans un tel tableau, si nous souhaitons accéder à la valeur de la case qui est à la 2e ligne et à la 3e colonne, nous écrirons maValeurCherchee = maListe[2][3], valeur qui est égale à "c". C'est à dire le troisième élément dans le deuxième élément de la liste principale.

- On voit par là que ces listes sont très souples, puisque les différentes lignes d'un tableau n'ont pas besoin de compter le même nombre de colonnes. Par ailleurs, on peut les redimensionner comme on veut puisqu'il suffit d'ajouter de nouveaux éléments. Ce détail est précieux quand on sait les problèmes de redimensionnement de tableaux que posent d'autres langages.

- Enfin, et conformément à la caractéristique non typée du langage, on voit que les données d'une liste n'ont pas besoin d'être homogènes, et qu'on peut allègrement mélanger, à l'intérieur d'une même liste ou combinaison de listes, des valeurs numériques entières ou non, des chaînes de caractères, d'autres listes, des variables qui contiennent des objets, etc...

- Les différentes valeurs qui composent les listes doivent être séparées par des virgules (dans le cas où on saisit manuellement des valeurs). Si on passe par la méthode add(), les virgules seront mises automatiquement.

- Dernier point qui rejoint la problématique du nommage des variables en général : il est intéressant, dans les conventions qu'on se fixe pour ses méthodes de travail générales à soi, d'adopter un système de préfixe habituel quand il s'agit de nommer une liste. Dans tout ce cours, on trouvera toujours les listes nommées avec le préfixe "lst", qui présente 2 avantages : il est clair du point de vue du langage naturel, et dans le cas d'un travail à plusieurs, il est clair également dans de nombreuses autres langues que le français.
Enfin, on remarquera que je l'ai nommée lstActeurs avec un "s" à Acteurs, et que j'ai respecté la capitale à "Acteurs" puisqu'il s'agit d'un nom formé de deux éléments (préfixe + nom). On gagnera toujours en effet à respecter une cohérence : puisqu'une liste d'acteurs induit obligatoirement qu'il y en a plusieurs (même si en fait on peut trouver des listes à un élément), ALORS le nom de la liste sera choisi avec une orthographe qui respecte cette logique, et donc on mettra un "s" à Acteurs. Se donner cela comme règle fixe évitera beaucoup de pertes de temps, en s'évitant notamment de retourner vérifier les déclarations pour savoir comment on l'a écrit cette fois-ci, en s'évitant les messages d'erreur du type "variable used before assigned a value " !

Une fois notre liste d'acteurs constituée dans le beginSprite, il nous reste à la lire dans la méthode jouer(), pour reconstituer notre vidéo.

La démarche est la suivante :

A l'exécution de jouer()
lire le nom de l'acteur actuellement affiché dans le sprite
trouver la position de cet acteur dans la liste
prendre le suivant et l'afficher
fin

Ce qui donne en code :

on jouer me
acteurCourant = sprite(spriteNum).member.name
leRang = lstActeurs.getPos(acteurCourant)
sprite(spriteNum).member = lstActeurs[leRang + 1]
end

On découvre là une instruction importante des listes : laListe.getPos(elementCherche). Celle-ci permet de trouver quelle est la position occupée par une valeur dans une liste. Cette instruction renvoie la valeur 0 si l'élément cherché n'est pas dans la liste.

Pour d'autres instructions de liste, moins usuelles peut-être, on pourra se reporter au dictionnaire Lingo.

Comme annoncé au début de cette étape, dans son état actuel, le script provoque une erreur. Cette erreur ne survient pas immédiatement, mais à la fin de la lecture de la séquence :

En effet, lorsque l'acteur affiché est le dernier de la liste, et qu'on demande de prendre le suivant, cet élément n'existe pas.
On a alors une erreur. On ne peut pas interroger une liste en dehors de son étendue, c'est à dire sur un index plus grand que le nombre d'éléments de la liste. On obtiendrait également une erreur si l'on demandait l'élément 0 ou un élément négatif.

Il va donc nous falloir gérer explicitement les fins et débuts de liste et c'est ce que nous allons faire dans l'étape suivante.

Une remarque à propos des messages d'erreur : les alertes d'erreur de script telle que ci-dessus comportent trois boutons. On voit trop souvent ceux qui s'initient aux délices de la programmation appuyer immédiatement sur le bouton "annuler" puis rester le bec dans l'eau avec un air découragé, et en déplorant : "ça ne marche pas !"
Lorsqu'on rencontre une alerte, la première chose à faire est de LIRE ce que dit le message d'alerte, puis d'appuyer sur le bouton "script...". La fenêtre de script s'ouvre alors et le pointeur de la souris se met sur la ligne qui a provoqué l'erreur. Ce qui représente tout de même une facilité pour déboguer. Attention toutefois, même si le curseur se met sur la ligne fautive, cela ne veut pas dire à tous les coups que l'erreur est dans cette ligne. Il peut également arriver qu'une ligne tout à fait correcte prescrive une action incompatible avec des dispositions prises plus haut dans le programme.
Exemple typique : déclaration d'une variable puis tentative de lui appliquer la méthode de liste add() sans avoir déclaré la variable explicitement comme liste auparavant. La ligne contenant add() est correcte syntaxiquement, mais s'applique alors à un objet inapproprié. L'erreur est bien en amont de la ligne fautive, puisqu'il s'agit d'un oubli de déclaration préalable.

C'est également le cas auquel nous sommes confrontés ici : l'erreur est provoquée par la ligne N° 28 : sprite(spriteNum).member = lstActeurs[leRang + 1]
Cette ligne est correcte syntaxiquement : c'est le calcul du rang qui est mauvais.
La cause de l'erreur est quasiment indiquée en clair dans le message d'erreur "index out of range" (index en dehors de l'étendue autorisée).
Pour déboguer, il faut alors surveiller la valeur de la variable locale leRang.
Pour surveiller cette valeur, on dispose de l'instruction put (ici put leRang), qui n'est utilisée qu'en phase de développement : placée à un endroit judicieux dans le script (un endroit judicieux est TOUJOURS une ligne qui précède de près celle qui provoque l'erreur), put écrit dans la fenêtre de message la valeur de leRang à chaque exécution du script. On peut ainsi constater l'évolution de cette variable, et s'apercevoir que sa valeur va dépasser le nombre d'éléments de la liste.
Put est commode pour les variables locales. Pour surveiller les propriétés et les globales, on dispose de l'inspecteur d'objet (depuis la version MX - auparavant, on avait la fenêtre de surveillance). Put est l'équivalent de trace() en ActionScript et il existe dans tous les langages une instruction permettant d'écrire la valeur d'une variable dans une fenêtre spéciale appelée en général fenêtre de sortie.

Ajoutons que le troisième bouton "Déboguer..." donne accès à la fenêtre du débogueur. Il y a un débogueur dans tous les environnements de programmation : le débogueur permet de mettre des "points d'arrêt" dans le programme, points où l'exécution du programme s'arrête en donnant la valeur de toutes les variables. Le débogueur permet également d'exécuter le programme pas à pas (c'est à dire instruction par instruction), ce qui facilite la traque d'une erreur.

NB : si on consulte le dictionnaire Lingo, on verra que ce langage comprend un autre type de listes appelées listes de propriétés. Une de leurs utilisations sera vue dans le cinquième exercice à propos de l'analyse de texte. Ce sont des listes qui, au lieu de comprendre des éléments simples (c'est à dire uniques), permet de stocker des valeurs par couple, sous la forme propriété:valeur.
Je n'en recommande pas l'usage pour ma part, car elles sont d'un maniement peu clair. Je préconise soit deux listes dont les rangs se correspondent (le nième élément de la première liste est apparié au nième élément de la seconde liste), soit une liste de listes à deux éléments chacune.

 

Retour à la première étape
Passer à la troisième étape