Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox
Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Par Nataly, le 08 juin 2010

Introduction
Classe de base (#1)
Classe de base (#2)
Héritage et surcharge (#1)
Héritage et surcharge (#2)
Diffuser des événements
Classe liée
Classe de document
Classe externe : une visionneuse (#1)
Classe externe : une visionneuse (#2)
Classe externe : méthodes statiques

Classe de document

Et nous voilà arrivés à l'incontournable classe de document. Vous la connaissez déjà c'est sûr, ne serait-ce que de réputation, on en voit partout, à commencer par la doc.
Rassurez vous, ça va aller vite, on a déjà quasi tout vu quant aux classes, il ne reste que quelques point spécifiques à ce cadre d'utilisation à préciser.

Commençons par le début, il s'agit donc une classe destinée à être associée, non pas à un symbole, comme nous l'avons fait jusqu'alors mais au fichier (document) .fla lui même.
Dire “associer une classe au fichier” c'est un abus de langage, en vrai c'est “associer une classe au 'clip racine' de l'animation” qu'il faudrait dire. Je m'explique :
Il ne faut pas perdre de vue que le scénario principal n'est en fin de compte qu'un gros clip qui constitue l'animation elle même. Un gros clip donc, qui en contient d'autres ainsi que divers éléments (composants, champs texte, formes…). C'est ce clip qui vaut this (depuis une image du scénario principal) et c'est toujours ce clip qui est renvoyé par la propriété root de n'importe quel objet de l'animation. C'est un clip unique, une instance de MainTimeline. Tracez this si vous voulez vérifier.

[object MainTimeline]

Quand une animation (.swf) est lue dans le lecteur, ce clip scénario principal (mainTimeline) est ajouté à la scène (stage).
D'ailleurs vous pouvez le supprimer de la liste d'affichage :

stage.removeChild(this)

Ça ne sert à rien d'autre que tranquilliser les St Thomas ;)

Au même titre qu'on peut associer une classe à un clip de bibliothèque, on peut associer une classe au gros clip scénario-principal (main timeline en anglais). Cette casse répondra aux mêmes exigences syntaxiques que n'importe quelle classe et devra étendre la classe MovieClip ou Sprite selon que nous aurons recours ou non aux méthodes spécifiques de gestion de scénario ( stop, play…).

• Au même titre que, comme nous l'avons vu précédemment, tout ce qui pourrait s'écrire dans la première image d'un clip peut s'écrire dans une classe associée, tout ce qui peut s'écrire dans la première image du scénario principal peut être écrit dans sa classe associée en tant que classe du document.

Utiliser les exemples de la doc

Je vous propose d'expérimenter le principe à partir d'un cas fréquent : un paquetage illustrant un point quelconque de la documentation, ensuite on verra comment fabriquer nos propres classes de document, mais en fait vous savez déjà ;)

Imaginons que nous consultions la rubrique ColorTransform.
La page
Tout en bas de la page un exemple prêt à l'emploi :

package {
    import flash.display.Sprite;
    import flash.display.GradientType;
    import flash.geom.ColorTransform;
    import flash.events.MouseEvent;
 
    public class ColorTransformExample extends Sprite {
        public function ColorTransformExample() {
            var target:Sprite = new Sprite();
            draw(target);
            addChild(target);
            target.useHandCursor = true;
            target.buttonMode = true;
            target.addEventListener(MouseEvent.CLICK, clickHandler)
        }
        public function draw(sprite:Sprite):void {
            var red:uint = 0xFF0000;
            var green:uint = 0x00FF00;
            var blue:uint = 0x0000FF;
            var size:Number = 100;
            sprite.graphics.beginGradientFill(GradientType.LINEAR, [red, blue, green], [1, 0.5, 1], [0, 200, 255]);
            sprite.graphics.drawRect(0, 0, 100, 100);
        }
        public function clickHandler(event:MouseEvent):void {
            var rOffset:Number = transform.colorTransform.redOffset + 25;
            var bOffset:Number = transform.colorTransform.redOffset - 25;
            this.transform.colorTransform = new ColorTransform(1, 1, 1, 1, rOffset, 0, bOffset, 0);
        }
    }
}

Pour tester, comme précisé dans la doc elle même, il nous faudra un fichier as pour la classe et un fla auquel l'associer.


On note que la classe s'appelle ColorTransformExample, ce sera donc le nom du fichier as que nous enregistrerons dans le même répertoire que le fla de test.


Quant au fichier fla, pour préciser l'association il suffit de saisir le nom de la classe en question dans le panneau propriétés dans le champ Classe.

Si vous avez collé le code dans le fichier as et enregistré vous pouvez constater qu'à l'exécution du fla il se passe quelque chose.
C'est un bon début.

Quand ça va mal

Mais peut-être rencontrez vous déjà un problème, sous forme de message d'erreur à la compilation :
ColorTransformExample.as, ligne 1 1180: Appel à une méthode qui ne semble pas définie, addFrameScript.
Il suffit que vous ayez écrit une malheureuse ligne de code dans le fla, un simple trace, voire une ligne commentée, voire pire : quelques lignes vierges… Si, si, essayez :


Ça suffit à le faire crier !!

Que se passe-t-il ?
Regardons la définition de classe :

public class ColorTransformExample extends Sprite {

Elle étend la classe Sprite.
Un objet Sprite est similaire à un clip, mais ne possède pas de scénario. C'est la doc qui le dit, j'ajoute et précise que par conséquent il n'expose pas les méthodes et propriétés de gestion de scénario spécifiques à la classe MovieClip, telles play, stop et autre currentLabel. Une autre méthode spécifique à MovieClip c'est addFrameScript. Non documentée, elle permet d'ajouter du code dynamiquement à une une image. Le fait d'écrire dans le panneau action est manifestement traduit par l'appel à cette fameuse fonction, d'où le message d'erreur. Ce qui est plus surprenant, je vous l'accorde, c'est de constater que même une ligne vierge est comprise comme code… Bon, c'est comme ça, il faut le savoir voilà tout ;)

Pour se débarrasser du problème il suffit de prendre soin de ne rien écrire du tout dans le panneau action, ou d'étendre la classe MovieClip.

  public class ColorTransformExample extends MovieClip {

… et bien sûr d'importer la classe ;)

  import flash.display.MovieClip;

Ainsi, nous disposons de sources fonctionnelles qu'il suffit d'associer à un document .fla pour les tester, exactement comme si on avait récupéré un .fla avec du code. Vu qu'il est plus simple de fournir du texte qu'un .fla, on trouve très souvent des sources sous cette forme.

Comme par ailleurs il est souvent préconisé d'étendre la classe Sprite chaque fois qu'on n'a pas besoin du scénario (quand tout se passe dans une seule image), il n'est pas rare de trouver des sources sous forme d'un fichier .as étendant la classe Sprite. Si vous voulez ajouter votre propre code au fla, ou seulement y noter des infos sous forme de commentaires vous devrez modifier la-dite classe afin qu'elle étende MovieClip.

Ça devient vite pénible de devoir doubler les fichiers (le .fla plus le .as) à chaque fois qu'on a besoin de tester trois lignes. Rien ne vous empêche de prélever la fonction qui vous intéresse et de la coller dans le fla.
Par exemple ici, que fait le paquetage fourni par la doc ?
Il dessine un carré dégradé et quand on clique dessus la fonction clickHandler est invoquée et applique le fameux ColorTransform à this (vite dis).

        public function clickHandler(event:MouseEvent):void {
            var rOffset:Number = transform.colorTransform.redOffset + 25;
            var bOffset:Number = transform.colorTransform.redOffset - 25;
            this.transform.colorTransform = new ColorTransform(1, 1, 1, 1, rOffset, 0, bOffset, 0);
        }

On est bien d'accord que le scénario c'est this ? On l'a vu en intro, associer une classe à un document c'est en fait associer une classe au gros clip principal que constitue l'animation. Pour vous en convaincre dessinez une forme sur la scène et testez.
On constate que la couleur de cette forme elle aussi est modifiée au clic. Normal : c'est tout l'objet TimeLine qui fait l'objet du ColorTransform, donc tout ce qu'on pose sur la “scène”. Souvent on dit par abus de langage qu'on dessine une forme, ou qu'on pose un clip directement sur la scène. En fait il s'agit toujours du scénario, qui lui même est ajouté à la scène à la lecture.

Pour en revenir à nos moutons, dans le cas qui nous intéresse (tester la fonction clickHandler) on pourrait tout aussi bien la coller dans le fla et l'appeler au clic d'un bouton (voire de la scène pour les plus fainéants) :

stage.addEventListener(MouseEvent.CLICK, clickHandler);
 
function clickHandler(event:MouseEvent):void {
	var rOffset:Number=transform.colorTransform.redOffset+25;
	var bOffset:Number=transform.colorTransform.redOffset-25;
	this.transform.colorTransform=new ColorTransform(1,1,1,1,rOffset,0,bOffset,0);
}

Tout ce qu'on a dessiné sur le scénario - avec les couleurs de notre choix pour bien comprendre - sera modifié à chaque clic…
Pensez bien à supprimer l'attribut public devant la fonction, il n'a de raison d'être qu'au sein d'un paquetage. D'ailleurs le message d'erreur est on ne peut plus explicite :
1114: L'attribut public ne peut être utilisé que dans un package.

Créer sa propre Classe de document

Pourquoi décider d'écrire un fichier as plutôt que dans le fla ?

• Parce que vous avez envie.
• Pour donner les sources à quelqu'un facilement sur le forum ;-)
• Pour pouvoir éditer son code dans un éditeur digne de ce nom. (Je dénonce : c'est Lilive qui le précise).
• Parce que vous anticipez un moteur commun à plusieurs animations. Ça c'est le motif qui vaut que je développe un peu :
Imaginons que vous ayez à réaliser une série de quizz, chacun d'eux sous forme d'un .swf différent. Tous ces quizz auront en commun la gestion même du questionnaire : passer à la question suivante seulement si une réponse a été faite dans la question courante (sinon une jolie boite d'alerte modale ;)), calculer le score, avertir en cas de reprise avant la fin de la série de questions (autre jolie boite modale type Oui/Non : “Votre score est de x, voulez vous vraiment reprendre à zéro”), etc.
Mais chaque quizz aura ses particularités, des questions différentes pour commencer, mais aussi une charte graphique, des illustrations et autres fioritures…
Il pourrait être intéressant d'avoir une classe de document Quizz.as qui s'occupe de ce qui est commun, il n'y aurait plus qu'à l'associer à chaque nouveau .fla sans plus se préoccuper de quoique ce soit d'autre que du graphisme…

Gestion du scénario

Un petit exemple vite fait de classe de document, histoire de nous entrainer. Une classe destinée à une animation qui utilise le scénario, tant qu'à faire…

Imaginons image 1 du scénario un champ de saisie, si la saisie est valide, l'animation est lue (joli effet) et s'arrête sur la dernière image, là on affiche un truc reprenant la saisie.
L'exemple n'est destiné qu'à illustrer les quelques points qui ont à voire avec la gestion du scénario.

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

Il vous faut donc un .fla minimaliste avec un champ de saisie image 1, une zolie animation jusqu'à image 30 (par exemple) et sur cette image un nouveau champ texte dynamique.
Il vous faut aussi une classe destinée à être associée. Pour une fois je ne vais pas jouer la francophone forcenée, d'accord, on l'appelle main (mais maitre ça allait aussi ;)).

Les deux classes dans le même répertoire, comme d'habitude. Si vous souhaitez vous organiser autrement, maintenant vous savez faire. Si la classe est incluse dans paquetage, il faudra tout écrire :

Préparons le travail, le fichier .as :

// ******** Requiert **************
// btValide : Bouton - image 1
// txtPseudo : textField - image 1
// txtNom : TextField - dernière image
//******************************
 
package {
 
	import flash.display.MovieClip;
 
	public class main extends MovieClip {
 
		public function main():void {
                    trace("passage dans main")
 
		}
	}
}

Bien sûr, on étend MovieClip puisqu'on compte lire le scénario, et on importe la classe.

Ne confondez pas…

Une fois la classe main associée au .fla via le champ Classe du panneau de propriété, on peut tester… Ce n'est pas bien satisfaisant, ça boucle (normal on n'a rien fait) mais bon, ça ne crie pas et c'est un bon début. Et puis aussi vous constatez que la fonction constructeur n'est invoquée qu'une fois. Je disais en introduction que tout ce qu'on écrit image 1 d'un fla peut tout aussi bien être écrit dans une classe, c'est toujours vrai, mais constatez tout de même la différence entre constructeur (initialisation) et action d'image. Si image 1 du .fla vous écrivez un trace(“Je passe”), la fenêtre de sortie affiche son “je passe” à chaque boucle, alors que le constructeur n'est invoqué qu'une fois.

Notez aussi au passage (si je peux dire) que le constructeur est invoqué avant les actions d'image de l'image 1

Première chose, arrêter la lecture :

		public function main():void {
			stop();
			trace("costructeur");
		}

C'est presque la même chose que le stop qu'on pose image 1.
Vous voyez pourquoi je dis presque ?

Lançons la lecture au clic sur le bouton :

		public function main():void {
			stop();
			trace("costruc");
			btValide.addEventListener(MouseEvent.CLICK,valide);
		}
		private function valide(me:MouseEvent) {
			play();
		}

Là c'est clair ! Un stop image 1 aurait de nouveau arrêté la lecture dès le retour image 1. Ici il n'y est pas, ça boucle donc sans discontinuer, normal, mais j'en connais qui s'y sont laissé prendre ;)

La liste d'affichage

Occupons nous maintenant d'écrire le contenu du champ txtPseudo (image 1) dans txtNom image 30 (la dernière)

Mauvaise idée :

		private function valide(me:MouseEvent) {
			gotoAndStop(30);
                        txtNom.text="Ça marche "+txtPseudo.text;
		}

Effectivement, image 30 il n'y a plus txtPseudo… A la lecture de chaque image la liste d'affichage est modifiée : n'y figurent que les objets présents sur l'image courante, mais vous le saviez… C'est juste dans le cadre du “ce qui va sans dire va toujours mieux en le disant” ;)

		private function valide(me:MouseEvent) {
			var leNom:String=txtPseudo.text;
			gotoAndStop(30);
			txtNom.text="Ça marche "+leNom;
		}

Ça c'est déjà mieux, et dans un contexte de réalité on sortirait sans doute la variable pour pouvoir y accéder de partout par la suite.

package {
 
	import flash.display.MovieClip;
 
	public class main extends MovieClip {
		private var _leNom:String
 
		public function main():void {
			stop();
			trace("costruc");
			btValide.addEventListener(MouseEvent.CLICK,valide);
		}
		private function valide(me:MouseEvent) {
			_leNom=txtPseudo.text;
			gotoAndStop(30);
			txtNom.text="Ça marche "+_leNom;
		}
	}
}

Ça, c'est carrément bien : une variable globale (visible de partout dans la classe) et privée (c'est de la cuisine interne, on n'y accèdera pas depuis le fla).

Ajouter des actions d'image

D'accord j'accélère, j'en vois qui s'impatientent…
La vraie question c'est comment lancer la lecture (play) pour lire la belle animation, et s'arrêter image 30 pour y écrire leNom.

La solution passe par la fameuse méthode non documentée addFrameScript. Elle admet deux paramètres, le premier le numéro de l'image, le second une fonction qui sera invoquée à la lecture de l'image en question.
Les images sont numérotées depuis zéro.

unClip.addFrameScript(19,test)

… invoquera la fonction test image 20

Ce que vous auriez écrit dans l'action d'image, vous l'écrivez dans le bloc de la fonction, c'est aussi simple et pratique que ça :)

		public function main():void {
			stop();
			trace("costruc");
			addFrameScript(totalFrames-1,fin);
			btValide.addEventListener(MouseEvent.CLICK,valide);
		}
		private function fin() {
			trace("fin");
			stop();
			txtNom.text="Ça marche "+_leNom;
		}
		private function valide(me:MouseEvent) {
			_leNom=txtPseudo.text;
			play();
		}

Tant qu'à faire on ajoute le code non pas image 30 mais sur la dernière image, ça nous permettra de modifier la durée de l'animation si le cœur nous en dit :)

Utiliser des clips associés à une classe

Juste pour le plaisir d'utiliser notre nouvelle classe BoiteAlerte, vérifions que txtPseudo n'est pas vide avant de lancer la lecture.

Il nous faut donc un symbole qui sera lié à la classe BoiteAlerte, si vous ne vous souvenez plus des caractéristiques requises par la classe, ouvrez là et lisez votre pense bête que vous n'avez pas manqué d'insérer tout en haut ;)

// ******** requiert ******************
//btOK : bouton
//txtPrompt : TextField 
//*************************************

Vous pouvez aussi copier/coller le symbole de l'exercice précédent :mrgreen:

Le code depuis le fla, vous savez faire. Là c'est pareil…

// ******** Requiert **************
// btValid : Bouton
// txtPseudo : textField - image 1
// txtNom : TextField - dernière image
//******************************
package {
 
	import flash.display.MovieClip;
	import flash.events.MouseEvent;
 
 
	public class main extends MovieClip {
		private var leNom:String;
        // ---> déclaration/valorisation de la variable 
		var bteAlerte:Mv_BoiteAlerte=new Mv_BoiteAlerte();
 
		public function main():void {
			stop();
			addFrameScript(totalFrames-1,fin);
			btValide.addEventListener(MouseEvent.CLICK,valide);
		}
		private function fin() {
			stop();
			txtNom.text="Ça marche "+leNom;
		}
		private function valide(me:MouseEvent) {
                //--> interception
                	if (txtPseudo.text=="") {
                                // "réglages"
				bteAlerte.racine=root;
				bteAlerte.prompt="Le champ ne doit pas être vide";
				bteAlerte.x=(stage.stageWidth-bteAlerte.largeurBoite)/2;
				bteAlerte.y=63;
				bteAlerte.coulEcran=0xFF0000;
                                // afficher
				bteAlerte.affiche();
				return;
			}
			leNom=txtPseudo.text;
			play();
		}
	}
}

Et voilà le travail :)

Pour résumer

:arrow: Une classe de document doit impérativement étendre la classe Sprite ou MovieClip.
On peut étendre Sprite quand on n'utilisera pas le scénario (une seule image), et on sait qu'on ne pourra rien écrire dans les actions d'image du fla associé, même pas un commentaire.
Dit autrement : étendre MovieClip, ça marche toujours ; étendre Sprite c'est soumis à contrainte.

:arrow: Le constructeur de la classe est invoqué avant les actions d'image 1 et ce n'est pas la même chose.

:arrow: Seuls les objets d'affichage de l'image courante du scénario sont présents dans la liste d'affichage.

Passer d'un .fla à un .as (et vices inversés)

Pour en finir avec ce chapitre, et puisqu'il est fréquent de disposer d'une classe document alors qu'on souhaite rester dans l'ide et pas si rare de s'apercevoir en cours de travail sur le fla, que tous comptes faits, une classe conviendrait mieux, voici le même code selon les deux versions :

On considère un champ texte txtSortie et un bouton btDemo.

[mode vieille chose] … et n'allez pas imbriquer toutes les fonctions dans le constructeur … [/mode]

Mais… Du coup…

Nous voici donc au point pour ce qui est de la classe de document, classe destinée à être associée au gros clip racine qu'est l'objet MainTimeline.

Et si on associait une classe de document à un “petit clip normal” de la bibliothèque ? Ça ferait quoi ?
Et bien pareil :)

Un clip c'est un clip, qu'il s'agisse du scénario principal ou d'un clip de bibliothèque, il hérite de la même classe (ouf…) et se comporte pareil (re ouf !). Le dernier exemple de classe que nous avons mis en œuvre ici présuppose qu'il existe à la racine du projet image 1, un champ de saisie et un bouton. Il présuppose aussi un autre champ texte sur la dernière image. Sachez que n'importe quel clip qui respecte ces prérequis pourra tout aussi bien être associé à cette classe.

Ce qui implique que rien ne nous oblige à exploiter une classe de document (trouvée sur le net par exemple) en tant que telle. Imaginons une classe de document qui mette en œuvre un carrousel, ou un menu Dock. L'associer à un un .fla pour la tester c'est bien, mais dans la pratique, on n'aura jamais besoin uniquement de ce menu, il viendra s'intégrer au reste de l'animation (un site probablement). Un peu compliqué, non, d'en faire un swf qui sera chargé et exploité dans un autre .fla ?
On pourra fort bien associer la classe à un symbole de la bibliothèque, en profiter pour enrichir ce symbole des éléments graphiques de notre goût (fond et fioritures) et en disposer une instance sur la scène.

Cette fois, c'est définitivement tout pour ce qui concerne les classes que j'ai dites “relatives à un fichier fla” en introduction. Il ne nous reste plus qu'à voir ce qui concerne les autres classes, celles que j'ai dites génériques dans cette même introduction.

C'est par là que ça se passe.