我正在用 Java Swing 制作游戲。其中我有相當簡單的圖形,目前沒有那么多邏輯操作,所以我很困惑為什么我的幀時間從未低于 15 毫秒,即使我以最低限度運行它。
這是我的一個宣告Timer以及我之前提到的最低限度的呼叫。
logicTimer = new Timer(1, new TickTimer());
logicTimer.setInitialDelay(0);
...
class TickTimer implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Logic processing took " (System.currentTimeMillis()-previousTime) " ms");
tick();
previousTime = System.currentTimeMillis();
}
}
public void tick() {}
我也有一個基本相同的 Timer 用于渲染。我渲染到 JFrame 中包含的 JPanel
對于一些額外的背景,我在配備 GTX 1060 MaxQ、i7-7700HQ 和 16GB RAM 的游戲筆記本電腦 (Lenovo Y720) 上獲得了這些結果。我也有一臺類似規格的 PC(1070、7700K),如果我沒有記錯的話,它沒有這個問題,但我現在無法測驗它。筆記本電腦確實有 60Hz 的螢屏,但據我所知,這并不重要。
所以我嘗試Timer用最少的呼叫來運行我的 s,但 frameTime 仍然是 15 毫秒、16 毫秒、30 毫秒或 31 毫秒,即使我將延遲設定為最小 1 毫秒而沒有初始延遲。我嘗試減小游戲的視窗大小,但也沒有效果。我嘗試拔下連接到筆記本電腦的顯示幕,但問題仍然存在。我嘗試關閉任何后臺視窗并將 JDK 可執行檔案設定為 Windows 中的高和實時優先級,但無濟于事。
你可能會說“15ms?那還是 60 fps,有什么好抱怨的?” 但問題是我將不得不在幾個月后展示這款游戲,可能是在這臺筆記本電腦上,雖然現在性能還可以,但如果我添加復雜/大量的邏輯或視覺效果,那么一個峰值會立即引起注意。這是假設這 15-30 毫秒是基線,如果我有 1 毫秒的處理時間,也會導致 1 毫秒更高的基本延遲/幀時間。
在閱讀了有關此問題的一些先前問題后,他們中的許多人得出結論,這是一個固有且無法解決的問題,但更多現代執行緒討論了如何使用更新版本的 Swing 解決此問題,其中使用了更精確的計時器,因此來源現代硬體的問題是使用 32 位 JVM,但我很確定我運行的是 64 位,如System.getProperty("sun.arch.data.model"). 不確定是否相關,但我的 JDK 版本是“17.0.2”。
uj5u.com熱心網友回復:
裸骨示例,以 1 毫秒運行

