1
\$\begingroup\$

I've been working on a Java game, but I couldn't keep it from stuttering. I did some research, and found out that I was using a bad game loop. I've been working on a new game loop, which works a little better, but my framerate often jumps from 60 FPS to 62 FPS, and sometimes even crashes. This is the loop I'm using;

import java.applet.Applet;
import java.awt.Color;
import java.awt.Frame;

@SuppressWarnings("serial")
public class GameLoop extends Applet implements Runnable {

    final int FRAMERATE = 60;

    @Override
    public void init() {
        setSize(1280, 720);
        setBackground(Color.WHITE);
        setFocusable(true);
        Frame frame = (Frame) this.getParent().getParent();
        frame.setTitle("Game Loop");
    }

    @Override
    public void start() {
        Thread thread = new Thread(this);
        thread.start();
    }

    @Override
    public void stop() {

    }

    @Override
    public void destroy() {

    }

    @Override
    public void run() {
        int framerate = 0;
        long counter = 0;

        long lastTime = System.nanoTime();
        long frameTime = 1000000000 / FRAMERATE;

        while (true) {
            long currentTime = System.nanoTime();
            long updateTime = currentTime - lastTime;
            lastTime = currentTime;

            counter += updateTime;
            framerate++;

            if (counter >= 1000000000) {
                System.out.println(framerate + " fps");
                counter = 0;
                framerate = 0;
            }

            try {
                Thread.sleep((frameTime * 2 - updateTime) / 1000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

I've walked it through a couple of times, but I can't find out what's wrong. This is the console output I get;

60 fps
60 fps
60 fps
60 fps
62 fps
60 fps
62 fps
60 fps
60 fps
60 fps
... 
Exception in thread "Thread-3" java.lang.IllegalArgumentException: timeout value is negative
    at java.lang.Thread.sleep(Native Method)
    at GameLoop.run(GameLoop.java:58)
    at java.lang.Thread.run(Unknown Source)

As far as I can understand, the loop crashes because Thread.sleep((frameTime * 2 - updateTime) / 1000000); has a negative input, but I don't know why it is negative.

I've added;

if (updateTime < 0)
    updateTime = 0;

After;

long currentTime = System.nanoTime();
long updateTime = currentTime - lastTime;
lastTime = currentTime;

To prevent getting a negative input, but the problem still occurs. Also, I've tried to implement the loop into my game, but it's even laggier than before. The player seems to be shaking back and forth while moving.

Could anyone tell me what I'm doing wrong and how I can fix this?

\$\endgroup\$
13
  • \$\begingroup\$ Why (frameTime * 2 - updateTime) / 1000000 is negative? That's easy, it means that frameTime * 2 - updateTime is negative, which means that updateTime is greater than frameTime * 2. Therefore currentTime - lastTime is greater than frameTime * 2. That means that the iteration took more time than frameTime * 2. Why? Read about Thread Preemption. How to fix it? Compute the time to the next update and substract the current time, instead of working with the interval (the result should be dropped frames). Or just check if negative \$\endgroup\$
    – Theraot
    Commented Apr 14, 2017 at 15:05
  • \$\begingroup\$ It seems very possible that updateTime could be larger than frameTime * 2. Another process could be stealing more than that amount of time when you call Thread.sleep in the previous frame, causing the current updateTime to be unexpectedly large. \$\endgroup\$
    – Victor T.
    Commented Apr 14, 2017 at 15:05
  • \$\begingroup\$ Something bothered me about this, so after I got a cup of coffee I came back to look. Here's the thing: Your first calculation of updateTime will be nearly zero. This means you pass Thread.sleep almost frameTime * 2. This makes the second calculation of updateTime result in about frameTime * 2, which means you pass Thread.sleep just about 0. Your framerate will always oscillate this way while you use this logic. The only way it will stabilize is if your game update takes at least frameTime to run, and at that point you'd be too slow for your target FPS. \$\endgroup\$
    – Victor T.
    Commented Apr 14, 2017 at 15:33
  • 2
    \$\begingroup\$ You don't have to use sleep, you don't have to try stabilize the frames per second for that matter. You can do updates taking the elapsed time from the last frame into account and let the frame rate adapt however it wants. Note: the thread can and will be preempted even out of an sleep call. I have never been fond of game loops with sleep, which is why I don't feel prepared to give you an answer. You say you did try checking for negative, if when you don't call sleep when the parameter is negative you still get the same message, it means something else is going on, mind to share that version? \$\endgroup\$
    – Theraot
    Commented Apr 14, 2017 at 16:12
  • 1
    \$\begingroup\$ Thread.sleep(Math.Max((frameTime * 2 - updateTime), 0) / 1000000); anyone? \$\endgroup\$ Commented Apr 14, 2017 at 20:29

1 Answer 1

1
\$\begingroup\$

You get that error because frameTime * 2 - updateTime is negative. This happens whenever the previous update took longer than 1/30 seconds. This can happen for all kinds of reasons. Maybe your game mechanics were a bit more computationally complex in that tick than you expected. Or maybe the operating system decided to idle your game thread because it wanted to execute some other task. This just happens and your render loop needs to be able to deal with it.

Try this instead:

long sleepDuration = (frameTime - updateTime) / 1000000;
if (sleepDuration > 0) {
    Thread.sleep(sleepDuration);
}

An even better solution might be to decouple your render-framerate from your game update-framerate. This means that your game won't run slower when the user's machine isn't powerful enough to render at 60 fps. You can do that by keeping track of game-time and render-time separately. Pseudocode:

realTime = 0;
updatedTime = 0;
while (game is running) {
     realTime += time the last loop took;
     while (updatedTime < realTime) {
         updateGameMechanics();
         updateTime += time one tick is supposed to take
     }
     renderGraphics();
     sleep if necessary
}

A different method is to use a variable lockstep. Instead of performing multiple updates, pass the time since the last update to updateGameMechanics and keep that time into account for all game calculations. I personally prefer a fixed lockstep because that way you don't need to take the deltaTime into account for absolutely everything in your game mechanics, but this is more of a religious question.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .