为什么我的自定义 Swing 组件在我移动鼠标时重新绘制得更快? (Java)
Why does my custom Swing component repaint faster when I move the mouse? (Java)
我正在尝试使用 Java 和 Swing 制作 2D 游戏,但 window 刷新速度太慢。但是,如果我移动鼠标或按键,window 会尽可能快地刷新!
这是一个 GIF,展示了 window 如何仅在我移动鼠标时快速刷新。
为什么window刷新这么慢?为什么鼠标和键盘会影响它的刷新率?如果可能的话,如何让它一直快速刷新?
背景信息
我在游戏面板上用javax.swing.Timer to update the game state every 1/25 seconds, after which it calls repaint()重绘场景
我知道计时器可能不会总是延迟恰好 1/25 秒。
我也明白调用 repaint() 只是请求尽快重绘 window 而不是立即重绘 window。
我的显卡不支持 OpenGL 2+ 或硬件加速 3D 图形,这就是我不使用 libgdx 或 JME 进行游戏开发的原因。
系统信息
- 操作系统:LinuxMint 19 Tara
- JDK版本:打开JDK11.0.4
- 显卡:Intel Corporation 82945G/GZ
研究
描述了我遇到的相同问题,但据报道作者通过在单独的计时器上重复调用 repaint() 解决了这个问题。我试过了,它 确实使 window 刷新得更快 ,但即便如此它也比我想要的慢。在这种情况下,在 window 上摆动鼠标仍然可以提高刷新率 。所以,看来post并没有真正解决问题。
Another Stack Overflow user 也遇到了这个问题,但他们在游戏循环中使用连续的 while 循环而不是 Timer。显然,这个用户通过在他们的 while 循环中使用 Thread.sleep() 解决了这个问题。但是,我的代码使用计时器完成延迟,所以我不知道 Thread.sleep() 如何解决我的问题,甚至不知道我会把它放在哪里。
我通读了 Painting with AWT and Swing 以确定我是否只是误解了重绘的概念,但该文档中没有任何内容为我阐明问题。每当游戏更新时,我都会调用 repaint(),并且 window 仅在发生鼠标或键盘输入时快速刷新。
我已经在网上搜索了几天试图找到答案,但似乎没有任何帮助!
代码
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.Color;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
class Game {
public static final int screenWidth = 160;
public static final int screenHeight = 140;
/**
* Create and show the GUI.
*/
private static void createAndShowGUI() {
/* Create the GUI. */
JFrame frame = new JFrame("Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.getContentPane().add(new GamePanel());
frame.pack();
/* Show the GUI. */
frame.setVisible(true);
}
/**
* Run the game.
*
* @param args the list of command-line arguments
*/
public static void main(String[] args) {
/* Schedule the GUI to be created on the EDT. */
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
}
/**
* A GamePanel widget updates and shows the game scene.
*/
class GamePanel extends JPanel {
private Square square;
/**
* Create a game panel and start its update-and-draw cycle
*/
public GamePanel() {
super();
/* Set the size of the game screen. */
setPreferredSize(
new Dimension(
Game.screenWidth,
Game.screenHeight));
/* Create the square in the game world. */
square = new Square(0, 0, 32, 32, Square.Direction.LEFT);
/* Update the scene every 40 milliseconds. */
Timer timer = new Timer(40, (e) -> updateScene());
timer.start();
}
/**
* Paint the game scene using a graphics context.
*
* @param g the graphics context
*/
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
/* Clear the screen. */
g.setColor(Color.WHITE);
g.fillRect(0, 0, Game.screenWidth, Game.screenHeight);
/* Draw all objects in the scene. */
square.draw(g);
}
/**
* Update the game state.
*/
private void updateScene() {
/* Update all objects in the scene. */
square.update();
/* Request the scene to be repainted. */
repaint();
}
}
/**
* A Square is a game object which looks like a square.
*/
class Square {
public static enum Direction { LEFT, RIGHT };
private int x;
private int y;
private int width;
private int height;
private Direction direction;
/**
* Create a square game object.
*
* @param x the square's x position
* @param y the square's y position
* @param width the square's width (in pixels)
* @param height the square's height (in pixels)
* @param direction the square's direction of movement
*/
public Square(int x,
int y,
int width,
int height,
Direction direction) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.direction = direction;
}
/**
* Draw the square using a graphics context.
*
* @param g the graphics context
*/
public void draw(Graphics g) {
g.setColor(Color.RED);
g.fillRect(x, y, width, height);
g.setColor(Color.BLACK);
g.drawRect(x, y, width, height);
}
/**
* Update the square's state.
*
* The square slides horizontally
* until it reaches the edge of the screen,
* at which point it begins sliding in the
* opposite direction.
*
* This should be called once per frame.
*/
public void update() {
if (direction == Direction.LEFT) {
x--;
if (x <= 0) {
direction = Direction.RIGHT;
}
} else if (direction == Direction.RIGHT) {
x++;
if (x + width >= Game.screenWidth) {
direction = Direction.LEFT;
}
}
}
}
我猜你可能无法通过 解决你的问题,因为你的 gpu 不支持它,一个可能愚蠢的解决方法是在每个计时器的迭代中手动触发一种事件。
/* Update the scene every 40 milliseconds. */
final Robot robot = new Robot();
Timer timer = new Timer(40, (e) -> {
robot.mouseRelease(0); //some event
updateScene();
});
timer.start();
(在 Swing 应用程序中,您唯一可以 Thread.sleep()
的地方是在 SwingWorker's doInBackground
方法中。如果您在 EDT 中调用它,整个 GUI 将冻结,因为事件无法发生。)
我运行遇到了同样的问题。解决方案非常简单。调用 repaint()
后立即调用 revalidate()
:
private void updateScene() {
/* Update all objects in the scene. */
square.update();
/* Request the scene to be repainted. */
repaint();
revalidate(); // <-- this will now repaint as fast as you wanted it to
}
我正在尝试使用 Java 和 Swing 制作 2D 游戏,但 window 刷新速度太慢。但是,如果我移动鼠标或按键,window 会尽可能快地刷新!
这是一个 GIF,展示了 window 如何仅在我移动鼠标时快速刷新。
为什么window刷新这么慢?为什么鼠标和键盘会影响它的刷新率?如果可能的话,如何让它一直快速刷新?
背景信息
我在游戏面板上用javax.swing.Timer to update the game state every 1/25 seconds, after which it calls repaint()重绘场景
我知道计时器可能不会总是延迟恰好 1/25 秒。
我也明白调用 repaint() 只是请求尽快重绘 window 而不是立即重绘 window。
我的显卡不支持 OpenGL 2+ 或硬件加速 3D 图形,这就是我不使用 libgdx 或 JME 进行游戏开发的原因。
系统信息
- 操作系统:LinuxMint 19 Tara
- JDK版本:打开JDK11.0.4
- 显卡:Intel Corporation 82945G/GZ
研究
Another Stack Overflow user 也遇到了这个问题,但他们在游戏循环中使用连续的 while 循环而不是 Timer。显然,这个用户通过在他们的 while 循环中使用 Thread.sleep() 解决了这个问题。但是,我的代码使用计时器完成延迟,所以我不知道 Thread.sleep() 如何解决我的问题,甚至不知道我会把它放在哪里。
我通读了 Painting with AWT and Swing 以确定我是否只是误解了重绘的概念,但该文档中没有任何内容为我阐明问题。每当游戏更新时,我都会调用 repaint(),并且 window 仅在发生鼠标或键盘输入时快速刷新。
我已经在网上搜索了几天试图找到答案,但似乎没有任何帮助!
代码
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.Color;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
class Game {
public static final int screenWidth = 160;
public static final int screenHeight = 140;
/**
* Create and show the GUI.
*/
private static void createAndShowGUI() {
/* Create the GUI. */
JFrame frame = new JFrame("Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.getContentPane().add(new GamePanel());
frame.pack();
/* Show the GUI. */
frame.setVisible(true);
}
/**
* Run the game.
*
* @param args the list of command-line arguments
*/
public static void main(String[] args) {
/* Schedule the GUI to be created on the EDT. */
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
}
/**
* A GamePanel widget updates and shows the game scene.
*/
class GamePanel extends JPanel {
private Square square;
/**
* Create a game panel and start its update-and-draw cycle
*/
public GamePanel() {
super();
/* Set the size of the game screen. */
setPreferredSize(
new Dimension(
Game.screenWidth,
Game.screenHeight));
/* Create the square in the game world. */
square = new Square(0, 0, 32, 32, Square.Direction.LEFT);
/* Update the scene every 40 milliseconds. */
Timer timer = new Timer(40, (e) -> updateScene());
timer.start();
}
/**
* Paint the game scene using a graphics context.
*
* @param g the graphics context
*/
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
/* Clear the screen. */
g.setColor(Color.WHITE);
g.fillRect(0, 0, Game.screenWidth, Game.screenHeight);
/* Draw all objects in the scene. */
square.draw(g);
}
/**
* Update the game state.
*/
private void updateScene() {
/* Update all objects in the scene. */
square.update();
/* Request the scene to be repainted. */
repaint();
}
}
/**
* A Square is a game object which looks like a square.
*/
class Square {
public static enum Direction { LEFT, RIGHT };
private int x;
private int y;
private int width;
private int height;
private Direction direction;
/**
* Create a square game object.
*
* @param x the square's x position
* @param y the square's y position
* @param width the square's width (in pixels)
* @param height the square's height (in pixels)
* @param direction the square's direction of movement
*/
public Square(int x,
int y,
int width,
int height,
Direction direction) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.direction = direction;
}
/**
* Draw the square using a graphics context.
*
* @param g the graphics context
*/
public void draw(Graphics g) {
g.setColor(Color.RED);
g.fillRect(x, y, width, height);
g.setColor(Color.BLACK);
g.drawRect(x, y, width, height);
}
/**
* Update the square's state.
*
* The square slides horizontally
* until it reaches the edge of the screen,
* at which point it begins sliding in the
* opposite direction.
*
* This should be called once per frame.
*/
public void update() {
if (direction == Direction.LEFT) {
x--;
if (x <= 0) {
direction = Direction.RIGHT;
}
} else if (direction == Direction.RIGHT) {
x++;
if (x + width >= Game.screenWidth) {
direction = Direction.LEFT;
}
}
}
}
我猜你可能无法通过
/* Update the scene every 40 milliseconds. */
final Robot robot = new Robot();
Timer timer = new Timer(40, (e) -> {
robot.mouseRelease(0); //some event
updateScene();
});
timer.start();
(在 Swing 应用程序中,您唯一可以 Thread.sleep()
的地方是在 SwingWorker's doInBackground
方法中。如果您在 EDT 中调用它,整个 GUI 将冻结,因为事件无法发生。)
我运行遇到了同样的问题。解决方案非常简单。调用 repaint()
后立即调用 revalidate()
:
private void updateScene() {
/* Update all objects in the scene. */
square.update();
/* Request the scene to be repainted. */
repaint();
revalidate(); // <-- this will now repaint as fast as you wanted it to
}