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.