Partage
  • Partager sur Facebook
  • Partager sur Twitter

[MOUVEMENT SACCADE] Jeu tileMapping 2D

Quelques ProTips pour améliorer ca?

    17 mars 2011 à 15:30:01

    Bonjour!

    Je suis en train de faire un jeu 2D MarioBros_like, en gros. Mais le mouvement du personnage et de la map est assez saccadé, ce qui n'est pas esthétique et demande à s'améliorer. ^^

    Mon code n'est pas excessivement lourd, pourtant. Et, ayant les stats d'utilisation processus et mémoire sous les yeux, je vois que ma bécane gère ca sans broncher. Pour tout vous dire, j'affiche 24*32 BufferedImage, j'appelle quelques fonctions de test pour la collision et le scrolling, et basta.

    Les deux variables sur lesquelles je peux jouer sont le temps entre 2 rafraichissement, pour le moment gentiment mis à 20ms, et le nombre de pixels parcourus par le joueur à chaque déplacement.
    Meme en tripatouillant ces variables, j'ai soit un rendu saccadé, soit un déplacement trèèès lent (à 1px/rafraichissement, ca me fait 50px/s soit 2 tiles, c'est pas folichon...)

    Est-ce que vous auriez quelques idées ou méthodes pour améliorer ca? Je vous en serais gré!
    Merci d'avance!

    PS : J'utilise Swing.
    • Partager sur Facebook
    • Partager sur Twitter
    Anonyme
      17 mars 2011 à 18:13:56

      Si c'est du swing y a le double buffering à activer je crois, y a plusieurs post à ce sujet sur le forum.
      • Partager sur Facebook
      • Partager sur Twitter
        17 mars 2011 à 18:21:48

        je vais regarder ca, merci!
        • Partager sur Facebook
        • Partager sur Twitter
          24 mars 2011 à 13:54:05

          Bon bah j'ai pas trouvé de solution potable en fait, donc je me uppe.

          le doublebuf est apparemment activé par défaut dans swing. Et j'ai utilisé un Graphics 2D au lieu d'un Graphics pour blitter mes tiles et mon perso.

          C'est Swing qui est lent ou moi qui sait pas optimiser mon code?

          Hésitez pas à me demander le code, si besoin.

          Merci d'avance!
          • Partager sur Facebook
          • Partager sur Twitter
            24 mars 2011 à 15:09:19

            Active rendering

            http://download.oracle.com/javase/tuto [...] endering.html

            C'est une technique qui consiste à rendre tout en boucle, et non lorsque repaint() est appelé.
            Joli schéma - > http://www.gamedev.net/page/resources/ [...] ndering-r2418

            Voilà un code typique.


            import java.awt.Canvas;
            import java.awt.Graphics;
            import java.awt.image.BufferStrategy;
            
            
            public class GameCanvas extends Canvas implements Runnable {
               private int maxFps = 24;
               private long lastTime = -1;
               private int fps = 0, cfps = 0;
            
                public DesktopCanvas() {
            	setIgnoreRepaint(true);
                }
            
                public void handleEvents() {
                    // On vérifie les événements clavier, souris,...
                    // ...
                }
            
                public void render(Graphics g) {
                    // On dessine tout
            	// ...
                }
            
                public void run() {
                    // On cree un double buffer 
            	createBufferStrategy(2);
            	BufferStrategy bs = getBufferStrategy();
            
                    // Boucle principale
            	while (!Thread.interrupted()) {
            	   long currentTime = System.currentTimeMillis();
            	   fps++;
            
            	   if (currentTime - lastTime >= 1000)
            	       if (lastTime == -1)
            	           lastTime = currentTime;
            	       else {
            	           lastTime = currentTime;
            	           fps = cfps;
            	           cfps = 0;
                           }
            
            	    handleEvents();
            	    Graphics g = bs.getDrawGraphics();
            	    try {
            		render(g);
            	    } finally {
            		g.dispose();
            	    }
            	    if (!bs.contentsLost())
            		bs.show();
            
                        // On attends pour limiter l'usage du processeur
            	    if (maxFps > 0) {
            		long diff = System.currentTimeMillis() - currentTime;
            		if (diff * maxFps < 1000)
            		    try {
            			Thread.sleep((950 - diff * maxFps) / maxFps);
            		    } catch (InterruptedException e) {
            		    }
            	    }
            	}
                }
            }
            
            • Partager sur Facebook
            • Partager sur Twitter
              5 avril 2011 à 1:53:01

              Arf, j'avais pas vu ta réponse! Merci beaucoup, je me documenterai quand j'aurai un peu plus de temps.

              Mais, à vue d'oeil, ca implique de balancer Swing en bloc? Ou je peux garder mon code et rajouter Canvas juste pour l'affichage?
              Non parce que ca m'ennuie d'avoir à tout ré-implémenter, même si je m'en accomoderais.

              Merci en tout cas!
              • Partager sur Facebook
              • Partager sur Twitter
                5 avril 2011 à 11:33:40

                Non non tu peux faire le reste de ton application complètement normalement. Canvas étend de Component, donc tu peux l'ajouter où tu veux dans une fenêtre Swing.

                PS : Pour le code j'ai oublié de dire que tu dois créer un nouveau thread pour le lancer
                (new Thread(gameCanvas)).start();
                • Partager sur Facebook
                • Partager sur Twitter
                  5 avril 2011 à 20:27:31

                  Bon, j'ai pas eu assez de temps pour me mettre à fond dans ton code, mais a priori, 2 trucs m'échappent :
                  - Ta gestion du Thread.sleep(...) me parait éxagéremment compliquée, meme si ca doit etre pour une bonne raison. En fait, c'est surtout que je connais pas ta classe FpsTimer. C'est à toi ou ca appartient à un package queje connais pas? J'ai pas trouvé des masses de ressources à son sujet...
                  - Je vois pas comment gérer handleEvents... Mon jeu gérant la gravité, il faut régulièrement que je checke la position du perso, meme s'il n'a pas bougé, pour le faire tomber s'il est dans le vide. Raffraichir toutes les x ms reviendrait pourtant au meme qu'un JPanel.paintComponent(...) non?

                  Quoi qu'il en soit merci beaucoup, cette voie m'a l'air très intéressante!
                  • Partager sur Facebook
                  • Partager sur Twitter
                    5 avril 2011 à 21:17:14

                    Pour le FpsTimer, c'est un oubli du copié collé. J'ai remplacé dans le code original.

                    En fait un Thread.sleep(quelquesMillisecondes) suffit amplement, mais cette méthode à l'avantage de conserver un framerate plus ou moins fixe.

                    Pour le second point, la méthode paintComponent est appelée uniquement lorsque repaint() est appelé (lorsqu'un événement extérieur survient ou qu'une valeur est modifiée). C'est très utile pour un composant d'interface graphique comme un bouton ou une checkbox, mais un jeu change très souvent. Appeler repaint() avec un timer pourrait être une solution, mais repaint() est aussi appelé lors de certains évènements extérieurs, et cette méthode est typiquement bien plus lente.

                    Pour ce qui est la gestion des évènement utilisateurs, Java ne permet malheureusement pas d'input pooling directement.
                    Un bon article sur comment en faire un soit-même : http://www.gamedev.net/page/resources/ [...] d-mouse-r2439

                    Pour ce qui est de la gravité, ça advient généralement après les évènements utilisateurs et avant le rendu. tu peux par exemple créer une fonction update(), que tu appelle juste après handleEvent()

                    Pour la gestion physique, on utilise la seconde loi de newton (F=m.a), et sachant que comme l'intervalle entre deux update est assez cour, on se permet d'intégrer discrètement l'accélération pour avoir la vitesse, et la vitesse pour avoir la position.

                    Un petit bout de code vaut mieux qu'un long discours.

                    private long previousTime = System.currentTimeMillis();
                    private static final float g = 9.81f;
                    
                    // les propriétés du joueur
                    private float altitude;
                    private float vitesse;
                    
                    private void update() {
                        // on calcule le temps écoulé depuis le dernier update
                        long l = System.currentTimeMillis();
                        float t = (l - previousTime) * 0.001f;
                        previousTime = l;
                    
                        // altitude est une primitive de la vitesse, qui est elle-même une primitive de l'accélération g
                        altitude += t * vitesse;
                        vitesse += t * g;
                        
                        // encore faut-il vérifier qu'on ne touche pas le sol
                    }
                    
                    • Partager sur Facebook
                    • Partager sur Twitter
                      5 avril 2011 à 21:28:25

                      J'ai déjà une gestion de la gravité qui fonctionne comme il faut, et des déplacements de joueur au clavier potables aussi.
                      Mais c'est vrai que j'appelle repaint() dans un thread appelé lui-meme par un timer pour le moment, précédé par l'actualisation des coordonnées.

                      De cette manière j'étais sur de pas laisser mon perso voler, mais j'avais pas pensé à l'idée du booléen pour s'assurer que le repaint est nécessaire.

                      Je m'en vais voir ton lien. Merci encore!
                      • Partager sur Facebook
                      • Partager sur Twitter
                        5 avril 2011 à 21:41:25

                        Citation : Adrisaboss

                        Mais c'est vrai que j'appelle repaint() dans un thread appelé lui-meme par un timer pour le moment, précédé par l'actualisation des coordonnées.



                        De toute façon repaint ne repeins pas directement le composant, il "marque" le composant qui sera rendu plus tard dans un autre Thread. Cela étant, c'est une mauvaise méthode pour un jeu. Trop de threads j'ai envie de dire.

                        Si tu utilisais déjà un timer avant, les modifications dans ton code ne doivent pas être énormes, il suffit de taper tout ce qu'il y a avant le repaint() dans update.
                        • Partager sur Facebook
                        • Partager sur Twitter
                          6 avril 2011 à 15:48:14

                          J'ai regardé pour le polling, en fait j'ai déjà un fonctionnement assez semblable dans mon jeu. Mais c'est implémenté direct dans mes KeyPressed et keyReleased, donc c'est pas aussi joli...

                          En attendant je suis en train d'implémenter Canvas. Mais après avoir regardé ta méthode pour voir le nombre de FPS, j'ai voulu faire pareil sur ma vieille version... Bah avec un truc quand meme presque optimisé, qui affiche 32*24 tiles par affichages (+ le perso), je me retrouve à 9 FPS... C'est dur. Je vais voir ce que ca dit une fois Canvas mis...

                          EDIT : si ca intéresse du monde, la gestion du polling quand on n'a pas beaucoup de touches à gérer :
                          // Dans une classe implémentant KeyListener, j'admets que vous savez linker ca à un Component
                          public void keyPressed(KeyEvent event) {
                          	int action = event.getKeyCode();
                          	switch (action) {
                          		// Si on appuie sur la flèche droite, on avance vers la droite
                          		case KeyEvent.VK_RIGHT :
                          			// On ne change la direction que si le perso était tourné de l'autre coté
                          			if (!panneau.perso.isDroite()) {
                          				panneau.perso.setDroite(true);
                          				panneau.perso.setGauche(false);
                          				
                          				// On change l'image du perso, pour qu'il soit affiché dans le sens de son mouvement
                          				try {
                          					panneau.setImgPerso(ImageIO.read(new File(Constantes.PERSO_IMAGE[Constantes.PERSO_DIRECTION.RIGHT.ordinal()])));
                          				} catch (IOException e) {
                          					e.printStackTrace();
                          				}
                          			}
                          			break;
                          		// Si on appuie sur la flèche gauche, on avance vers la gauche
                          		case KeyEvent.VK_LEFT :
                          			if (!panneau.perso.isGauche()) {
                          				panneau.perso.setGauche(true);
                          				panneau.perso.setDroite(false);
                          				
                          				try {
                          					panneau.setImgPerso(ImageIO.read(new File(Constantes.PERSO_IMAGE[Constantes.PERSO_DIRECTION.LEFT.ordinal()])));
                          				} catch (IOException e) {
                          					e.printStackTrace();
                          				}
                          			}
                          			break;
                          	}
                          }
                          public void keyReleased(KeyEvent event) {
                          	// Quand on relache une touche, on met à false les booléens du mouvement
                          	switch(event.getKeyCode()) {
                          		case KeyEvent.VK_LEFT : 
                          		case KeyEvent.VK_RIGHT : 
                          			panneau.perso.setGauche(false);
                          			panneau.perso.setDroite(false);
                          			break;
                          	}
                          }
                          
                          // Et plus loin, un ActionListener Qu'on appelle avec un timer :
                          
                          tPerso = new Timer(Constantes.TIMER_LATENCY, timerPerso);
                          tPerso.start();
                          
                          ActionListener timerPerso = new ActionListener() {
                          	public void actionPerformed(ActionEvent arg0) {
                          		if (perso.isDroite()) perso.move(Constantes.PERSO_DIRECTION.RIGHT);
                          		else if (perso.isGauche()) perso.move(Constantes.PERSO_DIRECTION.LEFT);
                          		else perso.move(Constantes.PERSO_DIRECTION.NULL);
                          		
                          		repaint();
                          	}
                          };
                          


                          Bon, vous m'excuserez si c'est bourré de constantes incompréhensibles ou autres, mais ca donne une idée.

                          EDIT2 : J'ai fini mon implémentation de Canvas, et j'obtiens une limite à 20FPS. C'est quand meme un joyeux progrès, mais c'est toujours faible. Peut-etre que mon code est affreux, mais il me sembla pas qu'il le soit tant... Ya qu'à voir, quand je retire l'affichage des tiles je monte à 90FPS, alors que quand je retire le mouvement du perso, qui est un peu calculatoire, ca change rien.

                          Enfin, nt progrès en tout cas, merci bien! =)
                          • Partager sur Facebook
                          • Partager sur Twitter
                            6 avril 2011 à 17:55:13

                            J'ai déja codé en groupe un jeu en java2D/swing, ce qui est devenu la raison pour laquelle je ne veut plus jamais faire de java de ma vie, d'ailleurs. En plus de ce qu'as dit quarante-sept sur les thread de rendus, ces cons qui affichent ce qu'on leur donnent que quand ils veulent et qui empeche de faire des animations de sprite en synchronisant directement le rendu et le gameplay, j'ai eu aussi d'enorme probleme de perf, que j'ai résolu en affichant tous les tiles dans un sprite plus grand correspondant a la surface de l'ecran, et en affichant ce sprite une seule fois derriere les objet mobiles. Un décor précalculé en temps réel, en quelque sorte. Quand le décor se déplace, t'as qu'a updater astucieusement le megasprite (en rafraichissant les bon tiles et en décalant le précédent rendu pour n'updater qu'une colonne de tile a chaque fois). Ca devrait aider. Sinon, tu peut aussi trouver un vrai langage et une vraie lib 2D..
                            • Partager sur Facebook
                            • Partager sur Twitter
                              6 avril 2011 à 18:00:27

                              Merci pour l'astuce, je vais voir si c'est jouable.

                              Et à quel langage tu penses qui soit bien adapté à la prog 2D?

                              PS : Ca m'étonne quand meme de me retrouver bloqué par un rendu aussi simple... Quand je vois ce que des gens comme Notch font avec Java/OpenGL... C'est Swing qui est à ce point à chier?
                              • Partager sur Facebook
                              • Partager sur Twitter
                                6 avril 2011 à 18:32:51

                                "Et à quel langage tu penses qui soit bien adapté à la prog 2D?"

                                Le plus performant, mais ça n'engage que moi. Avec la SDL en C++ j'ai vu faire des miracles, des milliers de sprites affichés en même temps avec un framerate élevé sans probleme.. En couleur 16 bits c'est juste hallucinant, les perfs. Swing est une interface graphique, pas une lib de rendu haute performance. Je crois que java2D n'utilise pas l'acceleration materielle, d'ou sa lenteur. En openGL, tu en bénéficierai, on peut tout a fait faire de la 2D en openGL et il me semble, l'integrer à swing. Je crois qu'il existe un fork de SDL pour java, si tu tiens absolument a rester sur ce langage, tu pourrais te renseigner, a ton niveau t'as autre chose a faire que de l'optimisation a la sauvette juste parce que la lib est lente comme une tortue..
                                • Partager sur Facebook
                                • Partager sur Twitter
                                  6 avril 2011 à 19:13:33

                                  Ca tombe bien, j'ai un autre projet à faire en C++/SDL (portage de C/SDL, surtout). Mais c'est vrai que je fais de la C/SDL depuis un an, et meme si mes projets étaient gentillets, j'ai jamais vu un problème de performance...

                                  Enfin, merci de tes lumières. J'avoue que s'amuser à optimiser une misère de code parce qu'une lib n'est pas adaptée n'est pas super classe... C'est bien mignon de poser des rustines...
                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                  Anonyme
                                    6 avril 2011 à 20:23:34

                                    tu peux faire du java avec une lib native, je fais un jeu en java avec ogre3D comme moteur graphique, les perfs sont excellentes.
                                    • Partager sur Facebook
                                    • Partager sur Twitter

                                    [MOUVEMENT SACCADE] Jeu tileMapping 2D

                                    × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                    × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                    • Editeur
                                    • Markdown