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.
<
Sommaire
|