簡體   English   中英

為什么我的自定義 Swing 組件在我移動鼠標時重繪速度更快? (爪哇)

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

我正在嘗試使用 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;
                        }
                }
        }
}

我猜您可能無法通過啟用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();

(而且,在Swing應用程序中唯一可以Thread.sleep()位置是SwingWorker的 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
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM