Animation Java

Pas de rafraîchissement de la fenêtre

Le problème exposé dans ce sujet a été résolu.

Bonjour à tous,

Je m’adresse à vous parce que je ne vois pas où j’ai pu me tromper dans mon code. L’objectif est de coder le déplacement d’un personnage à la manière d’un jeu Pokémon dans la troisième génération pour ceux qui connaissent, c’est-à-dire que lorsque le personnage se déplace d’une case à une autre adjacente, une animation sur ses jambes est montrée.

Le problème, c’est que mon code s’exécute de manière un peu bizarre. C’est-à-dire que lorsque je l’exécute, je n’obtiens que l’état final escompté, et lorsque je mets une pause de deux secondes, je n’obtiens que l’état initial. Les codes sont les suivants :

Main :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main;

import maze.Maze;
import maze.MazeReadingException;
import ui.MainWindow;

public class MainTest {

    public static void main(String[] args) throws MazeReadingException, InterruptedException {
        Maze maze = new Maze();
        MainWindow window = new MainWindow("Projet JAVA");
        maze.initFromTextFile("data/levels/labyrinthe.txt");
        window.getAppModel().setDrawnBoxes(maze.getCaseMatrix());
        window.setVisible(true);
                //Thread.sleep(2000);
        window.getAppModel().setCharXCharY(0, 0);
        window.getAppModel().setCharXCharY(0, 1);
        window.getAppModel().setCharXCharY(0, 2);
        window.getAppModel().setCharXCharY(0, 3);
    }

}

MainWindow est la classe créant ma fenêtre principale. Maze est une classe représentant le terrain, elle possède entre autres un attribut caseMatrix qui représente les cases du terrain. La méthode initFromTextFile sert à initialiser cet attribut à partir d’un fichier texte. AppModel est le modèle de mon application, qui possède comme attribut drawnBoxes qui représente les cases à dessiner. MainWindow possède comme content un panel qui contient deux JPanel, un pour les boutons, l’autre affichant le terrain. C’est celui-ci qui m’intéresse. Voici sa classe :

MainPanel :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package ui;

import java.awt.Graphics;
import javax.swing.JPanel;

public class MazePanel extends JPanel {
    public static final long serialVersionUID = 201801142359L;
    private final MainWindow mainWindow;

    @Override
    protected void paintComponent(Graphics g) {
        mainWindow.getAppModel().paintBoxes(g);
        mainWindow.getAppModel().paintCharacter(g);
    }

    public void notifyForUpdate() {
        repaint();
    }

    public MazePanel(MainWindow mainWindow) {
        super();
        this.mainWindow = mainWindow;
    }
}

paintBoxes() affiche les cases contenues dans drawnBoxes et fonctionne a priori bien. En revanche, paintCharacter(), censée afficher les animations, ne fonctionne pas comme je le souhaite. Voici son code, contenu donc dans AppModel :

 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
public void paintCharacter(Graphics g) {
        try {
            String filePath = "data/sprites/";
            filePath += departureCharacterBoy + direction + leftFoot + ".png";
            Image img = ImageIO.read(new File(filePath));

            if (direction.equals("Right")) {
                g.drawImage(img, 16*charX - 7, 16*charY - 3, null);
            }

            else if (direction.equals("Left")) {
                g.drawImage(img, 16*charX + 9, 16*charY - 3, null);
            }

            else if (direction.equals("Face")) {
                g.drawImage(img, 16*charX + 1, 16*charY + 5, null);
            }

            else {
                g.drawImage(img, 16*charX + 1, 16*charY - 11, null);
            }

            Thread.sleep(500);
            paintBoxes(g);
            filePath = "data/sprites/" + departureCharacterBoy + direction + ".png";
            img = ImageIO.read(new File(filePath));
            g.drawImage(img, 16*charX + 1, 16*charY - 3, null);
                        Thread.sleep(500);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Je voudrais donc que mon personnage se déplace de la moitié d’une case (une case étant un carré de 16x16 px) en une demi-seconde puis encore de la moitié en une demi-seconde également. Voici le code de la fonction setCharXCharY utilisée dans le main :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void setCharXCharY(int charX, int charY) {
        if (this.charX > charX) {
            this.direction = "Left";
        }

        else if (this.charX < charX) {
            this.direction = "Right";
        }

        else if (this.charY > charY) {
            this.direction = "Back";
        }

        else {
            this.direction = "Face";
        }

        this.leftFoot = !leftFoot;
        this.charX = charX;
        this.charY = charY;
        setChanged();
        notifyObservers();
    }

C’est la première fois que je demande de l’aide en Java, et je me rends compte que ça serait vraiment lourd de poster ici toutes les classes ici, mais d’un autre côté je ne vois vraiment d’où peut venir l’erreur, donc je suis désolé de la longueur du post. Je tiens à préciser que ce n’est absolument pas professionnel, donc si vous avez besoin du reste du code, des images ou du fichier .txt je n’aurai aucun problème à vous les donner.

Merci d’avance :)

Bonjour,

Pourquoi JAVA (et swing surtout)?

Soyons un peu plus sérieux.


Pourquoi tu as des "sleeps" et l’appel "drawboxes" dans ta fonction pour dessiner ton personnage ? Je pense que c’est mal placé.


Tu ne devrais pas avoir à charger à chaque utilisation tes images pour ton personnage.

De plus, tu ne devrais avoir qu’une seule image pour toutes les positions et actions de ton personnage. Comme ça tu n’as qu’à charger une image une bonne fois pour toute.


Tu ne devrais pas mettre à jour l’orientation en comparant l’évolution de la position, mais mettre à jour ton personnage directement depuis ton contrôleur.


Concernant ton problème, il vient selon moi de tes "sleeps", enfin, pas directement.

En fait, tu demande de dessiner ton personnage, tu attends, et tu demandes de dessiner ton personnage différemment. Le problème, c’est que le rafraîchissement de ta fenêtre ne se fait probablement qu’a la fin de ta fonction. D’où le problème d’affichage.

Bonjour,

Merci de ta réponse déjà. Ensuite :

Pourquoi JAVA (et swing surtout)?

JuDePom

Parce que c’est un projet pour mon école où l’on est forcé d’utiliser JAVA et Swing :p


Pourquoi tu as des "sleeps" et l’appel "drawboxes" dans ta fonction pour dessiner ton personnage ? Je pense que c’est mal placé.


JuDePom

Ce que je vouais faire en faisant ça, c’est de dessiner une première fois le personnage entre les deux cases avec l’animation de marche, puis, pour effacer le personnage, de redessiner le terrain par-dessus et de redessiner le personnage à sa nouvelle position. Le sleep sert à créer une "impression" de mouvement, même saccadée.


Tu ne devrais pas avoir à charger à chaque utilisation tes images pour ton personnage.

De plus, tu ne devrais avoir qu’une seule image pour toutes les positions et actions de ton personnage. Comme ça tu n’as qu’à charger une image une bonne fois pour toute.


JuDePom

D’accord, donc il voudrait mieux que je charge toutes les images au début de mon programme puis que je les utilise par la suite ? Pour la deuxième remarque, il n’y aurait pas l’impression de mouvement que je souhaite. Après, mon but pour l’instant est surtout de faire quelque chose de fonctionnel, donc si ça permet de débugger, ça me dérangera pas de l’enlever x)


Tu ne devrais pas mettre à jour l’orientation en comparant l’évolution de la position, mais mettre à jour ton personnage directement depuis ton contrôleur.


JuDePom

Je n’ai pas encore codé le contrôleur, je voulais surtout vérifier que les fonctions codées pour l’animation étaient fonctionnelles. Mais effectivement, ce serait plus logique, ce sera changé dans la suite du code, merci.


Concernant ton problème, il vient selon moi de tes "sleeps", enfin, pas directement.

En fait, tu demande de dessiner ton personnage, tu attends, et tu demandes de dessiner ton personnage différemment. Le problème, c’est que le rafraîchissement de ta fenêtre ne se fait probablement qu’a la fin de ta fonction. D’où le problème d’affichage.

JuDePom

Il faudrait donc que je force la rafraîchissement de la fenêtre avant l’appel à paintBoxes ? Je vois, d’accord ! Mais du coup il faut que je change pas mal la structure de mon code x)

Merci beaucoup pour ta réponse en tout cas !

D’accord, donc il voudrait mieux que je charge toutes les images au début de mon programme puis que je les utilise par la suite ? Pour la deuxième remarque, il n’y aurait pas l’impression de mouvement que je souhaite. Après, mon but pour l’instant est surtout de faire quelque chose de fonctionnel, donc si ça permet de débugger, ça me dérangera pas de l’enlever x)

BunshinKage

Pour l’effet recherché, il n’y a pas de différences, avec un fichier ressemblant à ceci:

Exemple de sprites issues du jeu Running guys

Tu n’affiches qu’une petite portion de l’image, et le tour est joué :)

+0 -0

Désolé de revenir, mais j’ai jugé qu’il était plus pertinent de poser ma question ici comme elle est en lien avec le reste du sujet plutôt que d’en créer un autre.

Sais-tu s’il est possible de dessiner sur un JPanel existant sans le modifier ? En soi, le but serait de ne pas rafraîchir le JPanel contenant le terrain à chaque fois que le personnage fait un pas. Ce serait donc comme superposer un JPanel transparent ne contenant que le personnage sur le premier qui lui par contre se fera rafraîchir à chaque fois que le personnage fait un pas.

Je pensais avoir trouvé une solution avec JLayer et JLayerUI, mais a priori, cela rafraîchit quand même le JPanel sur lequel on dessine…

Sais-tu s’il est possible de dessiner sur un JPanel existant sans le modifier ?

BunshinKage

Le problème, c’est que tu souhaites modifier un composant (ou un de ses fils) sans que celui ci n’agisse. Swing n’est pas fait pour ça. Swing est optimiser pour gérer des hiérarchies d’objets graphiques qui pour la majorité sont statiques. Chaque modification d’un composant appelle a redessiner tout ou partie des parents et des enfants de se composant.

