Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Proportionnalité pratique en Actionscript: Plus loin que la Règle de Trois

Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Par lilive (olivier tarasse), le 28 septembre 2011

Introduction

En programmant, on rencontre souvent la situation suivante (faites glisser les curseurs de l'animation ci-dessous):

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Si vous ne l'avez pas reconnue, laissez-moi le temps de vous la décrire avant de vous montrer des exemples plus convaincants:

  • Une variable numérique A peut prendre des valeurs comprises entre les valeurs A1 et A2. Dans l'exemple ci-dessus A1 vaut 20 et A2 150.
  • Une autre variable numérique B peut prendre des valeurs comprises entre les valeurs B1 et B2. Dans l'exemple B1 vaut 400 et B2 800.
  • A et B sont liées dans leurs variations, c'est-à-dire prennent en même temps leurs valeurs extrêmes, et parcourent “en parallèle”, ou “à la même vitesse” leur plage de valeurs possibles.
  • Quand une des deux variables change, l'autre variable doit changer aussi en accord avec la relation ci-dessus. Vous pouvez modifier avec la souris la valeur de A ou de B, l'autre variable s'ajustera automatiquement.

En pratique, on sera le plus souvent dans la situation de connaître les valeurs de A1, A2, B1, B2, de connaître la valeur de A et de chercher à calculer celle de B. Cela ne vous parle pas encore? Alors autres exemples:


Trois cas de figure

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Il y a des chances que vous ayez déjà été confronté à quelque chose de similaire. Si parfois vous cafouillez un peu à trouver le petit calcul nécessaire pour passer de A à B, ce tutoriel est pour vous.

Pour pouvoir suivre ce tutoriel vous aurez besoin de connaître quelques rudiments d'Actionscript, comme la manipulation des objets d'affichage, l'utilisation des variables, et l'écoute des évènements.


Une première formule

Pour commencer nos explications, oublions nos variables A et B et occupons-nous d'une variable X qui peut prendre une valeur entre 0 et MAX.

Exemple: MAX = 200, X = 100

on peut dire que X est à “la moitié” du chemin à faire entre 0 et MAX
On voit que si on calcule X / MAX on obtient 0.5
0.5 s'écrit d'ailleurs aussi 1/2
1/2 qui se lit “un demi”, est qui veut bien dire “la moitié” (et qui distinctement prononcé à haute voix à la terrasse d'un café aura certainement un résultat).

Autre exemple: MAX = 200, X = 50

Cette fois x est “au quart” du chemin à faire entre 0 et MAX
Si on calcule X / MAX on obtient 0.25
0.25 c'est aussi 1/4, qui se lit “un quart”, c'est bien ça.

Ce nombre qu'on calcule, X / MAX, c'est “le rapport” auquel est placé X entre 0 et MAX.
Ce rapport, nommons-le “ratio”, se calcule ainsi:

ratio = X / MAX

Comme X est compris entre 0 et MAX, ratio est toujours compris entre 0 et 1
Plus ratio est petit, plus X est proche de 0
Plus ratio est grand, plus X est proche de MAX
Si ratio est 0 alors X = 0
Si ratio est 1 alors X = MAX

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Inversement, si on a les valeurs MAX et ratio et qu'on cherche à calculer X, il suffit de faire:

X = MAX * ratio

Exemple: MAX = 200 et ratio = 0.5
X = 200 * 0.5 = 100

Exemple: MAX = 200 et ratio = 0.25
X = 200 * 0.25 = 50

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Voilà, nous avons ce qu'il nous faut pour réaliser la barre de progression du premier exemple:


Application pratique de la première formule

La façon de s'y prendre

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Prenons comme variable X le nombre d'octets chargés. Pour être plus explicite nous allons l'appeler bytesLoaded plutôt que X.

Ici, la valeur maximale MAX est 2134. Posons la variable bytesTotal = 2134

Nous pouvons calculer ratio:

ratio = bytesLoaded / bytesTotal;

On veut maintenant que la barre de progression se remplisse en même temps.

Disons que la barre est un rectangle de 260 pixels, comme dans l'exemple ci-dessus. Cette fois, prenons comme variable X la largeur du rectangle de remplissage, et appelons-la barWidth. Sa valeur MAX est donc 260. Plutôt que MAX, appelons-la barMaxWidth.

On veut:
Que quand le nombre d'octets bytesLoaded est à 0, la barre soit vide (que barWidth vaille 0).
Que quand le nombre d'octets bytesLoaded est égal à bytesTotal, la barre soit pleine (que barWidth vaille barMaxWidth).
Que quand le nombre d'octets bytesLoaded est à la moitié, la barre soit pleine à moitié.
C'est-à-dire que bytesLoaded et barWidth ont le même ratio.

Comme nous avons déjà calculé ratio à partir de bytesLoaded et bytesTotal, nous pouvons calculer barWidth en faisant:

barWidth = barMaxWidth * ratio;

C'est aussi simple que ça.


La réalisation

Voici le tout mis en application pour réaliser la barre de préchargement d'une animation faite dans Flash Professional CS4. Ce code fonctionne si on le met sur la première image du scénario, et si on place sur la scène un symbole de clip nommé bar qui représente le remplissage de la barre à son maximum:

// On stoppe la lecture de l'animation jusqu'à ce que le chargement soit terminé
stop();    
 
// On mémorise la largeur maximale que peut prendre la barre de progression
var barMaxWidth:int = bar.width;
// Et on la met à 0 puisque le chargement commence à peine
bar.width = 0;
 
// On met en place les écouteurs qui vont permettre de suivre le chargement
loaderInfo.addEventListener(ProgressEvent.PROGRESS, progressHandler);
loaderInfo.addEventListener(Event.COMPLETE, completeHandler);
 
// Au fur et à mesure que le chargement avance
function progressHandler(pEvt:ProgressEvent):void {
 
	// On calcule le ratio à partir des octets
	var ratio:Number = pEvt.bytesLoaded / pEvt.bytesTotal;
 
	// On calcule la largeur de la barre à partir du ratio
	bar.width = barMaxWidth * ratio;
}
 
// A la fin du chargement
function completeHandler( event:Event ) {
 
	// On se débarrasse des écouteurs
	loaderInfo.removeEventListener(ProgressEvent.PROGRESS, progressHandler) ;
	loaderInfo.removeEventListener(Event.COMPLETE, completeHandler) ;
 
	// Et on passe à la suite de l'animation
	gotoAndPlay(2);
}
Voici le .fla pour CS4 à télécharger: barre-de-progression.fla
Pensez à utiliser la fonction “Simuler le téléchargement” pour admirer le résultat.


Et la règle de trois dans tout ça?

Certains d'entre vous se sont peut-être dit qu'on aurait pu se passer de ce raisonnement avec le ratio, et appliquer une simple règle de trois. C'est absolument vrai, puisque nos 2 variables prennent en même temps la valeur 0 et que nous sommes donc avec deux valeurs proportionnelles. Le résultat serait le même (heureusement), puisque le calcul

ratio = bytesLoaded / bytesTotal;
barWidth = barMaxWidth * ratio;

peut se réduire en une ligne:

barWidth = barMaxWidth * bytesLoaded / bytesTotal;

Et c'est cette formule que nous aurions directement trouvée en appliquant la règle de trois. L'avantage de passer par le calcul d'un ratio, outre le fait d'amener une autre vision (donc compréhension) de la chose, est que nous pourrons nous débrouiller si les 2 variables ne prennent pas en même temps la valeur 0. C'est ce qui se passe dans les 2 autres exemples d'animations donnés au début, et que nous allons maintenant traiter.


Formule générale

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Dans cet autre exemple on ne peut plus appliquer notre première formule, car les variables A et B ne sont pas à 0 en même temps. Elle ne sont pas proportionnelles, au sens strict. Si nous essayons de faire comme auparavant:

ratio = A / A2
B = B2 * ratio

Nous aurons ceci:

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Quand le carré est à gauche, la transparence B n'est pas de 0.2 comme nous le voulions, mais de 0.12. D'ailleurs cela aurait été étonnant que ça marche, puisque notre calcul ne fait intervenir ni la valeur de A1, ni celle de B1.


De la variable au ratio

N'abandonnons pas notre ratio pour autant. L'idée est bonne: un nombre entre 0 et 1 qui nous indique à quel rapport se situe la variable A entre ses 2 extrêmes A1=30 et A2=260.

Pour nous ramener à une situation connue, nous allons poser

X = A - A1

Quand A prend sa valeur minimale A1=30, X vaut 30-30 c'est-à-dire 0.
Quand A augmente, X augmente.
Quand A approche de sa valeur maximale A2=260, X approche de sa valeur maximale MAX.

MAX = A2 - A1 = 260 - 30 = 230

Nous sommes revenu à la situation d'une variable X naviguant entre 0 et MAX, et nous pouvons calculer le ratio:

ratio = X / MAX

C'est-à-dire, en remplaçant X et MAX par leur formule:

ratio = (A - A1) / (A2 - A1)

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Nous obtenons bien un ratio entre 0 et 1, tout va bien.


Du ratio à la variable

Maintenant nous allons devoir calculer l'opacité du carré à partir du ratio. Nous voulons que cette opacité B varie entre B1=0.2 et B2=1

Nous pouvons voir B comme la somme de 0.2 et d'une valeur X qui varierait entre 0 et 0.8. Cette valeur MAX=0.8 s'obtient par la formule:

MAX = B2 - B1
MAX = 1 - 0.2 = 0.8

Nous pouvons calculer X d'après le ratio trouvé:

X = ratio * MAX
X = ratio * (B2 - B1)
X = ratio * 0.8

Puis pour calculer l'opacité B nous n'avons plus qu'à rajouter B1:

B = X + B1

Sur une seule ligne cela donne:

X = ratio * (B2 - B1)
B = X + B1
Soit
B = ratio * (B2 - B1) + B1


Synthèse: D'une variable à l'autre

Pour calculer la valeur de B à partir de celle de A nous devons donc faire:

ratio = (A - A1) / (A2 - A1)
B = ratio * (B2 - B1) + B1

C'est toujours la même logique. On trouve à quel rapport se situe la 1ère variable dans son intervalle de valeurs possibles, puis on affecte à la 2eme variable la valeur située au même rapport dans son propre intervalle.

Nous pouvons si nous voulons créer une fois pour toute la fonction qui fera le travail:

function mapValue(a:Number, a1:Number, a2:Number, b1:Number, b2:Number):Number {
	var ratio:Number = (a - a1) / (a2 - a1);
	return ratio * (b2 - b1) + b1;
}

Et en voici une variante plus compacte qui fait l'économie de la création de la variable temporaire ratio:

function mapValue(a:Number, a1:Number, a2:Number, b1:Number, b2:Number):Number {
	return (a - a1) / (a2 - a1) * (b2 - b1) + b1;
}


Exemple

Voici le code de l'animation d'exemple. square est l'occurrence du symbole de clip qui contient le dessin du carré:

// Valeurs extrêmes de l'abscisse du carré
var xMin:int = 30;
var xMax:int = 260;
 
// Valeurs extrêmes de l'opacité
var alphaMin:Number = 0.2;
var alphaMax:Number = 1;
 
// Rectangle dans lequel on autorise le carré à évoluer
var bounds:Rectangle = new Rectangle(xMin, 30, xMax - xMin, 140)
 
// On appelle une 1ere fois la fonction qui va définir l'opacité du carré d'après sa position
adjustAlpha();
 
// Et on écoute le clic sur le carré
square.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
 
// Quand le bouton de la souris est enfoncé sur le carré
function mouseDownHandler(e:MouseEvent):void {
 
	// On démarre le drag dans le rectangle de déplacements autorisés
	square.startDrag(false, bounds);
 
	// On écoute le déplacement de la souris et le relâchement du bouton
	stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
	stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
}
 
// Quand le bouton est relâché
function mouseUpHandler(e:MouseEvent):void {
 
	// On arrête le déplacement
	square.stopDrag();
 
	// On enlève les écouteurs du déplacement et du relâchement
	stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
	stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
}
 
// Quand la souris se déplace pendant le drag
function mouseMoveHandler(e:MouseEvent):void {
	// On demande à définir l'opacité du carré d'après sa position
	adjustAlpha();
}
 
// Calcule l'opacité du carré d'après sa position
function adjustAlpha():void {
 
	var ratio:Number = (square.x - xMin) / (xMax - xMin);
	square.alpha = ratio * (alphaMax - alphaMin) + alphaMin;
 
}
Voici le .fla pour CS4 à télécharger:
carre-mobile.fla


Bonus: Une classe toute faite

Suivant les idées de Dldler et de goabonga, voici une classe permettant de passer de A à B de plusieurs façons (voir les exemples fournis).



Miracles divers

Ce qui est beau avec les maths, c'est qu'il arrive que le résultat de nos recherches dépasse nos espérances. Mais si mais si, vous allez voir.

Nous avons raisonné à partir de valeurs d'exemples comme A1=30, A2=260, B1=0.2 et B2=1.
Cela nous aidait à y voir plus clair. Et nous en avons déduit une formule:

ratio = (A - A1) / (A2 - A1)
B = ratio * (B2 - B1) + B1

Le raisonnement s'appuyait sur des notions intuitives, valides dans la mesure où nous avions des valeurs positives, et que A1 était inférieur à A2, ainsi que B1 inférieur à B2. Relisez les parties précédentes en prenant des exemples où ces valeurs peuvent être négatives, ou bien rangées dans l'autre sens, et vous verrez que la justesse du propos se fait largement moins évidente.

Le miracle, c'est que les formules que nous avons trouvées ainsi restent valides quelques soient les valeurs de A1, A2, B1 et B2. Dans tous les cas, si nous appliquons ces formules, le résultat sera correct, et les deux variables A et B prendrons en même temps leurs valeurs A1 et B1, ainsi que leurs valeurs A2 et B2.


Le miracle de l'inversion des valeurs

Essayez. Reprenons le code ci-dessus:

var xMin:int = 30;
var xMax:int = 260;
var alphaMin:Number = 0.2;
var alphaMax:Number = 1;
 
function adjustAlpha():void {
	var ratio:Number = (square.x - xMin) / (xMax - xMin);
	square.alpha = ratio * (alphaMax - alphaMin) + alphaMin;
}

Et changeons le nom des variables:

var x1:int = 30;
var x2:int = 260;
var alpha1:Number = 0.2;
var alpha2:Number = 1;
 
function adjustAlpha():void {
	var ratio:Number = (square.x - x1) / (x2 - x1);
	square.alpha = ratio * (alpha2 - alpha1) + alpha1;
}

Vous pouvez donner toutes les valeurs que vous voulez aux extrêmes, cela fonctionnera encore: quand le carré sera en x1, son opacité sera alpha1, quand il sera en x2 son opacité sera alpha2. Par exemple:

var x1:int = 30;
var x2:int = 260;
var alpha1:Number = 1;
var alpha2:Number = 0.2;

Ou ce qui reviendra au même:

var x1:int = 260;
var x2:int = 30;
var alpha1:Number = 0.2;
var alpha2:Number = 1;

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Le troisième exemple donné tout en haut du tutoriel utilise lui aussi des valeurs inversées.


Le prodige de l'inversion des signes

Voici maintenant une variante qui utilise des valeurs négatives:

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

La rotation du carré se fait d'après l'abscisse du pointeur de la souris:

// Valeurs entre lesquelles on prend l'abscisse du pointeur en compte
var x1:int = 80;
var x2:int = 210;
 
// Valeurs extrêmes de la rotation
var alpha1:Number = 30;
var alpha2:Number = - 30;
 
// On appelle une 1ere fois la fonction qui va définir la rotation du carré d'après la position de la souris
adjustRotation();
 
// Et on écoute le déplacement du pointeur
stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
 
// Quand la souris se déplace
function mouseMoveHandler(e:MouseEvent):void {
	// On demande à définir la rotation du carré d'après la position du pointeur
	adjustRotation();
}
 
// Calcule la rotation du carré d'après la position du pointeur
function adjustRotation():void {
 
	// On calcule la position du pointeur bornée entre x1 et x2
	var x:Number = mouseX;
	if (x < x1) x = x1;
	if (x > x2) x = x2;
 
	// Puis on calcule la rotation
	var ratio:Number = (x - x1) / (x2 - x1);
	square.rotation = ratio * (alpha2 - alpha1) + alpha1;
 
}
Le .fla pour CS4 à télécharger:
carre-rotation.fla


La magie de l'extension à l'au-delà

Un dernier rebondissement pour en finir: Les formules restent également valables au-delà des intervalles de valeurs définis par A1 et A2, et B1 et B2.

C'est-à-dire que A1, A2, B1, B2 ne sont pas obligatoirement des valeurs extrêmes pour les variables A et B. A et B peuvent en fait varier de moins l'infini à plus l'infini. Enfin, au bout d'un moment Flash protestera et vous parlera peut-être de dépassement de valeur, mais vous avez de la marge.

A quoi cela peut-il servir? Nous allons bien trouver un petit exemple. Hum…

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

J'appelle pad le rectangle gris “Survolez-moi”.

On sait que quand la souris est à gauche du pad, l'image doit aligner son bord gauche sur celui de la scène. Donc:

// Valeur 1 pour la variable A (mouseX)
var xp1:Number = pad.x;
// Valeur 1 pour la variable B (image.x)
var xi1:Number = 0;

On sait que quand la souris est à droite du pad, l'image doit aligner son bord droit sur celui de la scène. Donc:

// Valeur 2 pour la variable A (mouseX)
var xp2:Number = pad.x + pad.width;
// Valeur 2 pour la variable B (image.x)
var xi2:Number = - image.width + stage.stageWidth;

Cette fois nous ne nous contentons pas de faire varier image.x en fonction de mouseX, mais nous voulons également faire varier image.y en fonction de mouseY. Au final nous aurons donc:

// Bornes du pad
var xp1:Number = pad.x;
var xp2:Number = pad.x + pad.width;
var yp1:Number = pad.y;
var yp2:Number = pad.y + pad.height;
 
// Valeurs de calage de l'abscisse de l'image
var xi1:Number = 0;
var xi2:Number = - image.width + stage.stageWidth;
// Valeurs de calage de l'ordonnée de la photo
var yi1:Number = 0;
var yi2:Number = - image.height + stage.stageHeight;

Dans la fonction de placement, nous calculons la position de l'image en fonction des coordonnées du pointeur de la souris:

// On positionne l'image d'après la position de la souris
function mouseMoveHandler(e:Event):void {
 
	var ratio:Number;
 
	// En abscisse
	ratio = (mouseX - xp1) / (xp2 - xp1);
	image.x = ratio * (xi2 - xi1) + xi1;
 
	// En ordonnée
	ratio = (mouseY - yp1) / (yp2 - yp1);
	image.y = ratio * (yi2 - yi1) + yi1;
 
}

L'image est donc entièrement survolée quand on explore le pad avec le pointeur de la souris. Mais comme les coordonnées du pointeur peuvent prendre des valeurs qui vont au-delà de celles des limites du pad, le mouvement de l'image continue également, et elle sort de la scène, la laissant dégagée. De cette façon les autres éléments interactifs de l'animation sont bien visibles quand l'utilisateur en approche le pointeur.

Le .fla pour CS4 à télécharger: pad.fla
Le code de ce .fla varie légèrement de celui donné ci-dessus afin que le déplacement de l'image soit bien fluide, mais ceci est une autre histoire.



Conclusion

Nous avons maintenant une méthode sûre qui permet de faire évoluer proportionnellement deux variables, à partir du moment où nous connaissons deux valeurs possibles pour chaque variable. Ceci suffira à résoudre bon nombre de situations type qu'on rencontre en programmant pour Flash. Enfin… au niveau de la conception mathématique en tout cas.

N'hésitez surtout pas à venir laisser commentaires et questions au sujet de ce tutoriel sur le forum. Le lien pour accéder à la discussion idoine est juste en dessous. Vous pouvez aussi y partager vos propres exemples mettant en jeu la proportionnalité. Et si vous avez trouvé cette page utile, un simple merci fait toujours très plaisir, rassure sur le fait qu'on n'a pas écrit pour rien, et donne envie de persévérer.

Lilive.