我正在尝试熟悉用 java 制作 GUI,并编写了一个程序,该程序应该根据 WASD 键输入在屏幕上移动一个方块(遵循在线教程)。
但是,运行时,似乎方块会闪烁并且“滞后”,有时渲染顺利,但大多数时候渲染不流畅。
这是我的 JPanel 类,这是主要内容发生的地方:
package me.analyzers.scs.game;
import me.analyzers.scs.utilities.KeyPressHandler;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MainPanel extends JPanel implements Runnable{
final int FPS = 240;
final int tileSize = 16;
final int scaling = 3;
final int realTileSize = tileSize * scaling;
final int widthX = 16;
final int widthY = 12;
final int screenWidth = realTileSize * widthX;
final int screenHeight = realTileSize * widthY;
Thread mainLoop;
KeyPressHandler keyPressHandler = new KeyPressHandler();
public MainPanel() {
super();
this.setPreferredSize(new Dimension(screenWidth, screenHeight));
this.setBackground(Color.WHITE);
this.addKeyListener(keyPressHandler);
this.setFocusable(true);
}
public void startGameLoop() {
mainLoop = new Thread(this);
mainLoop.start();
}
public boolean isRunning() {
return mainLoop != null;
}
int posX = 0;
int posY = 0;
@Override
public void run() {
long lastTime = 0;
while(isRunning()) {
long currentTime = System.currentTimeMillis();
if(currentTime-lastTime > 1000/FPS) {
lastTime = System.currentTimeMillis();
tick();
repaint();
}
}
}
public void tick() {
switch (keyPressHandler.getLastKey()) {
case 'w' -> posY-=1;
case 's' -> posY+=1;
case 'a' -> posX-=1;
case 'd' -> posX+=1;
}
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.BLACK);
g2d.fillRect(posX, posY, realTileSize, realTileSize);
g2d.dispose();
}
}
起初我认为这可能是并发问题引起的,所以我多次切换了“主循环”代码;我尝试通过检查 currentTime() 和 lastTime 有条件地执行它,我尝试执行 Thread.sleep,还尝试使用 swing 的 Timer 类。它们都产生相同的结果。
需要注意的一个有趣的事情是,如果我在 PaintComponent() 方法中调用 repaint() (我认为会导致循环),它会正常工作;但这会在每个游戏循环中调用paintComponent()数百次。
在网上搜索类似问题但无法修复后,我尝试在 Windows 虚拟机中运行相同的 jar 存档。令人惊讶的是,它渲染得很顺利!而且,方块在 Windows 机器上的移动速度似乎比我的机器上慢得多。这让我很困惑,因为我认为 JVM 应该在所有平台上保持一致。
编辑:我发现持续输入(这包括垃圾邮件点击窗口)会暂时停止奇怪的口吃。这让我想知道这是否不是由某些进程试图在我的 GUI 上“节省 CPU 时间”引起的?这个存在吗?
你的程序应该像这样开始:
class Program {
public static void main( String[] args ) {
EventQueue.invokeLater( () -> {
gui = new Gui();
});
}
}
class Gui extends JFrame {
public Gui() {
.....
.....
setVisible( true );
}
....
....
}
这样,你的整个程序将在事件线程上运行。
另一方面, setVisible( true ); 必须位于构造函数的最后一行(后面可以跟监听器赋值)。
通过进行这些更正,您的问题可能会消失。