Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I cap my framerate at 60 fps in Java?

I am writting a simple game, and I want to cap my framerate at 60 fps without making the loop eat my cpu. How would I do this?

like image 445
William Avatar asked Apr 21 '09 06:04

William


6 Answers

You can read the Game Loop Article. It's very important that you first understand the different methodologies for the game loop before trying to implement anything.

like image 74
cherouvim Avatar answered Nov 16 '22 14:11

cherouvim


I took the Game Loop Article that @cherouvim posted, and I took the "Best" strategy and attempted to rewrite it for a java Runnable, seems to be working for me

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);
    }
}
like image 36
Parth Mehrotra Avatar answered Nov 16 '22 13:11

Parth Mehrotra


The simple answer is to set a java.util.Timer to fire every 17 ms and do your work in the timer event.

like image 42
Lawrence Dol Avatar answered Nov 16 '22 13:11

Lawrence Dol


Here's how I did it in C++... I'm sure you can adapt it.

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

Just call it with capFrameRate(60) once per loop. It will sleep, so it doesn't waste precious cycles. glfwGetTime() returns the time since the program started in seconds... I'm sure you can find an equivalent in Java somewhere.

like image 25
mpen Avatar answered Nov 16 '22 15:11

mpen


Timer is inaccurate for controlling FPS in java. I have found that out second hand. You will need to implement your own timer or do real time FPS with limitations. But do not use Timer as it's not 100% accurate, and therefore cannot execute tasks properly.

like image 2
abc123 Avatar answered Nov 16 '22 14:11

abc123


Here's a tip for using Swing and getting reasonably accurate FPS without using a Timer.

Firstly don't run the game loop in the event dispatcher thread, it should run in its own thread doing the hard work and not getting in the way of the UI.

The Swing component displaying the game should draw itself overriding this method:

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

The catch is that the game loop doesn't know when the event dispatcher thread has actually got round to performing the paint action because in the game loop we just call component.repaint() which requests a paint only that may happen later.

Using wait/notify you can get the redraw method to wait until the requested paint has happened and then continue.

Here's complete working code from https://github.com/davidmoten/game-exploration that does a simple movement of a string across a JFrame using the method above:

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);
            }
        });
    }

}
like image 3
Dave Moten Avatar answered Nov 16 '22 14:11

Dave Moten