LECON 303


Faire une animation simple
Dans ce chapitre, nous allons voir comment créer une animation simple.

Vous ne pourrez pas faire de jeu à la fin, mais je pense que vous aurez de quoi vous amuser un peu...

Let's go alors...
Sommaire du chapitre :
  • Les déplacements : principe
  • Continue, ne t'arrêtes pas si vite !
  • Attention aux bords, ne va pas te faire mal...
  • Ce qu'il faut retenir

Les déplacements : principe

Voilà le compte rendu de ce que nous avons :
  • une classe héritée de JFrame
  • une classe héritée de JPanel dans laquelle nous faisons de zolis dessins. Un rond en l'occurrence...

Avec ces deux classes, nous allons pouvoir créer un effet de déplacement.
Vous avez bien entendu: j'ai dit un effet de déplacement !
En réalité, le principe réside dans le fait que vous allez donner des coordonnées différentes à votre rond, et vous allez forcer votre objet Panneau à se redessiner ! Tout ceci, vous l'aviez deviné, dans une boucle !

Nous allons donc nous préparer à ces nouveautés !
Jusqu'à présent, nous avons utilisé des valeurs fixes pour les coordonnées de notre rond, et il va falloir dynamiser tout ça...

Nous allons donc créer deux variables privées de type int dans notre classe Panneau : appelons-les posX et posY.
Pour l'animation que nous allons travailler, notre rond devra provenir de l'extérieur de notre fenêtre. Partons du principe que celui-ci va faire 50 pixels de diamètre : il faudra donc que notre panneau peigne ce rond en dehors de sa zone d'affichage, nous initialiserons donc nos deux variables d'instance à -50.

Voilà à quoi ressemble notre classe, maintenant :
Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
 
public class Panneau extends JPanel {
 
        private int posX = -50;
        private int posY = -50;
        
        public void paintComponent(Graphics g){
                g.setColor(Color.red);
                g.fillOval(posX, posY, 50, 50);         
        }
 
        public int getPosX() {
                return posX;
        }
 
        public void setPosX(int posX) {
                this.posX = posX;
        }
 
        public int getPosY() {
                return posY;
        }
 
        public void setPosY(int posY) {
                this.posY = posY;
        }
        
}


Il ne nous reste plus qu'à faire en sorte que notre rond se déplace : il nous faut donc un moyen de changer les coordonnées de celui-ci, le tout dans une boucle. Nous allons ainsi ajouter une méthode privée dans notre classe Fenetre afin de gérer tout cela ; nous appellerons celle-ci en dernier dans notre constructeur. Voici donc à quoi ressemble notre classe Fenetre :
Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.awt.Dimension;
 
import javax.swing.JFrame;
 
 
public class Fenetre extends JFrame{
 
        private Panneau pan = new Panneau();
        
        public Fenetre(){
                
                this.setTitle("Animation");
                this.setSize(300, 300);
                this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                this.setLocationRelativeTo(null);
                this.setContentPane(pan);
                this.setVisible(true);
                
                go();
        }
        
        private void go(){
                
                for(int i = -50; i < pan.getWidth(); i++)
                {
                        int x = pan.getPosX(), y = pan.getPosY();
                        x++;
                        y++;
                        pan.setPosX(x);
                        pan.setPosY(y);
                        pan.repaint();  
                        try {
                                Thread.sleep(10);
                        } catch (InterruptedException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                        }
                }
                
        }       
}

Hep ! Qu'est-ce que c'est que ces deux instructions à la fin de la méthode go() ?


Tout d'abord, je pense que, les deux dernières instructions mises à part, vous ne devez pas avoir trop de mal à comprendre ce code.

La première des deux nouvelles instructions est pan.repaint(). Cette dernière donne l'ordre à votre composant, ici un JPanel, de se redessiner.
La toute première fois, dans le constructeur de notre classe Fenetre, votre composant invoque la méthode paintComponent et dessine un rond aux coordonnées que vous lui avez spécifiées. La méthode repaint() ne fait rien d'autre que de faire à nouveau appel à la méthode paintComponent ; mais avant, vous avez changé les coordonnées du rond par le biais des accesseurs créés précédemment. Donc à chaque tour de boucle, les coordonnées de notre rond vont changer.

