The Basics of Game Loop Implementation – part 3

Intro

A good way to code a game loop in JavaScript is to use the new “Timing control for script-based animations” aka “requestAnimationFrame” feature. The specs for this are still in draft state, but most modern browsers support it one way or another. The browsers I’ve tried and found to support it are: Chrome v. 27, Firefox v. 24 and Internet Explorer v. 8. I’m sure other version of these borwsers, as well as other browsers support this.

Much like Open GL’s callbacks that we discussed in the 2nd part of these series, “requestAnimationFrame” gives your graphics drawing code a way to get notified when it can draw. This basically translates to taking advantage in the most optimal way possible of the graphics drawing resources of the browser.

Before we dive into detailing the implementation, you can take a look at the running demo by clicking here:



We’re back to the “1-second counters” from part 1. This time there’s also a bit of user input handling: if you left-click anywhere inside the rectangle containing the counters, you’ll see one of the counters slowly moving towards the clicked location. Don’t worry if everything becomes a bit jerky when the counters reach 7, it’s supposed to do that (it’s not a bug, it’s a feature!). It’s one of the things that will come into discussion later on.

The (mixed step) game loop

The game loop implemented in this example is the one most discussed throughout these series: the affectionately called “mixed step” game loop. The mixed part comes from the fact that the loop keeps to separate steps: one fixed, happening at a given rate per time interval (25 times per second in this example), and one variable, which is executed as fast as possible, but only when we’re sure that the fixed step’s frequency is respected. The fixed step is used for updating the game state, while the variable step is used to update the graphics.

Here’s a code snippet of the game loop implementation in JavaScript:

	this.update = function() {
		var self = this;

		var actualLoopDurationMs = self.getCurrentTimeMs()-self.lastLoopCallTime;
		self.lastLoopCallTime = self.getCurrentTimeMs();
		self.accumulatedTimeMs += actualLoopDurationMs;
		while(self.accumulatedTimeMs>=FIXED_STEP_IDEAL_DURATION_MS) {
			self.updateState();
			self.accumulatedTimeMs -= FIXED_STEP_IDEAL_DURATION_MS;
		}

		self.updateGraphics();

		/*
		 * request a new graphics rendering, specifying this function as the function to be called back when
		 * the browser schedules a frame update
		 */
		window.requestAnimationFrame(function() {self.update();});
	};

So basically, each time we want to update our graphics, we inform the browser of our intention by calling the “requestAnimationFrame” function, passing as argument the function that we’d like to have called (back) when the browsers deems that it’s time to make a new graphics rendering pass.

This must be manually invoked each time we’d like our graphics to be updated. This is why the “update” function in the code snippet above, has on its last line a call to requestAnimationFrame passing itself as argument.

What that call guarantees is that our update function will be called at some point in the future, when the browser thinks it’s a good time to do a new graphics update. For example, the update function will NOT be called if the browser tab/window where the drawing is happening is minimized or not visible for some reason. You can easily test this with the above example: As you can see, in the upper left corner it displays the local values for fixed updates rate (UPS) and variable update rates (FPS). Normally they are at about 25 and somewhere around 60 respectively. They may fluctuate a bit when the counters get to 7, as described above, but for most of the time they stick to these values. Now switch over from the tab displaying the demo, to another browser tab. Wait 10 seconds than go back to the game loop demo tab. You’ll notice that UPS and FPS are 0, followed by a quick jolt where they go to values such as 100 or 200 before finally settling in to their “regular” values of 25 and 60-ish, respectively. That’s because while the tab was invisible, the “update” function was not called, effectively NOT advancing our game state or rendering any of the game graphics. Once you come back to it, due to the fact that there’s a lot of accumulated time, a lot of game state updates will happen very fast, in order to give the game a chance to catch up and be back at about 25 game state updates per second.

This behavior from the requestAnimationFrame mechanism is a good thing. Your game loop will automatically spare resource usage when there’s no one to see it run. The way this is actually handled in this example is not the best way possible: normally you’d want to pause your game advancement when the game is no longer visible, and resume it when it’s visible again, and avoid the game state updates spike. This simplified implementation is done to showcase this behavior of the “requestAnimationFrame”. In a later article we’ll also see how to automatically and manually pause/resume a game.

Now, the game loop implementation is not really that exotic. We have two global variables: one for storing the last time when the last loop step was executed: self.lastLoopCallTime as well as another global variable for storing the accumulated time that has passed since a game state update has ran:

var actualLoopDurationMs = self.getCurrentTimeMs()-self.lastLoopCallTime;
self.lastLoopCallTime = self.getCurrentTimeMs();
self.accumulatedTimeMs += actualLoopDurationMs;

At the beginning of each loop step we calculate how much the last step has taken, we add that to the accumulated time, and then, if the accumulated time dictates that one or more game state updates are due, we execute them, deducting for each the fixed amount of time it should represent:

while(self.accumulatedTimeMs>=FIXED_STEP_IDEAL_DURATION_MS) {
    self.updateState();
    self.accumulatedTimeMs -= FIXED_STEP_IDEAL_DURATION_MS;
}

Finally, we do a graphics rendering update as well.

self.updateGraphics();

Coping with graphics rendering slowdowns

During the game loop steps, you may run into situations where one update to the graphics takes so long, there’s no more time left for the game state update to run at its fixed step.

The graphics update can have such slowdowns usually when the graphics on the screen suddenly become a lot more complex then they were a moment ago, and the hardware cannot draw them as fast as desired. Imagine explosions. You have a game where the player has to kill a boss enemy. The player is one sprite (or a 3d model consisting of X triangles). The boss is one main sprite with 10 extra small sprites for various effects such as lasers shooting in all direction, rockets firing, etc (or a big 3d model consisting of y triangles plus 10x smaller 3d models consisting of z triangles each). The hardware can handle all these ok: each graphics update is fast enough so that there’s time to call the game state update when necessary. However at some point the boss explodes and for a few seconds there are hundreds of explosion sprites (hundreds of 3d explosions of y triangles each). During this time, each graphics update might take 10 times longer than before. If each of those graphics updates were executed, the game stat updates would be delayed for 5 seconds. That would mean that for 5 seconds, the player couldn’t move and the game would seem to be advancing a lot slower than before.

But the game loop described here doesn’t handle things like this: with a target fixed step update of 25 per second this translates to ideally doing a fixed step every 40 milliseconds. This means that if a graphics update takes way more than 40 milliseconds, we’re falling behind on the fixed steps execution. To compensate for this, the game loop measures how much time it has passed since the last fixed update was called, let’s say:200 milliseconds, calculates how many fixed updates it has missed, 200/40 = 5 updates, and executes them before doing another graphics update. What this translates to is that, while the fluidity of the graphics has to suffer (animations and motions are more “jagged”, etc), the game remains responsive to input, and does not freeze for long periods (like those 5 seconds in our hypothetical example). You can see this in action with the demo, by doing the following: when the counters are at something like “3” or “4” click somewhere in the game area. One of the counters will start moving towards the destination. As it moves, watch what happens when the counters hit 7: the motion will no longer be smooth, but will become very jerky for a while (normally until the counters get to something like “8” or “9”), after which the motion becomes smooth again. You can also check that if you click somewhere else in the game area doing the “jerky” animation period, the input will in fact be taken into account, and the moving counter will start moving towards the new destination. Finally, you can see in the console below that the overall precision of the counters (how closely they are to counting actual seconds), remains very high (this is the counters’ precision from the very beginning of the loop, not a local measurement, like for the last 2 seconds or something).

The reason why everything gets “jerky/jagged” for a while after “7” is that at this point the code is “rigged” to simulate a slowdown in graphics rendering, the equivalent of the explosion described in the example above: basicaly, the GameEntity.js object contains the following:

	this.updateGraphics = function(context) {

		if(this.simulateUpdateGraphicsDelay && this.currentAnimationImageIdx==7) {
			var st = new Date().getTime();
			while(new Date().getTime()-st < 100) {}
		}
		context.drawImage(this.imgs[this.currentAnimationImageIdx], this.startX-this.width/2, this.startY-this.height/2, this.width, this.height);
	};

While the counter is a 7, the updateGraphics function will busy-wait for 100 milliseconds.

The Code Example

The code for this example can be git-cloned from here:

https://gitlab.com/shivansite/jsGameLoopStudy.git

For running the example locally you need to load the “WebContent/page.html” in your browser. The browsers on which this was tested are : Chrome v. 27, Firefox v. 24 and Internet Explorer v. 8.

Advertisements
This entry was posted in Examples, Game Dev and tagged , , , , , , , , . Bookmark the permalink.

2 Responses to The Basics of Game Loop Implementation – part 3

  1. Pingback: Using a Queue for User Input Evens | Shivan's Blog

  2. Pingback: Using a Queue for User Input Events | Shivan's Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s