• 12 heures
  • {0} Facile|{1} Moyenne|{2} Difficile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 30/04/2014

TP : Mauvais temps

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Après ces trois longues parties plutôt théoriques, il est temps de passer un peu à la pratique ! Au cours de ce TP, vous aurez donc l'occasion de revenir sur l'ensemble des notions que vous avez vues jusqu'à présent. Étant donné la quantité de choses que vous avez dû assimiler jusqu'à maintenant, il est normal que vous ayez quelques difficultés. Ne soyez donc pas gênés d'aller relire un chapitre ou deux qui vous ont semblé flous à la première lecture. Toutefois, vous êtes parfaitement en mesure de réussir ce TP, c'est pourquoi, je vous invite fortement à essayer par vous-mêmes avant d'aller voir directement la correction.

Le cahier des charges

L'objectif du chapitre

Le projet

L'objectif du chapitre est de concevoir une animation représentant une chute de neige !
Pour obtenir un rendu un peu plus réaliste, nous ajouterons également quelques petits détails que je vous présenterais dans la suite de ce TP. Avant de vous en dire plus, je vous propose à la figure suivante un petit aperçu de l'animation que nous obtiendrons à la fin de ce chapitre.

Aperçu de l'animation « Mauvais temps ».
Aperçu de l'animation « Mauvais temps ».

Comme vous pouvez le voir, nous allons créer un certain nombre de flocons que nous animerons afin de donner l'illusion d'une chute de neige. Nous ajouterons également une image de fond ainsi qu'un masque pour cacher les flocons derrière la branche au premier plan. Par ailleurs, vous avez peut être remarqué que les bords de l'image ont été assombris afin de mettre en valeur le centre de l'image.

Sachez qu'il y a des dizaines de façons différentes de réaliser une telle animation. Je vous présenterais donc ici la méthode que j'ai choisie, mais n'ayez pas peur si la votre est différente.
Avant de vous lancer tête baissée, lisez bien toutes les consignes et pré-requis, sans quoi vous n'arriverez certainement pas au bout !

Intérêt du projet

Ce projet a été choisi pour diverses raisons que voici :

  • Tout d'abord, il s'agit d'une animation très répandu en Flash et que beaucoup de débutants désirent réaliser.

  • Dans ce projet, nous allons utiliser la quasi-totalité des connaissances acquises jusqu'à présent à savoir : variables, conditions, boucles, fonctions, tableaux, classes , Programmation Orientée Objet et quasiment tout ce qui est lié à l'affichage.

  • Enfin, cette animation ne nécessite pas énormément de lignes de code, ce qui est plutôt favorable pour l'un de vos premiers TP.

Bien entendu, nous allons détailler davantage la manière de procéder pour concevoir ce projet.

Le travail à réaliser

Pour réaliser cette animation, je vous invite à procéder pas à pas. C'est pourquoi, je vous ai présenté les différentes étapes de la conception de ce projet.

Création de l'aspect d'un flocon

La première étape va consister à dessiner un flocon. Vous avez déjà toutes les connaissances pour parvenir à dessiner celui-ci, vous devriez donc y arriver sans problème. Il est possible de réaliser des formes de flocons extrêmement complexes, néanmoins un simple disque fera très bien l'affaire.
Le fait qu'il y aura des milliers de flocons dans notre future animation devrait faire naître des idées dans votre esprit quant à la manière de les concevoir . Je n'en dis pas plus, à vous de réfléchir d'ores et déjà, à la meilleure façon de faire !

La génération des flocons

Ensuite, vous allez devoir générer l'ensemble de vos flocons que nous réutiliserons à l'infini. Une fois généré, chaque flocon devra être positionné aléatoirement à l'écran. Il vous faudra également définir aléatoirement son aspect ainsi que sa vitesse de chute. Pensez également que vous pouvez donner de la profondeur à votre scène, sachant que les flocons les plus gros seront au premier plan et les plus rapides de notre point de vue.

L'animation des flocons

Tout ce qui concerne l'animation sera regroupé à l'intérieur d'une méthode qui sera appelée à intervalles réguliers grâce à la fonction setInterval().
À l'intérieur de cette méthode, vous ferez l'ensemble des modifications nécessaires à l'animation des flocons. Vous pourrez ainsi mettre à jour la position de chaque flocon, et éventuellement, réinitialiser un flocon sorti de la scène. Cette fonction sera donc appelée régulièrement et, à l'infini, se chargera ainsi de l'animation de la scène.

C'est un peu prématuré puisqu'il s'agit de l'objet du prochain chapitre, mais vous utiliserez pour cela ce qu'on appelle une fonction de rappel. Sans plus d'explications, voici comment utiliser cette fonction setInterval() :

var monInterval:uint = setInterval(maMethode, intervalle);

Dans l'instruction précédente, maMethode représente une méthode, donc de type Function, et intervalle correspond au temps espaçant deux exécutions consécutives de la fonction précédente.

Association des différents éléments graphiques

Lorsque que toute la gestion des flocons aura été terminée, vous pourrez alors passer à la composition de la scène finale. Vous devrez donc ajouter l'image de fond et le masque à la scène. Vous pourrez également tenter de créer cet effet d'assombrissement des bords de la scène en étant imaginatif.
Cette étape devrait normalement se dérouler relativement bien si vous être parvenu jusque là !

Les images

Enfin, je vous suggère de télécharger les deux images dont vous aurez besoin pour réaliser ce TP (voir les deux figures suivantes). Il y a donc l'image de fond intitulée « Paysage.png » ainsi que le masque nommé « Masque.png ».

Paysage.png
Paysage.png
Masque.png
Masque.png

La correction

La structure du programme

Avant de nous lancer tête baissée dans les lignes de code, je vais vous présenter un peu la manière dont j'ai conçu et organisé le code au sein du projet. Si vous avez réalisé ce projet par vous-même, il est probable que vous n'ayez pas structuré votre projet de la même façon. Je rappelle qu'il n'y a pas qu'une seule et unique manière de concevoir une telle animation.

La figure suivante vous montre comment j'ai procédé pour programmer ce TP.

La structure du projet telle qu'elle est définie dans la correction
La structure du projet telle qu'elle est définie dans la correction

Ainsi, dans l'organisation précédente, nous avons divisé le code en trois classes différentes :

  • La classe Flocon est celle qui va principalement vous permettre de dessiner un flocon. Elle hérite donc de Shape pour avoir accès aux méthodes de la classe Graphics. Vous pouvez également associer à cette classe un attribut _vitesse qui correspond à la vitesse de chute du flocon et qui lui est donc propre.

  • Ensuite, nous avons la classe Neige qui hérite de Sprite et est donc un conteneur. Elle va donc nous permettre de générer et d'animer l'ensemble des flocons.

  • Enfin, notre classe principale va nous servir à composer la scène finale à l'aide de l'image de fond, du masque et d'un dernier objet que nous créerons et qui servira à assombrir les bords de la scène.

Dans la suite de la correction de ce TP, nous allons donc détailler l'ensemble de ses classes en partant de la classe Flocon pour aller vers la classe principale.

La classe Flocon

Création de la classe

La première classe que nous allons créer est la classe Flocon !
Voici donc la structure de base de cette classe :

package  
{
    import flash.display.Shape;
    
    public class Flocon extends Shape
    {
        public function Flocon() 
        {
            super();
            
        }
    	
    }

}

Celle-ci permet tout d'abord de créer le « visuel » du flocon. Pour cela, nous utiliserons les méthodes de la classe Graphics puisque notre classe hérite de Shape. Rien de plus facile maintenant pour vous, il suffit de tracer un disque blanc à l'intérieur du constructeur de la manière suivante :

this.graphics.beginFill(0xFFFFFF);
this.graphics.drawCircle(0, 0, 1.5);

Sachant que nos flocons ne tomberont pas tous à la même vitesse, nous allons donc créer un attribut _vitesse pour retenir celle-ci :

private var _vitesse:Number;

Enfin, n'oubliez pas d'initialiser cet attribut et de lui associer des accesseurs, que nous ne détaillerons pas ici. Jetez simplement un coup d’œil au code complet de cette classe qui, soit dit entre nous, est plutôt succinct.

La classe Neige

Création de la classe

