为什么当我移动鼠标时,自定义的Swing组件重新绘制的速度更快?(Java)

use*_*097 6 java keyboard mouse swing repaint

我正在尝试使用Java和Swing制作2D游戏,并且窗口刷新太慢。但是,如果我移动鼠标或按键,则窗口刷新的速度应尽可能快!

这是一个GIF,显示仅当我移动鼠标时窗口如何快速刷新。

在此处输入图片说明

为什么窗口会像这样缓慢刷新?为什么鼠标和键盘会影响其刷新率?如果可能,如何使它始终保持快速刷新?

背景资料

我使用javax.swing.Timer每1/25秒更新一次游戏状态,然后在游戏面板上调用repaint()重新绘制场景。

我知道计时器可能不会总是延迟精确到1/25秒。

我也了解调用repaint()只是要求尽快重新绘制窗口,而不是立即重新绘制窗口。

我的图形卡不支持OpenGL 2+或硬件加速的3D图形,这就是为什么我不使用libgdx或JME进行游戏开发的原因。

系统信息

  • 操作系统:Linux Mint 19 Tara
  • JDK版本:OpenJDK 11.0.4
  • 显卡:英特尔公司82945G / GZ

研究

该Stack Overflow用户描述了我遇到的相同问题,但是据报道,作者通过在单独的计时器上反复调用repaint()解决了该问题。我试过了,它的确使窗口刷新有些快,但即使这样它也比我想要的要慢。在这种情况下,在窗口上摆动鼠标仍然可以提高刷新率。因此,该帖子似乎并没有真正解决问题。

另一个Stack Overflow用户也遇到了此问题,但是他们在游戏循环中使用连续的while循环而不是Timer。显然,此用户通过在while循环中使用Thread.sleep()解决了该问题。但是,我的代码使用Timer完成延迟,所以我不知道Thread.sleep()如何解决我的问题,甚至不知道该放在哪里。

我通读了《用AWT和Swing进行绘画》,以弄清是否误解了重新绘画的概念,但是该文档中没有任何内容可以为我阐明问题。每当游戏更新时,我都会调用repaint(),并且只有在发生鼠标或键盘输入时,窗口才会快速刷新。

我已经在网上搜索了几天,试图找到答案,但似乎无济于事!

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;
                        }
                }
        }
}
Run Code Online (Sandbox Code Playgroud)

Geo*_* Z. 1

我想您可能无法通过启用 OpenGL来解决您的问题,因为您的 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();
Run Code Online (Sandbox Code Playgroud)

Thread.sleep()( Swing 应用程序中唯一可以使用的位置是SwingWorker 的 doInBackground方法内部。如果您在 EDT 中调用它,整个 GUI 将冻结,因为事件无法发生。)