I am trying to make a 2D game with Java and Swing, and the window refreshes too slow. 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.
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?
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.
I understand that a Timer might not always delay for exactly 1/25 of a second.
I also understand that calling repaint() just requests the window to be repainted ASAP and does not repaint the window immediately.
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.
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. 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. Apparently, this user solved the problem by using Thread.sleep() in their while loop. 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.
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. I call repaint() whenever the game updates, and the window only refreshes quickly when mouse or keyboard input is happening.
I have searched the web for several days now trying to find an answer, but nothing seems to help!
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.
/* 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.)