À présent, nous allons nous occuper de la classe Neige qui sera donc notre conteneur pour l'ensemble des flocons.
Pour commencer, je vous invite donc à créer une nouvelle classe si ce n'est déjà fait. À l'intérieur, nous aurons besoin des trois constantes que j'ai nommé LARGEUR_SCENE, HAUTEUR_SCENE et NOMBRE_FLOCONS. Pour faciliter la suite, j'ai déjà fait l'intégralité des importations ainsi que la mise en place de la structure globale de la classe.
Voici donc notre classe Neige, prête à être remplie :

package
{
    import flash.display.Sprite;
    import flash.filters.BlurFilter;
    import flash.filters.BitmapFilterQuality;
    import flash.utils.*;
    
    public class Neige extends Sprite
    {
        private const LARGEUR_SCENE:int = 480;
        private const HAUTEUR_SCENE:int = 320;
        private const NOMBRE_FLOCONS:int = 3000;
        
        public function Neige()
        {
            super();
            // Génération des flocons
        }
        
        public function animerFlocons():void  
        {
            // Animation des flocons
        }
    }

}
Génération des flocons

Nous allons ici nous intéresser à la création des flocons et à leur mise en place sur la scène au démarrage de l'animation.
Pour commencer, nous allons créer nos flocons en utilisant un tableau de Flocon de type Array. Ce tableau devra être accessible depuis la fonction animerFlocons(), c'est pourquoi, j'ai décidé de créer un nouvel attribut nommé MesFlocons :

// Tableau de Flocon
private var mesFlocons:Array;

Dans ce paragraphe, nous nous occuperons de la génération des flocons. Plaçons-nous donc à l'intérieur du constructeur de la classe Neige.
Commençons tout de suite par initialiser notre tableau Array en définissant sa taille à NOMBRE_FLOCONS défini juste avant :

mesFlocons = new Array();

À présent, nous devons instancier nos flocons et leur apporter quelques modifications un par un. Il se trouve que les boucles sont justement très recommandées pour parcourir les éléments d'un tableau :

for (var i:uint = 0; i < NOMBRE_FLOCONS - 1; i++) {
    // Création et modification de chaque flocon
}

Nous pouvons maintenant instancier nos flocons, les uns après les autres :

mesFlocons.push(new Flocon());

Lors du démarrage de l'animation, il serait préférable que les flocons soient répartis aléatoirement sur l'écran. Nous allons donc modifier la position horizontale et la position verticale en utilisant la méthode random() de la classe Math.
Voici comment réaliser cette opération :

// Placement aléatoire des flocons sur l'écran
mesFlocons[i].x = Math.random() * LARGEUR_SCENE;
mesFlocons[i].y = Math.random() * HAUTEUR_SCENE;

Pour donner une impression de profondeur à notre scène, nous allons modifier l'aspect de nos flocons ainsi que leur vitesse. Pour cela, nous allons définir une variable aspect qui représentera la taille du flocon, mais également sa « distance » à l'écran, le zéro correspondant à une distance infinie, c'est-à-dire loin de l'écran. Étant donné qu'avec la distance, la vitesse de chute des flocons semble diminuer également, nous réutiliserons cette variable pour définir la vitesse du flocon.
Voici le code correspondant à ces manipulations :

// Définition d'un aspect aléatoire
var aspect:Number = Math.random() * 1.5;
// Modification de l'aspect du flocon
mesFlocons[i].scaleX = aspect;
mesFlocons[i].scaleY = aspect;
mesFlocons[i].alpha = aspect;
// Modification de la vitesse du flocon
mesFlocons[i].vitesse = 2 * (aspect + Math.random());

Suivant la distance du flocon à l'écran, celui-ci sera également plus ou moins net. Nous allons donc réutiliser notre variable aspect pour créer un filtre de flou, simulant la profondeur de champ de la caméra. Pour accentuer cet effet de profondeur, j'ai utilisé une formule mathématique avec des puissances.
Je vous donne directement le code correspondant :

// Génération du filtre
var flou:BlurFilter = new BlurFilter(); 
flou.blurX = Math.pow(10*aspect,4)/10000; 
flou.blurY = Math.pow(10*aspect,4)/10000; 
flou.quality = BitmapFilterQuality.MEDIUM;
// Application du filtre au flocon
mesFlocons[i].filters = new Array(flou);

