简体   繁体   English

为什么我的自定义 Swing 组件在我移动鼠标时重绘速度更快? (爪哇)

[英]Why does my custom Swing component repaint faster when I move the mouse? (Java)

I am trying to make a 2D game with Java and Swing, and the window refreshes too slow.我正在尝试使用 Java 和 Swing 制作 2D 游戏,但窗口刷新速度太慢。 But if I move the mouse or press keys, the window refreshes as fast as it should!但是,如果我移动鼠标或按键,窗口会以应有的速度刷新!

Here is a GIF showing how the window refreshes quickly only when I move the mouse.这是一个 GIF,显示了仅当我移动鼠标时窗口如何快速刷新。

在此处输入图像描述

Why does the window refresh slowly like that?为什么窗口刷新这么慢? Why does the mouse and keyboard affect its refresh rate?为什么鼠标和键盘会影响其刷新率? How, if possible, do I make it refresh quickly all the time?如果可能的话,我如何让它一直快速刷新?

Background Info背景信息

I use a javax.swing.Timer to update the game state every 1/25 seconds, after which it calls repaint() on the game panel to redraw the scene.我使用javax.swing.Timer每 1/25 秒更新一次游戏状态,然后在游戏面板上调用repaint()来重绘场景。

I understand that a Timer might not always delay for exactly 1/25 of a second.我知道计时器可能并不总是延迟正好 1/25 秒。

I also understand that calling repaint() just requests the window to be repainted ASAP and does not repaint the window immediately.我也明白调用 repaint() 只是请求尽快重新绘制窗口,而不是立即重新绘制窗口。

My graphics card does not support OpenGL 2+ or hardware accelerated 3D graphics, which is why I am not using libgdx or JME for game development.我的显卡不支持 OpenGL 2+ 或硬件加速 3D 图形,这就是我不使用 libgdx 或 JME 进行游戏开发的原因。

System Info系统信息

  • Operating system: Linux Mint 19 Tara操作系统:Linux Mint 19 Tara
  • JDK version: OpenJDK 11.0.4 JDK版本:OpenJDK 11.0.4
  • Graphics card: Intel Corporation 82945G/GZ显卡:英特尔公司 82945G/GZ

Research研究

This Stack Overflow user describes the same problem I have, but the author reportedly solved the issue by calling repaint() repeatedly on a separate timer. 这个 Stack Overflow 用户描述了我遇到的同样的问题,但据报道作者通过在单独的计时器上重复调用 repaint() 解决了这个问题。 I tried this, and it does make the window refresh somewhat faster , but even then it is a slower than I want.我试过了,它确实使窗口刷新得更快了,但即使这样它也比我想要的慢。 In this case, wiggling the mouse on the window still improves the refresh rate .在这种情况下,在窗口上摆动鼠标仍然可以提高刷新率 Therefore, it seems like that post did not truly solve the issue.因此,该帖子似乎并没有真正解决问题。

Another Stack Overflow user also encountered the issue, but they use a continuous while-loop instead of a Timer for their game loop. 另一位 Stack Overflow 用户也遇到了这个问题,但他们在游戏循环中使用了连续的 while 循环而不是 Timer。 Apparently, this user solved the problem by using Thread.sleep() in their while loop.显然,这个用户通过在他们的 while 循环中使用 Thread.sleep() 解决了这个问题。 However, my code accomplishes the delay using a Timer, so I do not know how Thread.sleep() could solve my problem, or even where I would put it.但是,我的代码使用 Timer 来完成延迟,所以我不知道 Thread.sleep() 如何解决我的问题,甚至不知道我会把它放在哪里。

I've read through Painting with AWT and Swing to figure out whether I just misunderstood the concept of repainting, but nothing in that document elucidates the issue for me.我已经通读了用 AWT 和 Swing 进行绘画,以确定我是否误解了重新绘画的概念,但该文档中的任何内容都没有为我阐明这个问题。 I call repaint() whenever the game updates, and the window only refreshes quickly when mouse or keyboard input is happening.每当游戏更新时,我都会调用 repaint(),并且只有在发生鼠标或键盘输入时,窗口才会快速刷新。

I have searched the web for several days now trying to find an answer, but nothing seems to help!我已经在网上搜索了几天试图找到答案,但似乎没有任何帮助!

Code代码

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;
                        }
                }
        }
}

I guess you probably cannot solve your issue by enabling OpenGL , since your gpu does not support it, a possible silly workaround could be to fire a kind of event manually in each timer's iteration. 我猜您可能无法通过启用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();

(And the only place you can Thread.sleep() in a Swing Application is inside a SwingWorker's doInBackground method. If you call it in EDT the whole GUI will freeze since events cannot take place.) (而且,在Swing应用程序中唯一可以Thread.sleep()位置是SwingWorker的 doInBackground方法内。如果在EDT中调用它,则由于无法发生事件,整个GUI将会冻结。)

I ran into the same issue.我遇到了同样的问题。 The solution is quite simple.解决方案非常简单。 Call revalidate() immediately before or after you call repaint() :在调用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
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM