为什么这个游戏循环实现的帧率低于每次迭代进行更多计算的帧率?

Why is this game loop implementation getting a lower framerate than one that does more calculations per iteration?

我尝试使用 javax.swing 和 java.awt 创建我自己的 "game loop" 实现,但是当我的目标 fps 为 60 时,我的实现只有 49-52 fps ,而另一个实现获得了 59-61 fps。如果我的实现执行的计算较少,为什么它会获得较低的帧率?

我的实现:

public class MainContainer implements Runnable {

    public Thread thread;

    private final String title = "Window";

    private int width = 800, height = 600;
    private float scale = 1f;

    private GameWindow window;

    private final int FPS = 60;
    private final long NS_PER_UPDATE = (long)((1.0d/FPS) * 1000000000);
    private int current_total = 0;
    private boolean running;

    public MainContainer(){
        running = true;
        start();
    }

    public void start(){
        window = new GameWindow(this);
        thread = new Thread(this);
        thread.run();
    }
    public void stop(){

    }

    @Override
    public void run() {
        long old = System.nanoTime();
        long counterOld = System.nanoTime();

        long missedTime;

        double frames = 0;

        long current;
        long delta;
        long counterDelta;

        while(running){
            current = System.nanoTime();
            delta = current - old;
            counterDelta = current - counterOld;

            if(counterDelta >= 1000000000){
                System.out.println(frames / (counterDelta/1000000000.0));
                frames = 0;
                counterOld = System.nanoTime();
            }

            if(delta >= NS_PER_UPDATE){
                render();
                missedTime = delta - NS_PER_UPDATE;
                old = System.nanoTime() - missedTime;
                frames++;
            }else{
                try {
                    thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void render(){
        window.update();
    }

    public static void main(String[] args) {
        MainContainer main = new MainContainer();
    }

    public int getWidth(){
        return width;
    }

    public int getHeight(){
        return height;
    }

    public float getScale(){
        return scale;
    }

    public void setScale(float s){
        scale = s;
    }

    public String getTitle(){
        return title;
    }

    public GameWindow getWindow(){
        return window;
    }
}

其他实现:

public class MainContainer implements Runnable {

    public Thread thread;

    private final String title = "Window";

    private int width = 800, height = 600;
    private float scale = 1f;

    private final int FPS = 60;
    private final long NS_PER_UPDATE = (long)((1.0d/FPS) * 1000000000);
    private boolean running;

    private GameWindow window;

    public MainContainer(){
        running = true;
        start();
    }

    public void start(){
        window = new GameWindow(this);
        thread = new Thread(this);
        thread.start();
    }
    public void stop(){

    }

    @Override
    public void run() {

        long unprocessedTime = 0;
        long frameTime = 0;

        double frames = 0;

        boolean render = false;

        long current;
        long delta;
        long old = System.nanoTime();

        while(running){
            current = System.nanoTime();
            delta = current - old;
            old = current;

            unprocessedTime += delta;
            frameTime += delta;

            while(unprocessedTime >= NS_PER_UPDATE){
                old = System.nanoTime();
                unprocessedTime -= NS_PER_UPDATE;
                render = true;

                if(frameTime >= 1000000000){
                    System.out.println(frames / (frameTime/1000000000.0));
                    frameTime = 0;
                    frames = 0;
                }
            }
            if(render){
                render();
                frames++;
                render = false;
            }else{
                try {
                    thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }

    }

    public void render(){
        window.update();
    }

    public static void main(String[] args) {
        MainContainer main = new MainContainer();
    }

    public int getWidth(){
        return width;
    }

    public int getHeight(){
        return height;
    }

    public float getScale(){
        return scale;
    }

    public void setScale(float s){
        scale = s;
    }

    public String getTitle(){
        return title;
    }

    public GameWindow getWindow(){
        return window;
    }
}

游戏窗口Class:

import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;

public class GameWindow {

    private JFrame frame;
    private BufferedImage image;
    private Canvas canvas;
    private BufferStrategy bs;
    private Graphics g;
    private boolean blue;
    private MainContainer mc;

    public GameWindow(MainContainer mc) {
        this.mc = mc;
        image = new BufferedImage(mc.getWidth(), mc.getHeight(), BufferedImage.TYPE_INT_RGB);
        canvas = new Canvas();
        Dimension s = new Dimension((int)(mc.getWidth() * mc.getScale()), (int)(mc.getHeight() * mc.getScale()));
        canvas.setPreferredSize(s);
        canvas.setMinimumSize(s);
        canvas.setMaximumSize(s);

        frame = new JFrame(mc.getTitle());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new BorderLayout());
        frame.add(canvas, BorderLayout.CENTER);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setResizable(false);
        frame.setVisible(true);

        canvas.createBufferStrategy(2);
        bs = canvas.getBufferStrategy();
        g = bs.getDrawGraphics();
    }

    public void update(){

        int[] p = ((DataBufferInt)(this.getImage().getRaster().getDataBuffer())).getData();
        for(int i = 0; i < p.length; i++){
            p[i] += i;
        }

        g.drawImage(image, 0, 0, canvas.getWidth(), canvas.getHeight(), null);
        bs.show();
    }

    public BufferedImage getImage(){
        return image;
    }
}

因为在 "Other Implementation" 它计算渲染过程之前的旧时间,而在您的实现中它是在渲染过程之后计算它,这使得值更大,因为它现在还包括它花费的时间使成为。因此,如果您将 if 语句中的代码翻转成这样,它将 运行 以大约 60 fps:

if(delta >= NS_PER_UPDATE){
    missedTime = delta - NS_PER_UPDATE;
    old = System.nanoTime() - missedTime;
    render();
    frames++;
}