Understanding Game Loops

Started by
6 comments, last by KaanAlpar 7 years ago

Hello guys, I always had a hard time understanding game loops. I understand the idea of it but I dont really understand how everything works. Can someone break this game loop im using down to me?


	public void run() {
		long lastTime = System.nanoTime();
		final double amountOfTicks = 60.0;
		double ns = 1000000000 / amountOfTicks;
		double delta = 0;
		int updates = 0;
		int frames = 0;
		long timer = System.currentTimeMillis();
		
		while(running) {
			long now = System.nanoTime();
			delta += (now - lastTime) / ns;
			lastTime = now;
			
			if(delta >= 1) {
				tick();
				updates++;
				delta--;
			}
			render();
			frames++;
	
			if(System.currentTimeMillis() - timer > 1000) {
				timer += 1000;
				System.out.println("Ticks: " + updates + ", Frames: " + frames + "\n");
				updates = 0;
				frames = 0;
			}
			
		}
		stop();			
	}
Advertisement

First, obligatory read if you didn't read already:

http://www.koonsolo.com/news/dewitters-gameloop/

Your loop is one of many designs. To be honest, not my favorite (and I think there's a mistake), but here we go:

You grab the amount of time (in nanoseconds) that one frame should have in a 60FPS case:


        final double amountOfTicks = 60.0;
        double ns = 1000000000 / amountOfTicks;

In the first step of your loop, you compute the elapsed time between this and the last frame. Since it's the first frame, it grabs the elapsed time since of the first call of 'System.nanoTime();', your lastTime:


            long now = System.nanoTime();
            delta += (now - lastTime) / ns;
            lastTime = now;

Then, you update the game AS LONG as

Then you update, but if the delta time since last time was too long (more than 1 second), update again:


            if(delta >= 1) {
                tick();
                updates++;
                delta--;
            }

I believe that should be:


            [removed]

Here you update ('tick()') as long as the frame should take. So, in a single frame (16 milliseconds), you might have more than one update. After that, you render.

Your last step prints the information after 1 ellapsed second:


            if(System.currentTimeMillis() - timer > 1000) {
                timer += 1000;
                System.out.println("Ticks: " + updates + ", Frames: " + frames + "\n");
                updates = 0;
                frames = 0;
            }

It updates 'timer' for when will be the next second (and print of information), then you print the total of updates (ticks) and frames inside that second. Last, you reset the count of updates and frames inside that second.

Here's what I don't like. You update as long as you can. First, it's CPU intensive, it doens't give time for the CPU to rest. Second, that update (tick()) will be called during all the expected 16 milliseconds of each frame, and THEN the render is called. So, your renderization will be called AFTER 16 milliseconds, which means your frame will take more than 16 milliseconds, necessarily your FPS will be under 60.

[Post corrected]

Here's what I don't like. You update as long as you can.

The idea of that loop is to update game simulation in fixed steps, and not to update as long as it can. That's why elapsed time divided by step duration (1/60 sec) to get exact amount of steps to be taken. You don't measure time elapsed between steps, because it doesn't matter. You know the time that passed since last render, and you want to move simulation in fixed steps to maintain simulation stability across systems with different rendering speed.

Here's what I don't like. You update as long as you can.

The idea of that loop is to update game simulation in fixed steps, and not to update as long as it can. That's why elapsed time divided by step duration (1/60 sec) to get exact amount of steps to be taken. You don't measure time elapsed between steps, because it doesn't matter. You know the time that passed since last render, and you want to move simulation in fixed steps to maintain simulation stability across systems with different rendering speed.

Good catch. I messed up when I saw 'if (delta >= 1)', but that delta is in seconds, not milliseconds, then it only updates if the specific delta has passed.

Oh, what the heck. It's the weekend, and this could be fun...

public void run() { //this lets the public say "run" and you respond
		long lastTime = System.nanoTime(); //last time sounds a lot like now, but it's not... trust me
		final double amountOfTicks = 60.0; //establishing you're using Java instead of languages with const
		double ns = 1000000000 / amountOfTicks; //apparently ns is 1/60th of a billion.
		double delta = 0; //Delta doesn't fly here
		int updates = 0; //no updates for you!
		int frames = 0; //no frames either!
		long timer = System.currentTimeMillis(); //what time is it? In Milliseconds! Called timer, because it's totally not!
		
		while(running) { //clearly the work of an inferior mortal who hasn't heard of "while (!done)"
			long now = System.nanoTime(); //now is like now, but in nanoseconds
			delta += (now - lastTime) / ns; //So now that we know when now is, let's figure out how many nanoseconds since last time we checked. Then divide by nanoseconds, which would give us a fraction of a second, except ns is really a billion over 60, so we've got some mess of what portion of 1/60th of a second has passed. Oh, and then we will add it to delta, because apparently delta isn't really the delta from last time, but a running total. So, sure it would make more sense to call it "fractionOfFixedUpdateTimeElapsedSinceLastFixedUpdate" or something, but we like flying with Delta.
			lastTime = now; //now that now isn't now any more, we'll call it lastTime.
			
			if(delta >= 1) { //oh, that's right... fractionOfFixedUpdateTimeElapsedSinceLastFixedUpdate. If that is greater than 1, it's kind of obvious what it means. But we fly with no comments and variable names that are brief even if they introduce needless ambiguity!
				tick(); //we don't know what tick is, but we can guess!
				updates++; //we have one more update now
				delta--; //showing off that your compiler isn't broken. That's right Microsoft, I went there! (don't tell anyone I'm going to stir up trouble later)
			}
			render(); //I bet this is when we render a frame. We better render faster than 60 fps or stuff isn't going to do what we think!
			frames++; //we have one more frame under our belts!
	
			if(System.currentTimeMillis() - timer > 1000) { //if current time is more than 1000 ms later than "timer"
				timer += 1000; // add a thousand to timer
				System.out.println("Ticks: " + updates + ", Frames: " + frames + "\n"); //show how many updates and frames we had
				updates = 0; //apparently we don't have any updates any more
				frames = 0; //or frames
			}
			
		}
		stop();			
	}
This is mostly in jest. It's not hard to read or anything, although if you used the same names and such in a far more complicated program, I'd get frustrated because you'd be that guy that calls everything a timer, or any value you change to anything else delta. To me, delta is always exactly current minus old. No modifications. But I'm a jerk, so who cares? And for me, timer should be a timer. Like I'd expect timer.nextTriggerTime to be a thing, not timer to be an int. And I guess objects have gotten me used to explaining those ints in long variable names.

But it's not entirely in jest. You seem to have already gotten caught forgetting what time units you're using where or something (I can't tell for sure since I can't see the old code)

But the real bug waiting to happen in your code is if you constantly get 30fps. delta will grow and grow. You probably want it to be a while delta > 1 instead of if. And if it's good enough for Unity...
Please use long names and include the unit of measure in the names as well.
"delta" should be "accumuatedFrameTimeInMilliSeconds" or something like that.
"updates" is better called "numerOfUpdatesPerSecond".

Is does not hurt to write long names, especially in java with good ide´s like eclipse and intelli-J you can easiely rename things.

Also when you use the "catch updates to match frametime method" the "tick" may not be called once per frame, rather how many times it needs to.

Lastly you need to clamp the accumulated frame time to some max number smaller than one second, so your ticks will catch up and not starting to infinitly be called.

Helps to think of game time as virtual time, you want to collect the game time that has elapsed and then periodically move things and update the game based on what kind of action would happen over that period of time. Use a while loop instead of a for loop, if rendering is slowing down your code a lot then it may be the case that you could say, have 10 game updates worth of time elapse between each rendering call.

Also I say game time but that's a bit of generic term since time could be used for many things. Imagine you have a game that runs in accelerated time, you can click buttons to make everyone run around the screen super fast. Well you'd still have to make them move and react based on real time, so they don't say do 10k actions in half a second when they should have done 200. Basically you're converting from real delta time to game time. If you think even more that game time may be occurring in a window that has a GUI, that gui may have buttons and things that need to react to time and input, but you probably don't want them to change speed with the game.

If you think about it that way, your basic goal is to determine how much real time has passed and then use that to affect game events as you need it to.

Thanks to everyone for commenting, I forgot that I post this :P . I understand the loop now, I just followed everything line by line and kept the numbers in my head until it made sense!

This topic is closed to new replies.

Advertisement