La deuxième instruction est en fait un moyen de faire une pause dans votre code...
Celle-ci met en attente votre programme pendant un laps de temps défini dans la méthode sleep(), ce temps est exprimé en millièmes de secondes (plus le temps d'attente est court, plus votre animation sera rapide). Thread est en fait un objet qui permet de créer un nouveau processus dans un programme, ou de gérer le processus principal.
Dans tous les programmes, il y a au moins un processus, celui qui est en cours d'exécution. Mais vous verrez plus tard qu'il est possible de diviser certaines tâches en plusieurs processus afin de ne pas avoir de perte de temps et de performances dans vos programmes. Pour le moment, sachez que vous pouvez faire des pauses dans vos programmes avec cette instruction :
Code : Java -
1
2
3
4
5
6
7
try{
Thread.sleep(1000);//Ici une pause d'une seconde
}catch(InterruptedException e) {
        
        e.printStackTrace();
 
}

Cette instruction est dite "à risque", vous devez donc l'entourer d'un bloc try{}catch(){} afin de capturer les exceptions potentielles ! Sinon : ERREUR DE COMPILATION !


Maintenant que toute la lumière est faite sur cette affaire, exécutez votre code, et vous obtenez :



Bien sûr, cette image est le résultat final, vous devriez avoir vu votre rond bouger mais au lieu d'être clair, il a laissé une trainée derrière lui...
Pourquoi ?

C'est simple : vous avez demandé à votre objet Panneau de se redessiner, mais il a gardé les précédents passages de votre rond sur lui-même ! Pour résoudre ce problème, il suffit d'effacer ceux-ci avant de redessiner votre rond.
Comment fait-on ça ?

Il vous suffit de dessiner un rectangle, d'une quelconque couleur, prenant toute la surface disponible, avant de dessiner votre rond. Voici le code de notre classe Panneau mis à jour :

Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
 
public class Panneau extends JPanel {
 
        private int posX = -50;
        private int posY = -50;
        
        public void paintComponent(Graphics g){
                //On décide d'une couleur de fond pour notre rectangle
                g.setColor(Color.white);
                //On dessine celui-ci afin qu'il prenne tout la surface
                g.fillRect(0, 0, this.getWidth(), this.getHeight());
                //On redéfinit une couleur pour notre rond
                g.setColor(Color.red);
                //On le dessine aux coordonnées souhaitées
                g.fillOval(posX, posY, 50, 50);         
        }
 
        public int getPosX() {
                return posX;
        }
 
        public void setPosX(int posX) {
                this.posX = posX;
        }
 
        public int getPosY() {
                return posY;
        }
 
        public void setPosY(int posY) {
                this.posY = posY;
        }
        
}


Voici trois captures d'écran prises à différents moments de l'animation :


Je pense qu'il est temps d'améliorer encore notre animation... Est-ce que ça vous dirait que celle-ci continue tant que vous ne fermez pas votre fenêtre ?
Oui ? Alors continuons.

Continue, ne t'arrêtes pas si vite !

Voici l'un des moments délicats que j'attendais... Si vous vous rappelez bien ce que je vous ai dit sur le fonctionnement des boucles, vous devez vous souvenir de mon avertissement sur les boucles infinies !
Eh bien ce que nous allons faire ici, c'est un exemple d'utilisation d'une boucle infinie...

Si vous y réfléchissez deux secondes, comment dire à une boucle de ne pas s'arrêter à moins qu'elle ne s'arrête jamais ?
Dans l'exemple que nous allons utiliser pour le moment, nous allons simplifier les choses, mais nous améliorerons cela lorsque nous commencerons à interagir avec notre application...


Il y a plusieurs manières de faire une boucle infinie : vous avez le choix entre une boucle for, while ou do...while. Regardez ces déclarations :

Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
//Exemple avec un boucle while
while(true){
 // Ce code se répètera à l'infini car la condition est TOUJOURS vraie !
}
 
//Exemple avec une boucle for
for(;;)
{
   // Idem que précédemment, ici il n'y a pas d'incrément => donc la boucle ne se terminera jamais
}
 
//Exemple avec do...while
do{
   //Encore une boucle que ne se terminera pas !
}while(true);


Nous allons donc remplacer notre boucle finie par une boucle infinie dans la méthode go() de notre objet Fenetre. Ce qui nous donne :

Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
private void go(){
                
                for(;;)
                {
                        int x = pan.getPosX(), y = pan.getPosY();
                        x++;
                        y++;
                        pan.setPosX(x);
                        pan.setPosY(y);
                        pan.repaint();  
                        try {
                                Thread.sleep(10);
                        } catch (InterruptedException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                        }
                }
                
        }


Par contre, si vous avez exécuté notre nouvelle version, vous avez dû vous rendre compte qu'il reste un problème à gérer ! Eh oui. Votre rond ne se replace pas au départ lorsqu'il atteint l'autre côté de notre fenêtre.

Si vous ajoutez une instruction System.out.println() dans la méthode paintComponent inscrivant les coordonnées de notre rond, vous devez voir que celles-ci ne cessent de croître !


Le premier objectif est atteint mais il nous reste à gérer ce dernier problème.
Il faut donc réinitialiser les coordonnées de notre rond si celles-ci arrivent au bout de notre composant.
Voici donc notre méthode go() revue et corrigée :

Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private void go(){
                
                for(;;)
                {
                        int x = pan.getPosX(), y = pan.getPosY();
                        x++;
                        y++;
                        pan.setPosX(x);
                        pan.setPosY(y);
                        pan.repaint();  
                        try {
                                Thread.sleep(10);
                        } catch (InterruptedException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                        }
                        
                        //Si nos coordonnées arrivent aux bords de notre composant
                        //On réinitialise
                        if(x == pan.getWidth() || y == pan.getHeight())
                        {
                                pan.setPosX(-50);
                                pan.setPosY(-50);
                        }
                        
                }
                
        }


Yes !
Le code fonctionne parfaitement. En tout cas, comme nous l'avions prévu !
Mais avant de passer au chapitre suivant, nous pouvons encore faire mieux...
On y va ?

Attention aux bords, ne va pas te faire mal...

Maintenant, nous allons faire en sorte que notre rond puisse détecter les bords de notre Panneau et ricoche sur ceux-ci !
Vous devez vous imaginer un code monstrueux, et vous êtes très loin du compte...

Tout d'abord, jusqu'à maintenant, nous n'attachions aucune importance sur le bord que notre rond dépassait, ceci est terminé. Dorénavant, nous séparerons le dépassement des coordonnées posX et posY de notre Panneau.
Mais comment lui dire qu'il faut reculer ou avancer sur tel ou tel axe ?

Pour les instructions qui vont suivre, gardez en mémoire que les coordonnées de notre rond sont en fait les coordonnées du coin supérieur gauche du carré entourant notre rond ! !

Voilà la marche à suivre :
  • si la coordonnée x de notre rond est inférieure à la largeur et qu'il avance, on continue d'avancer ;
  • sinon, on recule.

Et nous allons faire de même pour la coordonnée y.

Comment savoir si on doit avancer ou reculer ? Avec un booléen.
Au tout début de notre application, deux booléens seront initialisés à false et si la coordonnée x est supérieure à la largeur du Panneau, alors on recule ; sinon, on avance, idem pour la coordonnée y.
Dans ce code, j'utilise deux variables de type int pour éviter de rappeler les méthodes getPosX() et getPosY().


Voilà notre nouveau code de la méthode go() :

Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
private void go(){
                
                //Les coordonnées de départ de notre rond
                int x = pan.getPosX(), y = pan.getPosY();
                //Le booléen pour savoir si on recule ou non sur l'axe X
                boolean backX = false;
                //Le booléen pour savoir si on recule ou non sur l'axe Y
                boolean backY = false;
                
                //Pour cet exemple, j'utilise une boucle while
                //Vous verrez qu'elle fonctionne très bien
                while(true){
                        
                        //Si la coordonnée x est inférieure à 1, on avance
                        if(x < 1)backX = false;
                        //Si la coordonnée x est supérieure à la taille du Panneau
                        //moins la taille du rond, on recule
                        if(x > pan.getWidth()-50)backX = true;
                        
                        //idem pour l'axe Y
                        if(y < 1)backY = false;
                        if(y > pan.getHeight()-50)backY = true;
                        
                        //Si on avance, on incrémente la coordonnée
                        if(!backX)
                                pan.setPosX(++x);
                        //Sinon, on décrémente
                        else
                                pan.setPosX(--x);
                        
                        //Idem pour l'axe Y
                        if(!backY)
                                pan.setPosY(++y);
                        else
                                pan.setPosY(--y);
                                
                        //On redessine notre Panneau
                        pan.repaint();
                        
                        //Comme on dit : la pause s'impose ! Ici, 3 millièmes de secondes
                        try {
                                Thread.sleep(3);
                        } catch (InterruptedException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                        }
                }
                
        }


Exécutez votre application et vous devez voir que votre rond ricoche contre les bords de notre Panneau. Vous pouvez même étirer la fenêtre, la réduire et ça marche toujours !

On commence à faire des choses sympa, non ?
Un petit topo vous attend avant un petit QCM, et nous allons passer à la suite !

Ce qu'il faut retenir

  • À l'instanciation d'un composant, la méthode paintComponent est automatiquement appelée.
  • Vous pouvez forcer un composant à se redessiner en invoquant la méthode repaint().
  • Pensez bien à ce que va donner votre composant après être redessiné.
  • Pour éviter que votre animation bave, réinitialisez le fond de votre composant pour éviter ce phénomène...
  • Vous verrez que tous les composants fonctionnent de la même manière.
  • L'instruction Thread.sleep(), permet de faire une pause dans votre programme.
  • Cette méthode prend un entier comme paramètre qui correspond à un temps exprimé en millièmes de secondes.
  • Vous pouvez utiliser des boucles infinies pour faire des animations.
Encore un chapitre rondement mené !
Maintenant, je pense que vous êtes paré pour : Votre premier bouton.


0 comments to "LECON 303"

Post a Comment

Powered by Blogger.

About This Blog

Aller au debut de la page