views:

998

answers:

7

Hello, I'm developing a simple 2D game in Java, everything works fine. To find the correct FPS refresh/redraw/update, I used currentTimeMillis to find the difference.

The problem is that currentTimeMillis sometimes returns negative values, and the Thread.sleep will throw exception (java.lang.IllegalArgumentException: timeout value is negative)

What I did was to put a while in my game and while currentTimeMillis <= -1 check again until its over, then sleep.

Code sample:

private void gameLoop(){
 // Find FPS
 long FPS = 40;
 long beginTime = System.currentTimeMillis();
 while(beginTime < -1){
  beginTime = System.currentTimeMillis();
 }
 while(!done){
  // Sleep
  try{ 
   beginTime += FPS;
   long currTime = System.currentTimeMillis();
   while(currTime < -1){
    currTime = System.currentTimeMillis();
   }
   difference = (beginTime - currTime);
                           // Should be (currTime - beginTime) 

   Thread.sleep(difference); 
  }catch (Exception e){ 
   e.printStackTrace();
  }
  // RENDER GAME
  renderGame();
 }
 JOptionPane.showMessageDialog(null, "Game Over\nYou Died.");
 System.exit(0);
}// end gameLoop()

When the game starts, this works fine, but sometimes I still get the Exception. Is there a better way? I still think it´s strange that it´s returning a negative value.

+9  A: 

Isn't the real problem that beginTime-currTime is negative ?

difference = (beginTime - currTime);
Thread.sleep(difference);

I note that you add FPS (40) to beginTime. But if currentTime is greater than beginTime+FPS, you're going to have problems.

If you're trying to schedule something at regular intervals (which I think you are), check out Timer, which will allow you to do this more simply/reliably.

Brian Agnew
Ah, yes of course ! But now the game is way over 200 FPS. It goes up to 5000+.
Millad Dagdoni
+1  A: 

If currentTimeMillis returns negative value, then something is wrong with your JVM/OS, or memory corrupted etc.

stepancheg
That or you're running a computer with a JVM sometime before 1970.
Joachim Sauer
+3  A: 

imho the following instruction:

difference = (beginTime - currTime);

should be:

difference = (currTime - beginTime);

currTime is always greater than beginTime, so difference is always negative or at least 0.

dfa
A: 

for calculation of fps values i would recommend using

System.nanoTime()

it is way more accurate. the drawback is it gets you only realtive values, which does not matter at all when doing fps calcuation.

1 millisecond consists of 1000000 nanoseconds.

the resolution of currentTimeMillis is usually only 16ms.

Andreas Petersson
This is what I have used, but the game lags: long count1 = System.nanoTime( ); long count2 = System.nanoTime( ); long diff = (count2 - count1); // in nanosecondsDo you have a sample?
Millad Dagdoni
Depends. nanoTime offers higher precision but it may be less reliable. That's what I found: http://www.techper.net/2008/08/10/systemcurrenttimemillis-systemnanotime-and-their-resolution/
Andreas_D
+1  A: 

There is a conceptual problem in your code: at one point you add a framerate (Frames per second, FPS) to a time. Or you really want to add 40 milliseconds, but then you shouldn't name the variable FPS because this will lead to misunderstandings.

Assuming FPS is really frames per second, then you should use another value for your calculation:

long milliSecondsPerFrame = 1000 / FPS;

System.getCurrentTimeInMillis() will never return negative values (or at least not before Aug, 17, 08:12:55 - in the year 292.278.944 ;) ) *)

So you can safely remove the checks for negative currentTimeMillis() for real time usages.

The name of the beginTime variable leads to another misunderstanding. This is not the timestamp of some kind of beginning. In fact its the time when you expect the next frame update. So you should name it different, maybe 'nextFrameUpdateTime'. That would make clear, that the calculation of the difference is actually

difference = nextFrameUpdateTime - currentTime;

is correct and should return a positive value - as long as your sure, that you didn't miss a frame update ('you're too late'). And I think, that's the problem: a negative difference just indicates, that the renderGame() method took more time than the secondPerFrame value, so at some point during your game you just missed an update, the currentTime is bigger then the proposed nextFrameUpdateTime and you have the exception.

*) Tested with

Date date = new Date(Long.MAX_VALUE);
Andreas_D
A: 

It seems like this worked better.

private void gameLoop(){
 // Find FPS
 long nextFrameUpdateTime = System.currentTimeMillis();
 long milliSecondsPerFrame = 1000 / 60;
 while(!done){
  // Sleep
  try{ 
      long currentTime = System.currentTimeMillis();
   currentTime += milliSecondsPerFrame;

   difference = ( currentTime - nextFrameUpdateTime );
   Thread.sleep(difference); 

  }catch (Exception e){ e.printStackTrace(); }

  nextFrameUpdateTime = System.currentTimeMillis();
  renderGame();

 } // end While
 JOptionPane.showMessageDialog(null, "Game Over\nYou Died.");
 System.exit(0);
}// end gameLoop()

Thank you for your help everyone.

Millad Dagdoni
This isn't right, you're not accounting for how long it takes to call renderGame. In fact, you're adding that time to your sleep. So after each frame you're actually sleeping for how long it took to call renderGame, plus milliSecondsPerFrame.
CodeGoat
I'm having trouble understanding how to generally find how long it takes to call renderGame. Would you please explain more. I feel quite lost.
Millad Dagdoni
I posted a separate answer with some code.
CodeGoat
I would add a check for difference: "if (difference > 0) Thread.sleep(difference);"
Carlos Heuberger
A: 

If you're trying to set a specific frame rate, here's what it should look like:

long msPerFrame = 1000 / 60;
long now = System.currentTimeMillis();
long next = now + msPerFrame;
while(!done)
{
    now = System.currentTimeMillis();
    if(now >= next)
    {
        next = now + msPerFrame;
        renderGame();
    }
    else
    {
        // no chance of negative, we know next > now
        long diff = next - now;
        try { Thread.sleep(diff); } catch(InterruptedException ex) { }
    }
}

This is just a "close enough" fixed rate game loop, and should give you the basic idea. I say close enough because if renderGame() takes longer than msPerFrame, the game will slow down (like NES games when there was too much on the screen).

There's better ways to run a game loop, like using frame rate independent motion, or using frame skipping to handle slowdowns, but this is a good way to do it for a first game.

CodeGoat
Thank you, this is working better. Yes it is my first game, and I'm not a game developer. It's working very good, although the fps is jumping which I assume is normal.
Millad Dagdoni
Try changing the sleep to just sleep(1) instead of the diff. There's no guarantee that sleep wakes up in the time you want, so that could be why it's jumping. Using sleep(1) will basically just give up the rest of the thread's time slice.
CodeGoat
Alright, but the game will run very fast. It's actually good with the diff variable in sleep.
Millad Dagdoni
Are you seeing it run very fast? With the sleep(1) it should still only call renderGame at 60fps. It just means the loop will check more often if it's time to draw or not so you should get a steadier frame rate.
CodeGoat
When removing diff and placing 1 in sleep, the game runs better when exporting and running the jar file outside of Eclipse. But the game runs a bit slow when using diff in a exported jar than running it in Eclipse. Overall experience, the game runs good, and the enemies are looking quite real.
Millad Dagdoni