Comment plafonner mon framerate à 60 fps en Java?

je suis en train d'écrire un jeu simple, et je veux plafonner mon framerate à 60 fps sans faire la boucle manger mon cpu. Comment puis-je faire?

13
demandé sur ojblass 2009-04-21 10:00:36

10 réponses

Vous pouvez lire les Article Sur La Boucle De Jeu. Il est très important que vous compreniez d'abord les différentes méthodologies pour la boucle de jeu avant d'essayer de mettre en œuvre quoi que ce soit.

32
répondu cherouvim 2018-09-27 08:07:12

j'ai pris le Article Sur La Boucle De Jeu que @cherouvim a posté, et j'ai pris la "meilleure" stratégie et tenté de la réécrire pour un Java Runnable, semble travailler pour moi

double interpolation = 0;
final int TICKS_PER_SECOND = 25;
final int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
final int MAX_FRAMESKIP = 5;

@Override
public void run() {
    double next_game_tick = System.currentTimeMillis();
    int loops;

    while (true) {
        loops = 0;
        while (System.currentTimeMillis() > next_game_tick
                && loops < MAX_FRAMESKIP) {

            update_game();

            next_game_tick += SKIP_TICKS;
            loops++;
        }

        interpolation = (System.currentTimeMillis() + SKIP_TICKS - next_game_tick
                / (double) SKIP_TICKS);
        display_game(interpolation);
    }
}
7
répondu Parth Mehrotra 2017-01-08 06:41:32

la réponse simple est de définir un java.util.Minuterie pour tirer tous les 17 ms et faire votre travail dans l'événement de minuterie.

5
répondu Lawrence Dol 2009-04-21 06:59:58

Voici comment je l'ai fait en C++... Je suis sûr que vous pouvez l'adapter.

void capFrameRate(double fps) {
    static double start = 0, diff, wait;
    wait = 1 / fps;
    diff = glfwGetTime() - start;
    if (diff < wait) {
        glfwSleep(wait - diff);
    }
    start = glfwGetTime();
}

il vous suffit d'appeler capFrameRate(60) une fois par boucle. Il dormira, pour ne pas gaspiller de précieux cycles. glfwGetTime() renvoie le temps écoulé depuis le démarrage du programme en secondes... Je suis sûr que vous pouvez trouver un équivalent en Java quelque part.

4
répondu mpen 2009-04-21 07:20:18

voici un conseil pour utiliser Swing et obtenir des FPS raisonnablement précis sans utiliser de minuterie.

tout d'abord ne pas lancer la boucle de jeu dans le thread event dispatcher, il devrait fonctionner dans son propre thread en faisant le travail dur et ne pas se mettre en travers de L'interface utilisateur.

le composant de Swing qui affiche le jeu doit se dessiner en outrepassant cette méthode:

@Override
public void paintComponent(Graphics g){
   // draw stuff
}

le hic, c'est que la boucle de jeu ne sait pas quand le thread event dispatcher a réellement tourné pour effectuer l'action de peinture parce que dans la boucle de jeu nous appelons juste le composant.repaint () qui demande une peinture seulement qui peut arriver plus tard.

en utilisant wait / notify vous pouvez obtenir la méthode de redessiner d'attendre jusqu'à ce que la peinture demandée est arrivée et puis continuer.

Voici de travail complète le code de https://github.com/davidmoten/game-exploration que fait un simple mouvement d'une chaîne de caractères dans un JFrame à l'aide de la méthode ci-dessus:

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Image;

import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * Self contained demo swing game for stackoverflow frame rate question.
 * 
 * @author dave
 * 
 */
public class MyGame {

    private static final long REFRESH_INTERVAL_MS = 17;
    private final Object redrawLock = new Object();
    private Component component;
    private volatile boolean keepGoing = true;
    private Image imageBuffer;

    public void start(Component component) {
        this.component = component;
        imageBuffer = component.createImage(component.getWidth(),
                component.getHeight());
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                runGameLoop();
            }
        });
        thread.start();
    }

    public void stop() {
        keepGoing = false;
    }

    private void runGameLoop() {
        // update the game repeatedly
        while (keepGoing) {
            long durationMs = redraw();
            try {
                Thread.sleep(Math.max(0, REFRESH_INTERVAL_MS - durationMs));
            } catch (InterruptedException e) {
            }
        }
    }

    private long redraw() {

        long t = System.currentTimeMillis();

        // At this point perform changes to the model that the component will
        // redraw

        updateModel();

        // draw the model state to a buffered image which will get
        // painted by component.paint().
        drawModelToImageBuffer();

        // asynchronously signals the paint to happen in the awt event
        // dispatcher thread
        component.repaint();

        // use a lock here that is only released once the paintComponent
        // has happened so that we know exactly when the paint was completed and
        // thus know how long to pause till the next redraw.
        waitForPaint();

        // return time taken to do redraw in ms
        return System.currentTimeMillis() - t;
    }

    private void updateModel() {
        // do stuff here to the model based on time
    }

    private void drawModelToImageBuffer() {
        drawModel(imageBuffer.getGraphics());
    }

    private int x = 50;
    private int y = 50;

    private void drawModel(Graphics g) {
        g.setColor(component.getBackground());
        g.fillRect(0, 0, component.getWidth(), component.getHeight());
        g.setColor(component.getForeground());
        g.drawString("Hi", x++, y++);
    }

    private void waitForPaint() {
        try {
            synchronized (redrawLock) {
                redrawLock.wait();
            }
        } catch (InterruptedException e) {
        }
    }

    private void resume() {
        synchronized (redrawLock) {
            redrawLock.notify();
        }
    }

    public void paint(Graphics g) {
        // paint the buffered image to the graphics
        g.drawImage(imageBuffer, 0, 0, component);

        // resume the game loop
        resume();
    }

    public static class MyComponent extends JPanel {

        private final MyGame game;

        public MyComponent(MyGame game) {
            this.game = game;
        }

        @Override
        protected void paintComponent(Graphics g) {
            game.paint(g);
        }
    }

    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                MyGame game = new MyGame();
                MyComponent component = new MyComponent(game);

                JFrame frame = new JFrame();
                // put the component in a frame

                frame.setTitle("Demo");
                frame.setSize(800, 600);

                frame.setLayout(new BorderLayout());
                frame.getContentPane().add(component, BorderLayout.CENTER);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setVisible(true);

                game.start(component);
            }
        });
    }

}
2
répondu Dave Moten 2015-05-11 23:26:57

Timer est inexact pour le contrôle des FPS en java. Je l'ai découvert d'occasion. Vous aurez besoin de mettre en place votre propre timer ou de faire des FPS en temps réel avec des limitations. Mais n'utilisez pas la minuterie car elle n'est pas précise à 100%, et ne peut donc pas exécuter les tâches correctement.

1
répondu abc123 2011-04-19 03:37:39

ce que j'ai fait, c'est continuer à boucler la boucle, et garder une trace de quand j'ai fait une animation pour la dernière fois. Si elle a été au moins 17 ms alors passer par la séquence d'animation.

de cette façon, je pourrais vérifier les entrées des utilisateurs et activer/désactiver les notes musicales au besoin.

mais, c'était dans un programme pour aider à enseigner la musique aux enfants et mon application monopolisait l'ordinateur en ce qu'il était plein écran, donc il ne jouait pas bien avec les autres.

0
répondu James Black 2009-11-04 01:46:11

si vous utilisez Java Swing, le moyen le plus simple d'atteindre un 60 fps est de configurer un javax.swing.Minuteur de ce genre et est une façon courante de faire des jeux:

public static int DELAY = 1000/60;

Timer timer = new Timer(DELAY,new ActionListener() {
    public void actionPerformed(ActionEvent event) 
    {
      updateModel();
      repaintScreen();
    }
});

et quelque part dans votre code, vous devez régler cette minuterie pour la répéter et la démarrer:

timer.setRepeats(true);
timer.start();

chaque seconde a 1000 ms et en divisant cela avec votre fps (60) et en mettant en place la minuterie avec ce délai (1000/60 = 16 ms arrondi vers le bas) vous obtiendrez un framerate quelque peu fixe. Je dis "un peu", parce que cette cela dépend fortement de ce que vous faites dans les appels updateModel() et repaintScreen() ci-dessus.

pour obtenir des résultats plus prévisibles, essayez de chronométrer les deux appels dans le minuteur et assurez-vous qu'ils finissent dans les 16 ms pour maintenir 60 fps. Avec la préallocation intelligente de la mémoire, la réutilisation d'objet et d'autres choses, vous pouvez réduire l'impact du collecteur D'ordures aussi. Mais c'est une autre question.

0
répondu Johncl 2010-08-31 07:11:27

en java vous pouvez faire System.currentTimeMillis() pour obtenir le temps en millisecondes au lieu de glfwGetTime().

Thread.sleep(time in milliseconds) fait attendre le thread dans le cas où vous ne savez pas, et il doit être dans un try bloc.

0
répondu Anonymous 2011-11-09 19:12:29

Il y a déjà beaucoup de bonnes informations ici, mais je voulais partager un problème que j'avais avec ces solutions, et la solution à cela. Si, malgré toutes ces solutions, votre fenêtre / jeu semble sauter (surtout sous X11), essayez d'appeler Toolkit.getDefaultToolkit().sync() une fois par image.

0
répondu zrneely 2015-03-25 13:04:18