頂部文本是計時器滴答之間的時間,中間是重繪之間的時間。
這表明重繪之間有 1 毫秒的延遲(它確實會反彈一點,因此可能是 1-3 毫秒)。我更喜歡使用 5 毫秒的延遲,因為執行緒調度會產生開銷,這可能使其......“難以”達到 1 毫秒的精度
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.Instant;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private Instant lastRepaintTick;
private Instant lastTick;
private JLabel label = new JLabel();
public TestPane() {
setLayout(new BorderLayout());
label.setHorizontalAlignment(JLabel.CENTER);
add(label, BorderLayout.NORTH);
Timer timer = new Timer(1, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (lastTick != null) {
label.setText(timeBetween(lastTick, Instant.now()));
}
repaint();
lastTick = Instant.now();
}
});
timer.start();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected String timeBetween(Instant then, Instant now) {
Duration duration = Duration.between(then, now);
String text = String.format("%ds.d", duration.toSecondsPart(), duration.toMillisPart());
return text;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (lastRepaintTick != null) {
Graphics2D g2d = (Graphics2D) g.create();
FontMetrics fm = g2d.getFontMetrics();
String text = timeBetween(lastRepaintTick, Instant.now());
int x = (getWidth() - fm.stringWidth(text)) / 2;
int y = (getHeight() - fm.getHeight()) / 2;
g2d.drawString(text, x, y fm.getAscent());
g2d.dispose();
}
lastRepaintTick = Instant.now();
}
}
}
請注意,這不是“最簡單”的作業流程,它做了很多作業,創建了一堆短命的物件,這些物件本身可能會拖累幀速率。這使我認為您的框架計算已關閉。
我也試過...
System.out.println("Logic processing took " (System.currentTimeMillis() - previousTime) " ms");
if (lastTick != null) {
label.setText(timeBetween(lastTick, Instant.now()));
}
repaint();
lastTick = Instant.now();
previousTime = System.currentTimeMillis();
列印 1-2 毫秒。但請注意System.out.println會增加額外的開銷,并可能會減慢您的更新速度
Java is going to be limited by the underlying hardware/software/driver/JVM implementation. Swing has been using DirectX or OpenGL pipelines for years (at least 5). If you're experiencing issues on one machine and not another, I'd be trying to explorer the differences between those two machines. I've had (lots) of issues with integrated video cards over the years and this tends to come down to the driver on the OS.
15ms is roughly 66fps, so it "could" be a delay in the rendering pipeline limited by the screen/driver/video hardware (guessing), but all my screens are running at 60hz and I don't have issues (I'm running 3 off my MacBook Pro), although I am running Java 16.0.2.
I've also been playing around with 
Canvas on top
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferStrategy;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.setLayout(new GridLayout(2, 1));
TestCanvas testCanvas = new TestCanvas();
frame.add(testCanvas);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
testCanvas.start();
}
});
}
private static final long TIME_DELAY = 5;
private static final Dimension PREFERRED_SIZE = new Dimension(200, 35);
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss.nnnnnnnnn");
protected String timeBetween(Instant then, Instant now) {
Duration duration = Duration.between(then, now);
String text = String.format("%ds.d", duration.toSecondsPart(), duration.toMillisPart());
return text;
}
public class TestPane extends JPanel {
private Instant lastRepaintTick;
private Instant lastTick;
public TestPane() {
setLayout(new BorderLayout());
Timer timer = new Timer((int) TIME_DELAY, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
repaint();
lastTick = Instant.now();
}
});
timer.start();
}
@Override
public Dimension getPreferredSize() {
return PREFERRED_SIZE;
}
protected String timeBetween(Instant then, Instant now) {
Duration duration = Duration.between(then, now);
String text = String.format("%ds.d", duration.toSecondsPart(), duration.toMillisPart());
return text;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (lastRepaintTick != null) {
Graphics2D g2d = (Graphics2D) g.create();
FontMetrics fm = g2d.getFontMetrics();
String text = timeBetween(lastRepaintTick, Instant.now());
int x = (getWidth() - fm.stringWidth(text)) / 2;
int y = 0;
g2d.drawString(text, x, y fm.getAscent());
text = LocalTime.now().format(TIME_FORMATTER);
x = (getWidth() - fm.stringWidth(text)) / 2;
y = fm.getHeight();
g2d.drawString(text, x, y fm.getAscent());
g2d.dispose();
}
lastRepaintTick = Instant.now();
}
}
public class TestCanvas extends Canvas {
private Thread thread;
public TestCanvas() {
thread = new Thread(new Runnable() {
@Override
public void run() {
createBufferStrategy(3);
Instant lastRepaintTick = null;
do {
BufferStrategy bs = getBufferStrategy();
while (bs == null) {
bs = getBufferStrategy();
}
do {
// The following loop ensures that the contents of the drawing buffer
// are consistent in case the underlying surface was recreated
do {
// Get a new graphics context every time through the loop
// to make sure the strategy is validated
Graphics2D g2d = (Graphics2D) bs.getDrawGraphics();
// Render to graphics
// ...
g2d.setColor(getBackground());
g2d.fillRect(0, 0, getWidth(), getHeight());
if (lastRepaintTick != null) {
FontMetrics fm = g2d.getFontMetrics();
String text = timeBetween(lastRepaintTick, Instant.now());
int x = (getWidth() - fm.stringWidth(text)) / 2;
int y = 0;
g2d.setColor(Color.BLACK);
g2d.drawString(text, x, y fm.getAscent());
text = LocalTime.now().format(TIME_FORMATTER);
x = (getWidth() - fm.stringWidth(text)) / 2;
y = fm.getHeight();
g2d.drawString(text, x, y fm.getAscent());
}
lastRepaintTick = Instant.now();
g2d.dispose();
// Repeat the rendering if the drawing buffer contents
// were restored
} while (bs.contentsRestored());
// Display the buffer
bs.show();
// Repeat the rendering if the drawing buffer was lost
} while (bs.contentsLost());
Instant tickTime = Instant.now();
try {
Thread.sleep(TIME_DELAY);
} catch (InterruptedException ex) {
}
} while (true);
}
});
}
public void start() {
thread.start();
}
@Override
public Dimension getPreferredSize() {
return PREFERRED_SIZE;
}
}
}
uj5u.com熱心網友回復:
您可能在 Windows 上運行該應用程式,其中默認計時器解析度為 15.6 毫秒。
當您以不超過 10 毫秒的間隔呼叫時, JDK 可以臨時增加系統計時器的解析度:Thread.sleep
HighResolutionInterval(jlong ms) {
resolution = ms % 10L;
if (resolution != 0) {
MMRESULT result = timeBeginPeriod(1L);
}
}
注意:這不是檔案中的功能,可能會在 JDK 的任何未來版本中進行更改。
Thread.sleep所以解決方法是啟動一個以奇數間隔呼叫的后臺執行緒:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class HiResTimer {
public static void enableHiResTimer() {
Thread thread = new Thread("HiResTimer") {
@Override
public void run() {
while (true) {
try {
sleep(Integer.MAX_VALUE);
} catch (InterruptedException ignore) {
}
}
}
};
thread.setDaemon(true);
thread.start();
}
public static void main(String[] args) throws Exception {
enableHiResTimer();
new javax.swing.Timer(1, new ActionListener() {
long lastTime;
@Override
public void actionPerformed(ActionEvent e) {
System.out.println((System.nanoTime() - lastTime) / 1e6);
lastTime = System.nanoTime();
}
}).start();
Thread.currentThread().join();
}
}
但是,這是一個 hack,我建議不要這樣做。較高的計時器解析度會增加 CPU 使用率和功耗。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/450062.html
上一篇:它不會畫線