Enfin, il ne nous reste plus qu'à ajouter notre flocon à la liste d'affichage de la classe Neige :

this.addChild(mesFlocons[i]);

La génération des flocons est maintenant terminée et notre application est prête à être animée ! ^^
Pour cela, n'oubliez pas de mettre l'instruction suivante à l'extérieur de la boucle :

var monInterval:uint = setInterval(animerFlocons, 40);
L'animation des flocons

Dans ce paragraphe, nous allons nous occuper de toute la partie « animation » du programme. Nous travaillerons donc à l'intérieur de la méthode animerFlocons() qui, je le rappelle, sera exécutée toutes les 40 millisecondes grâce à la fonction setInterval().
Encore une fois, nous allons manipuler les flocons individuellement. Nous utiliserons donc à nouveau une boucle for :

for (var i:uint = 0; i < mesFlocons.length; i++) {
    // Mise à jour de chaque flocon
}

Le principe de l'animation consiste à modifier la position de chaque flocon. Pour faire cela, nous utiliserons la propriété vitesse de la classe Flocon que nous viendrons ajouter à la position verticale du flocon.
Voici comment il faut procéder :

mesFlocons[i].y += mesFlocons[i].vitesse;

Pour éviter de créer des flocons à l'infini, nous allons réutiliser les flocons qui sont sortis de la zone visible de l'écran pour les renvoyer en haut de celui-ci. Vous vous en doutez peut-être, nous utiliserons une condition if en testant la valeur de la position verticale d'un flocon comme ceci :

if (mesFlocons[i].y > HAUTEUR_SCENE) {
    // Réinitialisation du flocon en haut de la scène.
}

À l'intérieur, nous pouvons alors remettre la position verticale du flocon à zéro et éventuellement replacer aléatoirement le flocon horizontalement :

mesFlocons[i].x = Math.random() * LARGEUR_SCENE;
mesFlocons[i].y = 0;

Aussi surprenant que cela puisse paraître, nous avons à présent terminé l'animation des flocons !

La classe principale

Création des objets d'affichage

Nous voici de retour dans la classe principale !
Nous allons donc ici gérer la mise en place des différents éléments à l'écran. Dans un premier temps, je vous propose d'importer les deux images externes, à savoir Paysage.png et Masque.png que vous avez dû normalement télécharger précédemment.
Voici comment j'ai réalisé ceci :

// Préparation des images
[Embed(source = 'images/Paysage.png')]
private var Paysage:Class;
[Embed(source = 'images/Masque.png')]
private var Masque:Class;

J'ai donc utilisé quatre éléments différents pour composer la scène finale. J'ai tout d'abord pris deux occurrences monPaysage et monMasque des images précédentes. Ensuite j'ai instancié notre classe Neige qui contient l'ensemble des flocons de l'animation. Enfin, j'ai créé une nouvelle variable nommée maVignette de type Shape. Celle-ci va nous permettre de créer un « effet de vignette », comme nous le verrons juste après.
Voilà donc nos quatre objets :

// Création des objets d'affichage
var monPaysage:Bitmap = new Paysage();
var monMasque:Bitmap = new Masque();
var maNeige:Neige = new Neige();
var maVignette:Shape = new Shape();

Dans la suite, nous allons alors gérer l'« interconnexion » entre ces différents éléments pour aboutir au rendu final que vous avez vu en début de chapitre.

Création d'un effet de vignette

Un « effet de vignette » est une technique qui permet de mettre en valeur ce qui se trouve au centre d'une image. En fait, cela consiste à assombrir les bords et les angles de l'image. Pour faire cela, le plus simple est créer un dégradé elliptique, de couleur blanche au centre et sombre à l'extérieur. Ainsi, par application d'un mode de fusion, le centre de l'image se voit devenir plus lumineuse et ressort davantage.
Voici donc à la figure suivante le vignettage que nous allons réaliser.

Dégradé radial pour un « effet de vignette »
Dégradé radial pour un « effet de vignette »

