简体   繁体   中英

Java Thread.sleep() causing JFrame to flicker when resized

So I have a game that I'm trying to make and in the game loop, I call Thread.sleep(). Else where, I have code that maintains the aspect ratio of the window when resizing. This works great, except that I get weird flickering when I'm resizing. I've narrowed the problem down to Thread.sleep(), when I take this line out, my program works just as expected, but this causes the CPU to spike so high that on my Macbook, the Activity Monitor app says my game is using 170+%! Now this is problematic and exactly why I put the sleep line in there anyway. I've heard that sleeping on the event dispatch thread will cause this effect, but I am running this loop in a new thread, so I thought I was good. Do you guys know what could be going on? Here's part of the source code (you really need to look at the run() method):

package jeffrey_ryan.game2d;

public class GameLoop implements Runnable {
    private boolean running = false;
    private boolean paused = false;
    private float gameHertz = 30.0f;
    private long timeBetweenUpdates = (long) (1_000_000_000 / gameHertz);
    private int maxUpdates = 5;
    private LoopListener loopListener;

    public void run() {
        long lastUpdateTime = System.nanoTime();
        running = true;

        while (running) {
            long now = System.nanoTime();
            if (!paused) {
                int updates = 0;

                while (now - lastUpdateTime >= timeBetweenUpdates && updates < maxUpdates) {
                    if (loopListener != null) {
                        loopListener.update((double) timeBetweenUpdates / 1_000_000_000);
                    }

                    lastUpdateTime += timeBetweenUpdates;
                    updates++;
                }

                if (loopListener != null) {
                    float interpolation = Math.min(1.0f, (float) (now - lastUpdateTime) / timeBetweenUpdates);
                    loopListener.render(interpolation);
                }

                long timeRemaining = (timeBetweenUpdates - (now - lastUpdateTime)) / 1_000_000;

                try {
                    Thread.sleep(Math.max(timeRemaining - 5, 0)); // HERE'S THE OFFENDING LINE ******************
                }
                catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
            }
            else {
                try {
                    Thread.sleep(25);
                }
                catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
            }
        }
    }

    public void start() {
        running = true;
    }

    public void stop() {
        running = false;
    }

    public void pause() {
        paused = true;
    }

    public void play() {
        paused = false;
    }

    public float getSpeed() {
        return gameHertz;
    }

    public void setSpeed(float hertz) {
        gameHertz = hertz;
        timeBetweenUpdates = (long) (1_000_000_000 / gameHertz);
    }

    public int getMaxUpdates() {
        return maxUpdates;
    }

    public void setMaxUpdates(int updates) { 
        maxUpdates = updates;
    }

    public void setLoopListener(LoopListener listener) {
        loopListener = listener;
    }
}

In my subclass of JPanel, here's the code that runs this loop (Where the loop variable is an instance of the above class):

@Override
public void addNotify() {
    super.addNotify();

    addKeyListener(this);
    addMouseListener(this);

    Thread thread = new Thread(loop, "GameLoop");
    thread.start();
}

If you guys could help me I would love it, I'm really stumped. Thanks!

You should use SwingWorker instead of a Thread to manipulate Swing components asynchronously. When I discovered this guy my life changed =). The SwingWorker gives you a method "process" which you can use to make actions gradually, and a "done" method to finish your processing, both of these methods are safe to handle the event dispatch thread. The background process you should make on "doInBackground".

Calling 'Thread.sleep(n)' causes the whole thread to become unresponsive, if this thread is tied to your JFrame thread then that thread will also become unresponsive and cause the whole frame and component to freeze and stop responding -- probably the reason for the flickering. So make sure the sleep is in game loop and not on the frame, one way to do this is create two threads at initialization, one for the frame and the other for the logic, then just let the game loop handle input and output while the display thread simply displays (i believe this how most game engines work). Also make sure neither thread is linked in any or the sleeping thread will affect the display thread.

I found the answer to my problem. The class that was calling the loop, which was a JPanel, didn't repaint when resized, only when the loop told it to, which caused some periods where the JPanel wasn't painted too. I fixed this by overriding paintComponent.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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