Société de service et de conseil multimédia en Suisse romande. Prossel Software offre des services de création, conception, réalisation, développement et programmation pour sites Web et applications mobiles.

Nous sommes basés à Senarclens, près de Cossonay, entre Yverdon-les-Bains et Lausanne, dans canton de Vaud depuis 2020. Auparavant, nous étions à Cheyres, dans le canton de Fribourg.

Domaines d'activité: bases de données, programmation, développement, informatique, php, mysql, React, Symfony, WordPress.

spacer spacer spacer spacer spacer
 
Prossel Software - Programmation et développement - Conception et réalisation de sites Internet, CD-ROM et DVD-ROM
spacer

Accueil

Services

spacer

Programmation

Sites Internet

Produits

spacer

DynPage

FormEZ

Relocator

Réalisations

Ressources

Contact

spacer
spacer

 

Prossel Software - Accueil
spacer
spacer spacer spacer
Vous êtes ici:
Accueil > Ressources > Communication inter-sprites
spacer

Communication inter-sprites

Par Pierre Rossel

Dernière mise à jour le 28 mars 2000.

Cet article n'est pas une traduction, mais il est largement inspiré d'une publication en anglais de James Newton sur le même sujet.

 

1. Introduction

Il y a communication inter-sprites quand le comportement d'un sprite agit sur un ou plusieurs autres sprites. Par exemple, le clic sur un bouton qui déplace des objets sur la scène. Le codage "en dur" permet de développer rapidement une animation avec Director qui nécessite de faire communiquer des sprites entre eux. Coder "en dur" dans le cas de la communication inter-sprites signifie que le numéro du sprite sur lequel on veut agir est directement écrit dans le code. Tôt ou tard, cette manière de faire finira par poser problème. En effet, dès qu'il faudra déplacer un sprite sur une autre piste du scénario, il sera aussi nécessaire d'ajuster son numéro dans le code Lingo. Cela devient vite fastidieux et difficile à maintenir quand l'animation devient plus conséquente.

Nous allons examiner quelques techniques de communication inter-sprites pour montrer leurs avantages et inconvénients. On verra qu'il est possible d'écrire relativement simplement des comportements qui sont indépendants de la position des sprites dans le scénario.

2. N° de sprite en dur dans le Lingo

Donner le numéro du sprite que l'on veut manipuler directement dans le code lingo est la méthode la plus rapide, mais aussi la pire. Dans l'exemple 1, les comportements des deux flèches envoient chacun un message au sprite numéro 1.

-- Comportement de la flèche haut:
on mouseDown me

  sendsprite
1, #Monte
end

La balle qui est le sprite 1 traite ces messages en changeant son opacité.

-- Comportement de la balle:
on
Monte me
  sprite
(me.spritenum).blend = min(sprite(me.spritenum).blend + 10, 100)
end

Exemple 1: sendSprite 1 (source)

S'il fallait maintenant ajouter une image de fond à notre animation, il faudrait décaler nos 3 sprites vers le bas, ce qui fait que la balle se trouverait par exemple en 2. Sans rien toucher au code, l'animation s'exécute toujours. Par contre étant donné que les messages sont envoyés au sprite 1, la balle ne les reçoit plus. Le comportement de la balle doit donc être adapté au nouveau numéro de sprite de la balle pour que l'animation continue de fonctionner.

Le fait qu'aucune erreur ne soit générée lorsque l'on clique sur les flèches peut être considéré comme un avantage ou un inconvénient. Un avantage car justement l'animation continue de s'exécuter, mais un inconvénient car on ne se rend pas forcément compte que quelque chose ne fonctionne plus comme prévu.

On peut palier à cet inconvénient en utilisant la syntaxe:

-- Comportement de la flèche haut:
on mouseDown me

  Monte sprite 1

end

Si le gestionnaire Monte n'est pas défini pour le sprite 1, Director génère une erreur (Handler not defined) et l'animation s'arrête.

3. N° de sprite dans un paramètre de comportement

Pour éviter d'avoir le numéro de sprite dans le lingo, on peut le définir en tant que propriété du comportement. Modifions le comportement d'une flèche pour qu'il utilise une propriété contenant le numéro du sprite à piloter.

property iSpriteToCall

on
getPropertyDescriptionList
  return
[ #iSpriteToCall [ #comment "Sprite à appeler",¬
  #format #integer
, #default 1 ]]
end


on
mouseDown me
  sendsprite
iSpriteToCall, #Monte
end

Maintenant, pour configurer le numéro de sprite qui doit recevoir le message, il suffit de sélectionner le sprite sur la scène, ouvrir l'inspecteur de comportement et modifier le numéro dans la liste de propriété. Ca fait déjà plus professionnel, mais ça ne résout pas le problème du changement de position du sprite, ça ne fait que le déplacer. Il faut toujours aller mettre à jour le numéro quelque part. Au lieu d'aller dans le code, il faut aller dans le dialogue des paramètres du comportement. Maigre consolation, le même comportement peut être utilisé pour plusieurs instances devant envoyer le même message à des sprites différents, sans avoir à récrire un script pour chaque numéro.

4. SendAllSprites: envoi du message à tous les sprites

Pour ne pas avoir à se soucier du numéro du sprite qui doit recevoir le message, on peut l'envoyer à tous les sprites. Le sprite concerné traite le message, les autres l'ignorent.

on mouseDown me
  sendAllSprites #
Monte
end

De cette manière, quelque soit l'emplacement du sprite qui doit traiter le message, il le recevra. Voilà donc déjà une nette amélioration de la communication. Mais attention, cette manière de faire n'est pas sans inconvénient. En effet, si chaque sprite d'une animation envoie en message à tous les autres lors de chaque entrée dans une image, cela peut faire beaucoup de messages et influencer la vitesse de déroulement de l'animation. Le nombre de messages envoyés est proportionnel au carré du nombre de sprites et au nombre de comportements par sprite.

Illustrons ce principe avec la métaphore suivante: Une équipe de nettoyage se répartit les bureaux d'une grande entreprise. Chaque nettoyeur est assigné à un bureau. Une fois que le travail à commencé, le chef, qui est resté à la réception, doit rappeler un de ses employé (appelons le A) pour une raison quelconque. Il décide d'utiliser le téléphone, car chaque bureau en est équipé. Il appelle chaque numéro, donne le message suivant: "Si tu est A, reviens à la réception" et raccroche. A finit par recevoir le message et revient. Un peu plus tard, c'est au tour de B d'être demandé par son chef. Celui-ci n'a donc plus qu'à recommencer ses téléphones. On a bien compris que cette méthode n'est pas très efficace. Nous allons voir comment l'améliorer.

5. Call sur liste de pointeurs de scripts

Continuons avec notre histoire de nettoyeurs. Le chef peut faire une seule fois sa série de téléphone. En demandant le nom de la personne qui répond, il établit une liste gardant la relation nom-numéro de téléphone. Ensuite, lorsqu'il doit atteindre quelqu'un il n'a plus qu'à consulter sa liste et appeler directement le bon numéro.

Pour traduire ce principe de liste en Lingo, nous allons envoyer, depuis le beginSprite de la flèche haut, un message d'initialisation à tous les sprites, ainsi qu'une liste vide.

-- Comportement de la flèche haut
property
m_lstBalles


on
beginSprite me
  m_lstBalles = []

 
SendAllSprites #InitListBalles, m_lstBalles
end

En réponse à ce message, le comportement de la balle s'ajoute à la liste.

-- Comportement de la balle
on
InitListBalles me, lstBalles
  lstBalles.add(me)
end

Lorsque la flèche doit envoyer un message à la balle elle n'a plus qu'à le faire pour les membres de sa liste de balles.

on mouseDown me
  call #
Monte, m_lstBalles
end

Nous avons introduit ici plusieurs éléments nouveaux. Étudions les un par un.

Dans le cas d'un sendAllSprites, le gestionnaire du message ne peut pas utiliser le mot clé return pour retourner une valeur à l'appelant. En effet, le message est envoyé à tous les sprites. Si chacun retourne une valeur, que devrait retourner la fonction sendAllSprites ? En fait son comportement n'est pas défini, on ne peut donc pas utiliser le retour de fonction pour situer la balle. 

La propriété m_lstBalles du comportement de la flèche garde la liste des balles qui désirent recevoir le message de la flèche. Cette propriété est initialisée avec une liste vide dans le beginSprite. Pourquoi une liste ? Pour l'expliquer, il faut comprendre comment fonctionnent les passages de paramètres en Lingo.

5.1. Paramètres par valeur

Plusieurs langages de programmation, (tels que C, C++, Pascal, Visual Basic, ...) offrent la possibilité de passer les paramètre par valeur ou par référence. En Lingo tous les paramètres sont passés par valeur.

Dans le passage de paramètre par valeur, comme son nom l'indique, c'est une valeur qui est passée à la fonction. Même si on donne un nom de variable à l'appel, ce n'est qu'une copie de son contenu (la valeur) qui est reçue par le destinataire du message. Si celui-ci modifie la valeur de la variable reçue, il ne fait que de modifier sa copie. La variable utilisée à l'appel n'est pas modifiée.

Considérons le script d'animation suivant:

on test
  var1 = 10
  put
"Début du test " & var1
  fonction1 var1
  put
"fin du test " & var1
end

 
on
fonction1 param1
  put
"entrée de fonction1 " & param1
  param1 = param1 * 2
  put
"sortie de fonction1 " & param1
end

Dans la fenêtre de message, on appelle la fonction test et on obtient le résultat suivant:

test
-- "Début du test: 10"
-- "entrée de fonction1: 10"
-- "sortie de fonction1: 20"
-- "fin du test: 10"

On constate bien que la variable var1 contient toujours la valeur 10 après le retour de fonction1 alors qu'elle été modifiée dans la fonction. Une fonction ne peut donc pas utiliser ses paramètres pour retourner une valeur à son appelant. 

5.2. Cas spécial: les listes

Une variable peut contenir une liste. Si cette variable est copiée dans une autre, il n'y a toujours qu'une liste en mémoire, mais les deux variables permettent de la modifier. Dans le cas des listes, les variables sont en fait des pointeurs. Exemple:

uneListe = [1, 2, 3]
uneAutre = uneListe
uneAutre.Add(4)
put uneListe
-- [1, 2, 3, 4]

Si on passe en paramètre une variable contenant une liste, on ne passe pas vraiment la liste, mais son pointeur. Il n'existe toujours qu'une liste, mais deux pointeurs: l'original et la copie passée à fonction. Celle ci peut modifier la liste grâce à sa copie du pointeur. Les modifications de la liste depuis l'intérieur de la fonction agissent sur la même liste qui est visible à l'extérieur de la fonction.

On comprend mieux maintenant, pourquoi on passe un paramètre de type liste avec le message #InitListBalles.

5.3. Me: instance de script

Le comportement de la balle réagit au message #InitListBalles en ajoutant la variable me à la liste des balles. Le paramètre me est automatiquement renseigné par Director lorsqu'on envoie un message avec SendSprite, SendAllSprites ou call. Il s'agit encore une fois, comme pour une liste, d'un pointeur. Pour un comportement, le me représente l'instance du script qui est associé au sprite.

5.4. Fonction call avec une liste

Le comportement de la flèche envoie son message à la liste de balles avec la fonction call. La fonction call permet d'envoyer un message à une instance de script ou à une liste d'instances de scripts. Dans notre cas, la liste de balles contient un pointeur sur l'instance du comportement Balle du sprite de la balle.

5.5 Résumé

Grâce à un seul SendAllSprites et à la liste des balles, nous pouvons maintenant n'envoyer plus qu'un seul message à la balle au lieu d'inonder de messages tous les sprites de l'animation. Il y a cependant un problème si la balle est située sur une piste plus élevée que la flèche. Dans ce cas, lorsque la flèche envoie son message d'initialisation depuis son beginSprite, la balle n'existe pas encore et ne peut donc pas répondre au message. La liste de balles reste vide et l'animation ne fonctionne pas.

6. L'aller-retour

Nous avons vu que lorsqu'un sprite apparaît sur la scène, le gestionnaire beginSprite est appelé. Il peut envoyer un message à tous les sprites avec la fonction SendAllSprites, mais seuls les sprites dont le beginSprite a déjà été appelé peuvent répondre au message. Si un lien doit être établi avec un sprite qui n'a pas encore été initialisé, il faut prévoir un double mécanisme. Le premier si le sprite à atteindre est situé sur une piste inférieure et le deuxième s'il se situe sur une piste supérieure.

Dans notre exemple, il faut qu'une balle initialisée après la flèche lui envoie un message pour lui signaler sa présence. On ajoute un SendAllSprites au comportement de la balle:

-- Comportement de la balle

on beginSprite me

  -- Permet à la flèche d'acquérir une référence sur la balle
 
SendAllSprites #EnregistreBalle, me
end

La flèche peut ainsi ajouter la balle "retardataire" à sa liste:

-- Enregistre les balles "retardataires"
on
EnregistreBalle me, uneBalle
  m_lstBalles.add(uneBalle)
end

Nous avons maintenant tenu compte des 2 cas possibles, selon qu'un sprite est initialisé avant ou après l'autre, et notre animation fonctionne de nouveau correctement, quelque soit l'emplacement des sprites dans le scénario.

L'exemple 2 illustre l'établissement de la communication grâce au SendAllSprites depuis le beginSprite, l'utilisation d'une liste pour garder des références sur des instances de scripts et l'aller-retour pour terminer l'initialisation dans tous les cas.

Exemple 2: Aller-retour (source)

Il y a quand même un inconvénient: il a fallu écrire 2 fois du code pour arriver au même résultat. Ce n'est peut-être pas grand chose de plus, mais pour la maintenance du code, il vaudrait mieux l'éviter. En effet, si quelque chose devait être modifié, il faudrait le faire à deux endroits et le risque d'en oublier une partie n'est pas négligeable. L'animation pourrait très bien fonctionner, jusqu'au jour où l'un des protagonistes doit être déplacé sur le scénario, ce qui mettrait en évidence la correction manquante dans l'autre partie du code.

Alors comment faire pour éviter cette redondance de code ? La réponse est simple: attendre que tous les sprites soient initialisés pour établir les liens entre eux.

7. Retarder l'initialisation

Il y a plusieurs possibilités pour retarder l'initialisation des liens entre les sprites. Le but étant d'attendre que tous les sprites aient été initialisés, on peut attendre le premier prepareFrame, enterFrame, exitFrame, stepFrame, ou même la première fois qu'il est nécessaire de communiquer. Le principe est le même à chaque fois: tester si l'initialisation à déjà eu lieu et initialiser si ce n'est pas le cas.

7.1. Attendre le premier prepareFrame

Tant que l'initialisation n'a pas été faite, la liste a la valeur void. Lors des passages suivants dans la fonction prepareFrame, la liste existe et il n'y a plus rien à faire.

-- Comportement de la flèche haut
property
m_lstBalles

on
prepareFrame me
  if
voidP(m_lstBalles) then
    m_lstBalles = []
    SendAllSprites #
InitListBalles, m_lstBalles
  end if
end
 
on mouseDown me

 
call #Monte, m_lstBalles
end

La balle n'a plus qu'à répondre au message d'initialisation:

-- Dans le comportement de la balle
on
InitListBalles me, lstBalles
  lstBalles.add(me)
end


on
Monte me
  valeur = sprite(me.spritenum).blend
  valeur = valeur + 10
  if
valeur > 100 then valeur = 0
  sprite
(me.spritenum).blend = valeur
end

Cette manière de faire est probablement celle qui a le meilleur rapport efficacité/complexité pour établir des liens entre des sprites.

On a cependant introduit un test supplémentaire qui est effectué lors de chaque changement d'image. Dans l'immense majorité des cas d'application, ce ne sera jamais un problème et l'effet peut être complètement ignoré. Pour la beauté de l'art, nous allons encore voir qu'il est possible de le supprimer.

7.2. Utiliser the actorList

Sans entrer dans les détails, un script peut s'ajouter dans une liste spéciale définie par Director: the actorList. Les objets dans actorList reçoivent un message stepFrame à chaque passage de la tête de lecture sur une image.

Le comportement suivant utilise cette propriété pour, lors du premier stepFrame, envoyer un message d'initialisation au sprite auquel il est affecté. Une fois sa tâche accomplie, il se supprime lui-même de la liste pour ne plus recevoir les messages suivants. De cette manière il n'y a plus besoin de tester si l'initialisation a été faite. Le comportement se supprime aussi de la liste des comportements du sprite, même si cela n'est pas indispensable. A noter: l'astuce qui permet de ne pas perdre un message quand un script est supprimé de scriptInstanceList.

-- Le sprite qui possède ce comportement reçoit
-- le message #InitSprite dès que tous les sprites
-- présents sur l'image sont initialisés. Le but
-- est de pouvoir à ce moment envoyer un message
-- à tous les sprites avec un SendAllSprites.
--
-- Ecrit par Pierre Rossel le 23 mars 2000 pour illustrer
-- un article sur la communication inter-sprites

property
spriteNum

on
beginSprite me
  (the actorlist).add(me)
end


on
endSprite me
  -- Au cas ou on est encore dedans (Par exemple en cas
  -- d'erreur en mode auteur avant le prepareFrame)

  (the actorlist).deleteone(me)
end


on stepFrame me
  -- C'est maintenant qu'on envoie le message à tous les
  -- comportements du sprite
  call
(#InitSprite, sprite(spriteNum).scriptInstanceList)
end


on
prepareFrame me

  -- Pour ne plus recevoir les prochains #stepFrame
  (the actorlist).deleteone(me)

  -- Le fait d'enlever un script de la liste empèche le
  -- suivant de recevoir son message. Donc on l'envoie nous même.
  lst = sprite(spriteNum).scriptInstanceList
  pos = lst.getPos(me)
  lst.deleteAt(pos)
  if
pos <= lst.count then
    call
(#prepareFrame, [lst[pos]])
  end if
end

Si le comportement InitSprite est ajouté à la flèche, son comportement flèche peut se simplifier ainsi:

-- Comportement de la flèche haut
property
m_lstBalles

-- Message envoyé par le comportement InitSprite
on
InitSprite me
  m_lstBalles = []
  SendAllSprites #
InitListBalles, m_lstBalles
end


on mouseDown me

 
call #Monte, m_lstBalles
end

8. Conclusion

Faire communiquer les sprites entre eux n'est pas très compliqué. Le moyen le plus simple est à éviter car il rend le code Lingo dépendant de la position des sprites sur le scénario. Avec un minimum d'effort, il est possible de s'affranchir de cette dépendance.

Le tableau suivant résume les avantages et inconvénients de chaque méthode. Celles qui  sont dépendantes d'un numéro de sprite ont été volontairement omises.

SendAllSprites chaque fois que nécessaire

Simple

Pas efficace s'il doit être effectué souvent avec beaucoup de sprites sur la scène.

SendAllSprites depuis le beginSprite pour remplir une liste de scripts, puis utilisation de la fonction call

Efficace pour appels fréquents

Nécessite du code à double pour tenir compte de tous les cas.

SendAllSprites depuis le prepareFrame pour remplir une liste de scripts, puis utilisation de la fonction call

Efficace pour appels fréquents

Nécessite un test à chaque prepareFrame

SendAllSprites depuis le premier stepFrame pour remplir une liste de scripts, puis utilisation de la fonction call

Efficace pour appels fréquents

Aucun


Copyright © Prossel Software depuis 2001 - www.prossel.com

spacer
spacer
spacer        
Prossel Software - Accueil