Pour réaliser cet effet, nous allons créer un dégradé de type radial à l'intérieur d'un remplissage de forme rectangulaire.
Pour commencer, nous nous occuperons des réglages du dégradé en termes de couleurs. Nous aurons donc un dégradé du blanc au noir, comme vous pouvez le voir ci-dessous :

// Paramètrage du dégradé
var couleurs:Array = [0xFFFFFF, 0x000000]; 
var alphas:Array = [1, 1]; 
var ratios:Array = [96, 255];

Ensuite, il nous faut générer la matrice permettant de contrôler l'aspect général du dégradé. Pour cela, nous allons donc créer un dégradé radial plus large que la taille de la scène afin de ne pas avoir de noir absolu, puis nous recentrerons celui-ci.
Voici comment j'ai réalisé ceci :

// Création de la matrice de description
var matrix:Matrix = new Matrix(); 
var largeur:Number = 2*LARGEUR_SCENE;
var hauteur:Number = 2*HAUTEUR_SCENE;
var rotation:Number = 0;
var tx:Number = -0.5*LARGEUR_SCENE; 
var ty:Number = -0.5*HAUTEUR_SCENE; 
matrix.createGradientBox(largeur, hauteur, rotation, tx, ty);

L'étape suivante consiste alors à tracer le remplissage correspondant à la taille de la scène :

// Tracé du remplissage
maVignette.graphics.beginGradientFill(GradientType.RADIAL, couleurs, alphas, ratios, matrix); 
maVignette.graphics.drawRect(0, 0, LARGEUR_SCENE, HAUTEUR_SCENE);

Enfin, nous allons appliquer un mode de fusion de type « multiplier » qui va permettre d'assombrir les bords de l'image en laissant le centre de celle-ci intact.

// Application du mode de fusion
maVignette.blendMode = BlendMode.MULTIPLY;

Nous avons à présent l'intégralité de nos éléments graphiques qu'il va falloir agencer les uns avec les autres pour obtenir le rendu final désiré.

Mise en place des éléments à l'écran

Maintenant, nous devons ajouter chacun de nos éléments à la liste d'affichage. Mis à part le masque qui ne sera pas visible, les objets d'affichage doivent être ajoutés dans un certain ordre. En arrière-plan, nous aurons donc l'image de fond présente dans la variable monPaysage. Ensuite, nous placerons l'objet maNeige par-dessus qui sera masqué par monMasque. Enfin, nous placerons maVignette au premier plan pour que son mode de fusion s'applique à tous les objets derrière elle.
Voilà donc l'ordre à respecter :

// Ajout des éléments à la liste d'affichage
this.addChild(monPaysage);
this.addChild(monMasque);
this.addChild(maNeige);
this.addChild(maVignette);

Pour finir, appliquez le masque à l'objet maNeige :

// Application du masque
maNeige.cacheAsBitmap = true;
monMasque.cacheAsBitmap = true;
maNeige.mask = monMasque;

Ça y est, c'est terminé ! :D
Nous avons à présent fini l'intégralité de cette animation qui demandait quelques efforts, je vous l'accorde.

Le code source complet

La classe Flocon

Flocon.as

package  
{
    import flash.display.Shape;
    
    public class Flocon extends Shape
    {
        private var _vitesse:Number;
        
        public function Flocon() 
        {
            super();
            _vitesse = 0;
            this.graphics.beginFill(0xFFFFFF);
            this.graphics.drawCircle(0, 0, 1.5);
        }
        
        public function get vitesse():Number
        {
            return _vitesse;
        }
        
        public function set vitesse(value:Number):void
        {
            _vitesse = value;
        }
    	
    }

}

La classe Neige

Neige.as

package
{
    import flash.display.Sprite;
    import flash.filters.BlurFilter;
    import flash.filters.BitmapFilterQuality;
    import flash.utils.*;
    
    public class Neige extends Sprite
    {
        private const LARGEUR_SCENE:int = 480;
        private const HAUTEUR_SCENE:int = 320;
        private const NOMBRE_FLOCONS:int = 3000;
        // Tableau de Flocon
        private var mesFlocons:Array;
        
        public function Neige()
        {
            super();
            mesFlocons = new Array();
            for (var i:uint = 0; i < NOMBRE_FLOCONS - 1; i++) {
                // Création et modification de chaque flocon
                mesFlocons.push(new Flocon());
                // Placement aléatoire des flocons sur l'écran
                mesFlocons[i].x = Math.random() * LARGEUR_SCENE;
                mesFlocons[i].y = Math.random() * HAUTEUR_SCENE;
                // Définition d'un aspect aléatoire
                var aspect:Number = Math.random() * 1.5;
                // Modification de l'aspect du flocon
                mesFlocons[i].scaleX = aspect;
                mesFlocons[i].scaleY = aspect;
                mesFlocons[i].alpha = aspect;
                // Modification de la vitesse du flocon
                mesFlocons[i].vitesse = 2 * (aspect + Math.random());
                // Génération du filtre
                var flou:BlurFilter = new BlurFilter(); 
                flou.blurX = Math.pow(10*aspect,4)/10000; 
                flou.blurY = Math.pow(10*aspect,4)/10000; 
                flou.quality = BitmapFilterQuality.MEDIUM;
                // Application du filtre au flocon
                mesFlocons[i].filters = new Array(flou);
                // Ajout du flocon à la liste d'affichage
                this.addChild(mesFlocons[i]);
            }
            
            var monInterval:uint = setInterval(animerFlocons, 25);
        }
        
        public function animerFlocons():void  
        {
            for (var i:uint = 0; i < mesFlocons.length; i++) {
                // Mise à jour de chaque flocon
                mesFlocons[i].y += mesFlocons[i].vitesse;
                if (mesFlocons[i].y > HAUTEUR_SCENE) {
                    // Réinitialisation du flocon en haut de la scène.
                    mesFlocons[i].x = Math.random() * LARGEUR_SCENE;
                    mesFlocons[i].y = 0;
                }
            }
        }
    }

}

La classe principale

Main.as

package 
{
    import flash.display.Shape;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.display.Bitmap;
    import flash.geom.Matrix;
    import flash.display.GradientType;
    import flash.display.BlendMode;
    
    [SWF(width="480", height="320")]
    public class Main extends Sprite 
    {
        private const LARGEUR_SCENE:int = 480;
        private const HAUTEUR_SCENE:int = 320;
        // Préparation des images
        [Embed(source = 'images/Paysage.png')]
        private var Paysage:Class;
        [Embed(source = 'images/Masque.png')]
        private var Masque:Class;
        
        public function Main():void 
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private function init(e:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            // Création des objets d'affichage
            var monPaysage:Bitmap = new Paysage();
            var monMasque:Bitmap = new Masque();
            var maNeige:Neige = new Neige();
            var maVignette:Shape = new Shape();
            // Paramétrage du dégradé
            var couleurs:Array = [0xFFFFFF, 0x000000]; 
            var alphas:Array = [1, 1]; 
            var ratios:Array = [96, 255];
            // Création de la matrice de description
            var matrix:Matrix = new Matrix(); 
            var largeur:Number = 2*LARGEUR_SCENE;
            var hauteur:Number = 2*HAUTEUR_SCENE;
            var rotation:Number = 0;
            var tx:Number = -0.5*LARGEUR_SCENE; 
            var ty:Number = -0.5*HAUTEUR_SCENE; 
            matrix.createGradientBox(largeur, hauteur, rotation, tx, ty);
            // Tracé du remplissage
            maVignette.graphics.beginGradientFill(GradientType.RADIAL, couleurs, alphas, ratios, matrix); 
            maVignette.graphics.drawRect(0, 0, LARGEUR_SCENE, HAUTEUR_SCENE);
            Application du mode de fusion
            maVignette.blendMode = BlendMode.MULTIPLY;
            // Ajout des éléments à la liste d'affichage
            this.addChild(monPaysage);
            this.addChild(monMasque);
            this.addChild(maNeige);
            this.addChild(maVignette);
            // Application du masque
            maNeige.cacheAsBitmap = true;
            monMasque.cacheAsBitmap = true;
            maNeige.mask = monMasque;
            
        }
        
    }
    
}
Exemple de certificat de réussite
Exemple de certificat de réussite