Aller au menu - Aller au contenu

Icône Faire une animation simple

Mise à jour : 12/02/2010
Difficulté : Intermédiaire Intermédiaire Creative Commons BY-NC-SA
96 702 visites depuis 7 jours, dont 1 374 sur ce chapitre classé 4/786
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... :D
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

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... :D

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... :D
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 :

Image utilisateur


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 :

Image utilisateur Image utilisateur Image utilisateur


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 ? :D
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... :waw:

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 ! :magicien:
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 ! :D
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 ! :magicien:

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

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... :D
  • 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.

Q.C.M.

Quelle méthode appelle-t-on pour redessiner un composant ?
Que va faire le code suivant ?
Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Test{
 
     public static void main(String[] args) {
        
          String str = "toto";
          Thread.sleep(300);
          System.out.println("Bonjour " + str);
 
     }
 
}
Si vous vous rappelez le chapitre sur les boucles, quelle instruction arrête une boucle et donc, une boucle infinie ?

Statistiques de réponses au QCM

Encore un chapitre rondement mené !
Maintenant, je pense que vous êtes paré pour : Votre premier bouton.
Chapitre précédent Sommaire Chapitre suivant

Partager

26 commentaires pour "Faire une animation simple"
Note moyenne : 3.57 / 4 (1025 votes)
Pseudo Commentaire
Hors ligne blaackjack # Posté le 06/06/2010 à 13:40:16

Un p'tit bémole par contre, faut arréter de coder en dur! :S
Donc pas bien ;)

Concernat les collisions, il existes pas mal de bible d'algorythmes. Parce que le code que j'ai vu là est pas très zoli à voir ;)
Hors ligne geo373 # Posté le 07/07/2010 à 13:30:50
Avatar

Je dois avoir un problème sur mon ordinateur, j'ai créé un constructeur qui me permet de rentrer mes valeurs de dimensions de la fenêtre et ayant eu un problème avec le rebond j'ai voulu vérifier ce que Java voyait.
Ma fenêtre fait 800*600 d'après ce que je rentre.
Lorsque dans go() je rajoute:
System.out.println(pan.getWidth()); il me trouve 792 au lieu de 800
System.out.println(pan.getHeight()); il me trouve 566 au lieu de 600

Et j'ai du modifier la condition de rebond qui passe de:
if(x<800-50)... à if(x<370) et
if(y<600-50)... à if(y<257)

J'ai remarqué que 792 ~= 370*2+50=790
Et que 566 ~= 257*2+50=364

Ce qui me trouble le plus c'est que je ne multiplie ni divise jamais dans mon code une seule dimension.Si quelqu'un sait ce qu'il se passe ça m'aiderait. Merci d'avance
Hors ligne bibabobu # Posté le 25/07/2010 à 18:38:37

blaackjack => Que veut-dire coder en dur?
Hors ligne Nicolas M. # Posté le 19/09/2010 à 21:33:42
M(NiCoLaSm) = 406,9 g/mol
Avatar

Ville : Notre-dame de bondeville
Pays : France métropolitaine

@geo373 : Les dimensions de ton panel (pan.getWidth(); et pan.getHeight();) ne prennent pas en compte les bordures de la fenêtre, contrairement aux dimensions de ta fenêtre que tu as fixées à 800 et 600... ;)

D'après moi, ta fenêtre comporte 8 pixels de bordure sur les côtés et 34 pixels de bordures en haut et en bas, qui ne sont pas comptés dans les dimensions de ton panel...
Secret (cliquez pour afficher)
Donc 4 px à gauche, 4 px à droite, 4 px en bas et 30 px en haut, mais ça peut être totalement faux... :p

Image utilisateur Image utilisateur

Le saviez-vous ? Les forums sont environ 283 174 fois plus efficaces que ma boîte MP pour vous aider. ;)
Image utilisateur
 
Hors ligne Styfore # Posté le 07/04/2011 à 10:12:00
Et pourquoi pas ?
Avatar

J'aurais bien une petite question :
Pour essayer pour comprendre, à la place du pan.repaint() j'ai mis this.setContentPane(pan) .
Et ... ça marche mais en plus ça ne laisse pas de trainer et cela évite de devoir redessiner le fond à chaque fois.
Est-ce mieux ? Moins bien ? pourquoi ?

Peut-on aussi surcharger la méthode paintComponent ? Si oui, comment utiliser les nouveau paramètres ?
(c'est débile ce que j'ai dit)

Merci :)

Voir tous les commentaires