Il y a la fonction paintImmediatly(x, y, w, h), qui repeint unique la zone demandée, mais je ne pense pas du tout que cela soit une bonne idée.

Sais-tu s’il est possible de dessiner sur un JPanel existant sans le modifier ?

BunshinKage

Le problème, c’est que tu souhaites modifier un composant (ou un de ses fils) sans que celui ci n’agisse. Swing n’est pas fait pour ça. Swing est optimiser pour gérer des hiérarchies d’objets graphiques qui pour la majorité sont statiques. Chaque modification d’un composant appelle a redessiner tout ou partie des parents et des enfants de se composant.

JuDePom

Damned, je suis obligé d’utiliser Swing :'(

Il y a la fonction paintImmediatly(x, y, w, h), qui repeint unique la zone demandée, mais je ne pense pas du tout que cela soit une bonne idée.

JuDePom

Pourquoi ce n’est pas une bonne idée ? C’est une mauvaise pratique que d’utiliser cette fonction ?

Ce n’est pas une bonne idée car swing n’est pas prévu pour une telle utilisation (enfin, ce n’est pas son but).

Je ne sais pas si l’on peut parler de "mauvaise pratique", disons qu’il est recommandé de savoir pourquoi on fait ça au lieu de laisser ça au composant.

Par contre, même le comportement par défaut ne devrait pas impacter tant que ça :/.

Si tu fais juste dessiner des sprites, tu devrais le faire dans une image, et demander au JPanel de juste dessiner l’image que tu as générée. Il me semble que c’est le mieux.

Je suis désolé, c’est la deuxième fois que je passe de résolu à non-résolu…

Mon problème initial subsiste : si je ne redessine pas par-dessus, j’obtiens bien les différentes étapes de déplacement superposées. Si je redessine par-dessus, j’obtiens l’état final. Mais si je met un Thread.sleep(500) entre les étapes, mon programme met juste une seconde à s’exécuter et je n’obtiens que l’état final, plutôt que l’effet d’animation comme souhaité…

J’ai l’impression que le problème vient de la manière dont j’ai imbriqué mes Panel. En effet, j’ai une fenêtre principale, ici :

 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
public class MainWindow extends JFrame implements Observer {
    public static final long serialVersionUID = 201801142359L;
    //private final MenuBar menuBar;
    private final MainPanel mainPanel;
    private AppModel appModel;

    public void update(Observable observable, Object parameter) {
        notifyForUpdate();
    }

    public void notifyForUpdate() {
        mainPanel.notifyForUpdate();
    }

    public AppModel getAppModel() {
        return this.appModel;
    }

    public void setAppModel(AppModel appModel) {
        this.appModel = appModel;
    }

    public MainWindow(String title, int width, int height) throws IOException {
        super(title);
        this.mainPanel = new MainPanel(this);
        this.appModel = new AppModel();
        this.setSize(width, height);
        this.setLocationRelativeTo(null);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setContentPane(mainPanel);
    }
}

Dont le conteneur principal est le MainPanel :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package ui;

import java.awt.BorderLayout;

import javax.swing.JPanel;

public class MainPanel extends JPanel {
    public static final long serialVersionUID = 201801142359L;
    private final MazePanel mazePanel;
    private final ButtonPanel buttonPanel;

    public void notifyForUpdate() {
        mazePanel.notifyForUpdate();
        buttonPanel.notifyForUpdate();
    }

    public MainPanel(MainWindow mainWindow) {
        super();
        this.setLayout(new BorderLayout());
        this.add(mazePanel = new MazePanel(mainWindow), BorderLayout.CENTER);
        this.add(buttonPanel = new ButtonPanel(mainWindow), BorderLayout.SOUTH);
        this.validate();
    }
}

Qui contient donc deux Panel, que sont le MazePanel qui contient le labyrinthe et le ButtonPanel qui contient les boutons. Le problème, c’est que si par exemple je met mon MazePanel en BordelLayout.East, il me l’affiche en dehors de la fenêtre. De même, je le mets ici au centre en théorie, mais il me l’affiche en haut à droite… Si je fais un mainWindow.pack(), le Panel des boutons reste inchangé mais celui du labyrinthe se retrouve écrasé.

De quoi cela peut-il venir ? Est-ce la taille du JPanel qui pose problème, comme si elle ne se mettait pas à jour en lui affichant uniquement des images ?

Concernant ton problème, il vient selon moi de tes "sleeps", enfin, pas directement.

En fait, tu demande de dessiner ton personnage, tu attends, et tu demandes de dessiner ton personnage différemment. Le problème, c’est que le rafraîchissement de ta fenêtre ne se fait probablement qu’a la fin de ta fonction. D’où le problème d’affichage.

JuDePom

Si tu n’as pas édité et que tu fais toujours - dessiner l’animation - sleep(500) - dessiner l’autre animation

effectivement, cela ne peux pas marcher.

Il faut que ta fonction paintCharacter ne dessine qu’une seule image a chaque appel, sinon, c’est forcément la dernière que tu auras.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte