Script parent ou comportement ?
C'est un projet simple de jeu arithmétique: l'enfant
doit regrouper les animaux en fonction de leur poids pour
préparer leur embarquement sur trois bateaux. Une
arche de Noé mathématique en quelque sorte...
Un clic sur chaque animal suffit pour qu'un dialogue d'alerte
(en réalité une voix) informe l'enfant sur
le nom et le poids. Tous les animaux réagissent donc
identiquement au clic mais chaque fois les informations
sont différentes. La réalisation de ce projet
simple est ici le prétexte idéal pour comparer
trois approches de la programmation Lingo. Scripts de scénario,
scripts parents et comportements.
A) - Première méthode : les scripts "de
scénario"
Une première méthode consiste à associer
à chaque sprite sur la scène, des scripts
différents permettant d'intercepter les clics. Soit
pour l'écureuil :
on mouseUp
alert "Je suis
un écureuil" & return
& "je pèse 0,5 kilos"
end
De même pour le perroquet :
on mouseUp
alert "Je suis
un perroquet" & return
& "je pèse 0,5 kilos"
end
Et ainsi pour chaque animal. Chaque script contient, codées
"en dur", les informations spécifiques. Si le bestiaire
est complet, on pourrait bien trouver l'approche un peu
répétitive ! Mais la principale critique à
faire de cette méthode c'est la difficulté
de maintenance du code. Si l'on décide après
coup d'indiquer les poids en livres pour localiser le projet,
alors TOUS les scripts doivent UN PAR UN être modifiés.
Une approche plus élégante de ce genre de
travail est d'utiliser la POO (programmation orientée
objet).
B) - Deuxième méthode : l'approche objet
(les scripts parents)
Tenant compte du fait que tous les animaux ont la même
réaction lorsque l'on clique dessus, on préférera
définir une fois pour toute et globalement ce comportement
dans un script d'un genre spécial que l'on appelle
un script parent. Dans ce script seront définies
abstraitement toutes les propriétés génériques
et toutes les possibilités d'actions communes à
tous les animaux. Mais bien sûr s'ils partagent tous
également le fait d'avoir un nom et un poids, de
réagir au clic, les animaux devront avoir à
chaque fois aussi leurs spécificités propres.
Le script parent ne sera pas le script de tous les animaux,
auquel cas ils se comporteraient tous de façon semblable,
mais on dira que les animaux sont autant d'instances
du script parent, des cas concrets et particuliers du genre
commun, des objets individuels,
stockées en mémoire, héritant
leurs caractéristiques générales du
script parent mais avec des propriétés à
chaque fois distinctement renseignées.
On créé d'abord un script par Fenêtre/Script
que nous nommons "parentBetes" puis - c'est important -
dans les propriétés de l'acteur script (Modifier/Acteur/Propriétés)
on lui affecte le type "parent". Notre code doit maintenant
définir les propriétés génériques
de tout animal. Chacun aura un poids (pPoids), sera placé
sur une piste du scénario (pNumeroDeSprite) et enfin
aura un nom (pEspece).
property pPoids, pNumeroDeSprite,
pEspece
Mais bien sur, pour chaque animal ces propriétés
seront diversement renseignées. Lors de la naissance
d'une bête nous veillerons à ce que nous soient
transmises des valeurs précises (arguments unePiste,
uneEspece, unPoids) que nous utiliserons pour sa fabrique.
on new
me, unePiste, uneEspece,
unPoids
pPoids = unPoids
pNumeroDeSprite = unePiste
pEspece = uneEspece
global
listeDesObjets
add listeDesObjets me
return me
end
Le mot clef "me" est utilisé de façon purement
conventionnelle ("animal", "this" ,"toto" ou "xyz" feraient
très bien l'affaire) pour désigner l'instance
à chaque fois créée.
En ajoutant l'instance à la liste globale "listeDesObjets"
on s'assure d'avoir en permanence accès aux instances
créées.
Notre script parent ne serait pas complet si nous n'y définissions
pas une réaction au clic, commune à tous les
instances (un méthode).
on clicSur me
if rollover(pNumeroDeSprite)
then
alert " Je suis
un " & pEspece & return & " je pèse
" & pPoids & " kilos"
end if
end
Parce que définie dans le script parent, cette "méthode"
sera transmise génétiquement à tout
nouvel objet et ainsi "ClicSur "est un événement
que toutes nos instances sauront traiter. Tous les objets
crées, lorsqu'ils reçoivent le message "clicSur"
savent quoi faire : si la souris est au dessus du sprite
qui leur est associé (propriété pNumeroDeSprite),
ils doivent informer de leur nom et poids respectifs.
Mais tout seul, notre script "parentBetes" ne produira
strictement aucun effet. Nous devons créer un autre
script, d'animation par exemple, dans lequel nous appellerons
"parentBetes" autant de fois que nous voulons créer
d'instance. Nous n'échapperons pas à la nécessité
de donner une valeur aux arguments attendus par le script
parent (unePiste, uneEspece, unPoids) pour la fabrique d'objet.
Les animaux sont disposés sur les pistes 2 à
7. A noter , nous choisissons de transmettre "uneEspece"
sous forme de #symbole plutôt que de chaîne
entre guillemets. Ce n'est absolument pas nécessaire
ici, mais juste une bonne habitude à prendre.
on startmovie
global
listeDesObjets
listeDesObjets = [ ]
-- création des instances
"animaux"
new(script
"parentBetes", 2, #écureuil, 0.5)
new(script
"parentBetes", 3, #éléphant, 900)
new(script
"parentBetes", 4, #oiseau, 0.5)
new(script
"parentBetes", 5, #kangourou, 0.5)
new(script
"parentBetes", 6, #ours, 220)
new(script
"parentBetes", 7, #crapeau, 0.5)
set the
floatPrecision to
1
end
La modification de la propriété floatPrecision
permet d'éviter qu'un crapeau ne pèse 0,50000
kilos par exemple : purement esthétique.
Lors du lancement de l'animation, le script parent sera
appelé 6 fois et à chaque fois sera créée
une instance en mémoire reprenant les caractéristiques
communes sous une forme individuées. Dès lors
ce sont ces objets stockées en mémoires qui
agiront et réagiront. Le script parent, devenu inutile,
pourra même être effacé !
Maintenant, à chaque clic, nous devons faire parvenir
à tous nos objets l'événement "clicSur",
c'est-à-dire que nous appelons une méthode
"clicSur" pour chaque objet créé "me". Lors
de l'appel d'une méthode d'objet, on passe l'objet
concerné en argument. Toujours dans le script d'animation,
nous écrivons :
on mouseUp
global listeDesObjets
repeat with n in listeDesObjets
clicSur (n) --
pour chaque instance, on appelle sa méthode clicSur
end repeat
end
Ce n'est donc pas on l'a compris le script-parent lui-même
qui répond par la suite au clic mais seulement et
chaque fois l'une ou l'autre instance stockée en
mémoire.
À la fin de l'animation, en supprimant toute référence
à nos objets, nous autorisons automatiquement Director
à libérer la place qu'ils occupent en mémoire.
on stopMovie
global listeDesObjets
listeDesObjets
= [ ]
end
Cette approche objet était requise dans les versions
4 et 5 de Director. Dans la version 6, prenant acte de l'orientation
résolument objet de l'application elle-même
(les sprites, les acteurs, la scène elle-même
sont déjà de toute façon des objets
intégrés), Macromedia a introduit la notion
de comportements (behaviors).
C) - Troisième méthode : Les comportements
1) - De la POO sans le savoir
Et d'abord qu'est ce que c'est un comportement ? En quoi
un comportement se distingue-t-il des autres scripts ?
Dans Director 7 l'appellation "scripts de scénario"
a complètement disparue. Hormis les scripts d'acteurs,
les scripts d'animation et les scripts parents tout est
comportement. (En fait il nous apparaîtra à
la fin de cet article qu'il en était déjà
tacitement de même dans la version 6)
Un comportement c'est un script qui possède les
fonctionnalités suivantes :
Il peut être attaché à différents
sprites
Il peut utiliser des propriétés indépendantes
et différemment renseignées pour chaque
sprite auquel il est attaché
Dans l'exemple qui suit on déclare une propriété
commune (couleurDuSpriteAssocié), on lui affecte
une valeur individuelle aussitôt que c'est possible
et lors du clic, on utilise la référence "uneInstance"
pour retrouver la valeur de cette propriété :
property couleurDuSpriteAssocié
on beginSprite
uneInstance
couleurDuSpriteAssocié = string(sprite(the
spriteNum of
uneInstance ).color)
end
on mouseUp
uneInstance
alert
"Je suis l'instance du comportement associée au
sprite " &
the
spriteNum of
uneInstance &
" dont la couleur est " & the
couleurDuSpriteAssocié
of
uneInstance & " , je gère les clics"
end
Ce comportement peut être attaché à
plusieurs sprites. Au lancement de l'animation Director
va en créer autant d'instances que de sprites, autant
de "copies" stockées en mémoire. Lors du clic
de la souris, c'est chaque fois l'instance associée
à un sprite qui gérera l'événement
mouseUp. La variable "uneInstance" désigne chaque
fois l'objet en cours, telle ou telle instance du comportement
concernée par l'événement et permet
de retrouver ses propriétés.
NOTA : Le mot clef "me" est devenu comme la marque de fabrique
des comportements mais outre qu'il n'est que d'un usage
conventionnel et peut être remplacé ("uneInstance"),
il n'est véritablement utile de posséder une
référence à l'objet courant que dans
les scripts parents de la POO "classique" quand on créé
une instance par l'instruction new.
En fait grâce à la variable the currentSpriteNum,
un comportement n'a que rarement à faire usage de
la référence d'objet i-e du mot clef "me".
Par exemple, le comportement plus haut peut tout aussi bien
s'écrire :
property couleurDuSpriteAssocié
on beginSprite
couleurDuSpriteAssocié = string(sprite(the
currentSpriteNum).color)
end
on mouseUp
alert
"Je suis l'instance du comportement associée au
sprite " & the currentSpriteNum
& " dont la couleur est " & couleurDuSpriteAssocié
& " , je gère les clics"
end
2) - Comportement = instance(s) de comportement
Un comportement - comme un script parent - n'est donc pas
un script dont les gestionnaires "mouseUp, mouseEnter, exitFrame.."
s'exécutent en réponse aux événements.
C'est un modèle qui sert à fabriquer autant
d'instances ou d'objets que requis. La création des
instances est automatique (au lancement de l'animation),
les instances sont automatiquement associés aux sprites
ou aux tableaux et reçoivent leurs événements,
enfin elles sont automatiquement globales (ils restent en
mémoire tant que les sprites sont sur la scène).
Hormis ces faits les comportements ne se distinguent en
rien des scripts parents. L'animation lancée, une
fois que la tête de lecture a atteint le sprite auxquels
un comportement était attaché et que Director
à créé une instance en mémoire,
on peut effacer l'acteur comportement de la distribution
(D7 seulement) sans nuire à l'animation. C'est bien
là la preuve qu'une instanciation a eu lieu.
Associé à un sprite, l'instance d'un comportement
recevra durant la lecture de l'animation les événements
système suivants et dans cet ordre :
new - (Director 7 seulement)
Ce message est envoyé une fois, lors de la création
de l'instance. L'aide en ligne de Director néglige
à tord cet événement et confie l'instanciation
au soin de l'événement beginSprite. À
noter : the currentSpriteNum renvoie zéro lors
de l'appel new. Toutefois la propriété the
spriteNum of me est déjà renseignée.
beginSprite - Une seule
et unique fois, lorsque la tête de lecture atteint
la première apparition du sprite sur la scène.
C'est à ce moment que Director place l'instance
de comportement dans "the scriptInstanceList". Cette liste,
propriété du sprite, sera utilisée
par la suite pour transmettre les événements
à l'instance. Quand un événement
mouseUp mouseEnter... à lieu, Director en informe
toutes les instances présentes dans la liste "the
scriptInstanceList" du sprite concerné.
À ce stade, Le frame n'étant pas dessiné,
toutes les propriétés du sprite peuvent
ne pas être accessibles. D'après l'aide en
ligne, il n'a pas de dimensions par exemple puisqu'il
n'est pas encore dessiné sur la scène. Cela
paraît logique mais c'est faux. Director n'a pas
du lire l'aide en ligne !
Nota : La commande go est sans effet dans un gestionnaire
beginSprite.
prepareFrame - À
chaque frame, chaque instance reçoit ce message
juste avant que le frame soit dessiné. Là
encore on s'étonnera que les propriétés
rect of sprite ou width of sprite soient pourtant accessibles
et ce, dès le premier frame.
enterFrame - À chaque
frame. EnterFrame n'est séparé du précèdent
(prepareFrame) que par le délai nécessaire
pour dessiner le frame. EnterFrame à lieu après
prepareFrame et avant tout autre événement
y compris Idle (Idle n'est envoyé qu'aux frame
script et movie script).
mouseUp, mouseDown, mouseEnter,
mouseLeave, mouseWithin, mouseUpOutside, rightMouseUp,
rightMouseDown, keyUp, keyDown - selon les événements
utilisateurs. A noter : mouseWithin est envoyé
une fois par image tant que la souris est dans la zone
de sélection du sprite. KeyUp et keyDown ne sont
envoyés aux sprite que si ce sont des acteurs champs
ou des acteurs texte modifiables.
exitFrame - À chaque
frame, et juste avant le frame suivant. Aucun événement
idle ne sépare exitFrame du prepareFrame suivant.
A noter exitFrame, enterFrame et prepareFrame sont également
envoyés aux scripts d'acteurs si ces derniers ont
une occurrence sur la scène.
endSprite - Ce message n'est
envoyé qu'une seule fois lorsque la tête
de lecture quitte le dernier frame du sprite. C'est à
ce moment que la liste "the scriptInstanceList" est vidée.
Dès lors qu'aucune variable n'y fait plus référence,
Director libère la mémoire des instances.
3) - Démonstration du mécanisme
Il est est possible très simplement de démonter
le mécanisme qui associe une instance de comportement
à un sprite. pour cela nous vous proposons de créer
un comportement simple que l'on nomme "essai" et que l'on
associe à un sprite (piste 2 ) sur la scène.
on mouseUp
me
alert
"Je suis l'instance qui gère le clic sur le sprite
2"
end
Au lancement de l'animation et dès l'apparition
du sprite piste 2, Director créé une instance
du comportement cité et l'ajoute à la liste
"the scriptInstanceList of sprite 2".
Dans la fenêtre message on peut connaître cette
propriété par :
put the
scriptInstanceList of
sprite 2
-- [<offspring "essai" 1 2f97d20>]
C'est une liste des objets ou instances de comportement
auxquels les événements du sprite 2 doivent
être transmis. Entre deux tag < > on trouve
d'abord l'origine (en anglais "offspring") de l'instance
(l'acteur script "essai"), suit un chiffre (ici 1) indiquant
le nombre de références faites à l'objet
(pour que Director détruise un objet rappelons-le
il suffit de supprimer ses références), enfin
l'indication de son emplacement mémoire.
Nous décidons de créer une nouvelle référence
de l'instance :
TOTO = getAt(the
scriptinstanceList of
sprite 2, 1)
Puis de la supprimer de la liste
deleteAt the
scriptInstanceList of
sprite 1, 1
Essayez donc de cliquer maintenant ! Aucune réaction
ne se produit. Le sprite ne peut plus en effet transmettre
l'événement à son instance puisqu'il
ne la trouve plus dans sa liste. Pourtant l'instance existe
toujours et son gestionnaire mouseUp est disponible mais
nous devons l'appeler directement :
mouseUp(TOTO)
Nous avons tout simplement transformé l'instance
d'un behavior en objet "libre", en objet traditionnel. On
libère la mémoire par :
TOTO = 0
4) - Paramétrage d'instances et interface auteur/utilisateur
Rappelons-nous maintenant notre jeu arithmétique
: un clic sur un animal ouvre un dialogue informant sur
son nom et sur son poids. Toutes les instances de comportement
attachées aux animaux auront donc la même réaction
lors du clic, de même toutes auront ces deux même
propriétés (property
pMonEspece, pMonPoids) même si c'est à chaque
fois avec des valeurs différentes. Le comportement
(modèle de ces instances) que nous devons rédiger
est simple :
property pMonEspece, pMonPoids
on beginsprite
me
sprite(the
spriteNum of
me).ink = 8
end
on mouseUp
me
alert "Je suis
un " & pMonEspece & return
& "je pèse " & pMonPoids & " kilos"
end
Il manque quand même quelque chose à notre
comportement ! Chaque fois qu'il fera l'objet d'une instanciation
ce sera avec des valeurs de propriétés différentes,
à défaut de pouvoir coder "en dur" ces valeurs,
nous devons ici utiliser un dialogue de paramétrage.
En POO classique, c'est lors de l'instanciation (instruction
new) que l'on passe les valeurs de propriété
individuelles pour l'instance. Director et ses comportement
proposent une approche originale : c'est quand on attachera
un comportement à un sprite et AVANT QU'AUCUNE INSTANCE
NE SOIT CRÉÉE que les paramètres seront
saisis via l'interface de Director. Ces paramètres
individuels, à affecter aux instances à venir,
seront stockés par Director AVEC le sprite. Ils resteront
accessibles et modifiables.
On pourra les visualiser, le sprite étant sélectionné,
dans l'inspecteur de comportement.
Les modifier aussi à l'aide du bouton paramètres

5) - Créer un dialogue de paramétrage
Lors du glisser d'un comportement depuis la distribution
sur un sprite ou sur la scène, ou lors de l'association
d'un comportement à une sélection via l'inspecteur
de comportements, etc., la fonction getPropertyDescriptionList
est utilisée par Director pour connaître les
valeurs individuelles à affecter aux propriétés.
En plaçant un gestionnaire getPropertyDescriptionList
dans notre comportement on obtient l'affichage d'un
dialogue permettant de définir des propriétés
PARTICULIÈRES pour les instances à venir.
property pGenre, pMonEspece,
pMonPoids
on getPropertyDescriptionList
ListeDescriptive = [ #pGenre : [#default:"ours",
#format:#string, #comment:"À
quel ¬
genre appartient-il ?"] #pMonEspece
: [#default:"ours", #format:#string,
#comment:"Quelle ¬
est son espèce ?"], #pMonPoids
: [#default:150, #format:#integer,
#comment:"Quel est son ¬
poids", #range:[#min:1,
#max:999]]]
return ListeDescriptive
end getPropertyDescriptionList
on beginsprite
me
sprite(the
spriteNum of
me).ink = 8
end
on mouseUp
me
alert "Je suis
un " & pMonEspece & return
& "je pèse " & pMonPoids & " kilos"
end
On obtient :
(NOTA :Pour obtenir dans ce dialogue un menu pop-up en
lieu et place d'une zone de saisie, nous aurions pu renseigner
#range avec une liste de valeurs : #pMonEspece : [#default:"ours",
#format:#symbol, #comment:"Quelle
est son espèce ?", #range:["écureuil",
"éléphant", "grenouille"] ] )
Si la fonction getPropertyDescriptionList
retourne une liste de
propriétés (ListeDescriptive), le message
runPropertyDialog est alors
émis. Il en est de même lorsque, dans l'inspecteur
de comportements, on clique sur le bouton paramètres.
En interceptant l'événement runPropertyDialog,
nous empêcherons le dialogue de paramétrage
d'apparaître. Dans ce cas les propriétés
de l'instance-sprite auront automatiquement les valeurs
par défaut. Pour nous ici, ce ne peut être
utile que dans un seul cas : le sprite est un ours
et les valeurs par défaut lui conviennent ! Dans
tous les autres cas nous laissons passer le message-évènement
runPropertyDialog afin que le dialogue s'affiche bien :
on runPropertyDialog
if the name
of member
( the memberNum
of sprite
(the currentSpriteNum)
) <> "ours" then pass
-- le dialogue de
paramétrage ne s'ouvrira que si c'est utile
end runPropertyDialog
Dernier raffinement, lors de l'ouverture de l'inspecteur
de comportements ou à la sélection d'un behavior
dans sa partie supérieure, le message getBehaviorDescription
est émis. S'il est intercepté par un
gestionnaire d'événement on
getBehaviorDescription, il permet d'afficher un commentaire
dans la fenêtre grsie de l'inspecteur. Aux fins de
classement...
on getBehaviorDescription
return "ce comportement
est un applicables aux fleurs comme à tous les animaux"
-- ce texte sera
affiché par l'inspecteur de comportements
end getBehaviorDescription
6) - Modifier l'icône d'un comportement
Les behaviors en provenance de la bibliothèque -
vous l'avez remarqué - ont leurs icônes personnalisées.
Nos comportements veulent eux aussi être distingués
! Pour ce faire on doit copier d'abord un bitmap dans le
presse-papiers - ce peut être fait depuis la fenêtre
dessin de Director - et ouvrir le dialogue de propriétés
de l'acteur comportement par Modifier/Acteur/Propriétés.
La boite de dialogue présente alors une miniature
à gauche dotée d'un menu pop-up qui nous permet
en collant le contenu du presse-papiers d'obtenir une miniature
personnalisée pour notre script.
7) - Ajouter nos comportements à la bibliothèque
le dossier "libs" est situé à l'intérieur
du dossier de l'application Director. C'est dans ce dossier
que l'on doit placer les Distribution contenant nos behaviors
si l'on veut les voir apparaître dans la librairie.
Ici nous avons créer une nouvelle distribution EXTERNE
par Fichier/nouveau/Distribution avant d'y placer nos comportements.
Nous avons ensuite enregistré celle-ci dans le dossier
ad hoc et relancé Director.
Et voilà le travail.
|