如何在 Java 中将帧速率限制为 60 fps?

发布于 2024-07-17 15:57:37 字数 60 浏览 5 评论 0原文

我正在编写一个简单的游戏,我想将帧速率限制在 60 fps,而不会让循环占用我的 CPU。 我该怎么做?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

紙鸢 2024-07-24 15:57:38

您可以阅读游戏循环文章。 在尝试实现任何内容之前,首先了解游戏循环的不同方法非常重要。

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.

萌吟 2024-07-24 15:57:38

我选择了@cherouvim 发布的游戏循环文章,并选择了“最佳”策略并尝试将其重写为 java Runnable,似乎对我有用

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

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);
    }
}
一杆小烟枪 2024-07-24 15:57:38

简单的答案是设置一个 java.util.Timer 每 17 毫秒触发一次,并在计时器事件中完成工作。

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

守不住的情 2024-07-24 15:57:38

这是我在 C++ 中的做法...我相信您可以适应它。

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

只需在每个循环中使用 capFrameRate(60) 调用一次即可。 它会休眠,因此不会浪费宝贵的周期。 glfwGetTime() 返回自程序启动以来的时间(以秒为单位)...我相信您可以在 Java 中找到等效的东西。

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.

窝囊感情。 2024-07-24 15:57:38

这里已经有很多好的信息,但我想分享我在这些解决方案中遇到的一个问题以及解决方案。 如果尽管有所有这些解决方案,您的游戏/窗口似乎会跳过(尤其是在 X11 下),请尝试每帧调用一次 Toolkit.getDefaultToolkit().sync()

There is a lot of good information here already, but I wanted to share one problem that I had with these solutions, and the solution to that. If, despite all these solutions, your game/window seems to skip (especially under X11), try calling Toolkit.getDefaultToolkit().sync() once per frame.

孤城病女 2024-07-24 15:57:38

以下是使用 Swing 并在不使用计时器的情况下获得相当准确的 FPS 的技巧。

首先,不要在事件调度程序线程中运行游戏循环,它应该在自己的线程中运行,完成艰苦的工作,并且不会妨碍 UI。

显示游戏的 Swing 组件应该重写此方法来绘制自身:

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

问题是游戏循环不知道事件调度程序线程何时真正开始执行绘制操作,因为在游戏循环中我们只调用 component.repaint( )只要求稍后可能发生的绘制。

使用等待/通知,您可以让重绘方法等待,直到请求的绘制发生,然后继续。

这是来自 https://github.com/davidmoten/game-exploration 的完整工作代码,它执行以下操作:使用上述方法在 JFrame 上简单移动字符串:

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

}

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

}
帝王念 2024-07-24 15:57:38

java中定时器控制FPS不准确。 我已经从二手资料中发现了这一点。 您将需要实现自己的计时器或执行有限制的实时 FPS。 但不要使用 Timer,因为它不是 100% 准确,因此无法正确执行任务。

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.

谢绝鈎搭 2024-07-24 15:57:38

我所做的就是继续循环,并跟踪我上次制作动画的时间。 如果已经至少 17 毫秒,则浏览动画序列。

这样我就可以检查任何用户输入,并根据需要打开/关闭音符。

但是,这是一个帮助向儿童教授音乐的程序,而我的应用程序由于全屏显示而占用了计算机,因此它与其他应用程序不能很好地配合。

What I have done is to just continue to loop through, and keep track of when I last did an animation. If it has been at least 17 ms then go through the animation sequence.

This way I could check for any user inputs, and turn musical notes on/off as needed.

But, this was in a program to help teach music to children and my application was hogging up the computer in that it was fullscreen, so it didn't play well with others.

眼角的笑意。 2024-07-24 15:57:38

如果您使用 Java Swing,实现 60 fps 的最简单方法是设置一个 javax.swing.Timer,如下所示,这是制作游戏的常见方法:

public static int DELAY = 1000/60;

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

并且您必须在代码中的某个位置设置此计时器以重复并启动它:

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

每秒有 1000 毫秒,通过将其除以您的 fps (60) 并设置具有此延迟的计时器(1000/60 = 16 毫秒向下舍入),您将获得稍微固定的帧速率。 我说“某种程度上”是因为这在很大程度上取决于您在上面的 updateModel() 和 repaintScreen() 调用中所做的事情。

为了获得更可预测的结果,请尝试在计时器中对两个调用进行计时,并确保它们在 16 毫秒内完成以保持 60 fps。 通过智能预分配内存、对象重用和其他功能,您还可以减少垃圾收集器的影响。 但这是另一个问题。

If you are using Java Swing the simplest way to achieve a 60 fps is by setting up a javax.swing.Timer like this and is a common way to make games:

public static int DELAY = 1000/60;

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

And somewhere in your code you must set this timer to repeat and start it:

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

Each second has 1000 ms and by dividing this with your fps (60) and setting up the timer with this delay (1000/60 = 16 ms rounded down) you will get a somewhat fixed framerate. I say "somewhat" because this depends heavily on what you do in the updateModel() and repaintScreen() calls above.

To get more predictable results, try to time the two calls in the timer and make sure they finish within 16 ms to uphold 60 fps. With smart preallocation of memory, object reuse and other things you can reduce the impact of the Garbage Collector also. But that is for another question.

静若繁花 2024-07-24 15:57:38

在java中,您可以使用System.currentTimeMillis()来获取以毫秒为单位的时间,而不是glfwGetTime()

Thread.sleep(time in milliseconds) 使线程等待,以防万一您不知道,并且它必须位于 try 块内。

In java you could do System.currentTimeMillis() to get the time in milliseconds instead of glfwGetTime().

Thread.sleep(time in milliseconds) makes the thread wait in case you don't know, and it must be within a try block.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文