JavaScript Game Loop

Welcome back to the next tutorial on building a game engine in Node.js and JavaScript. If you’re just joining please download the github repository so you can follow along. The previous tutorials talked more about setting up our development environment and automating some tasks for us. In this tutorial we are going to focus on JavaScript coding and setting up the game engine frame work.

Basic Game Engine Concepts

One of the first things a game engine needs to do is iterate through a number of frames every second so that we have a way to be interactive with our player. There are many ways to go about creating a game loop which we will investigate and try and find a decent solution. In C++ and other compiled languages we are able to use a simple loop like:

while(true){
…
}

A while loop is great for just infinitely repeating the same line of code over and over. We could us this in our JavaScript application but the down side to this kind of loop is that it will monopolize the processor and not let anything else run. Back in the old DOS days this was okay because only one application ran at a time but in today’s operating systems we need to release control of the processor to other applications. I don’t recommend this loop because many browsers will also report a malicious script running and will ask the user to shut it down. Another down side to this loop is different computers will process a different number of frames a second. So in essence your program will run normally on your computer but when you share it with a friend who has a faster computer everything will be out of sync. It would be like watching the flash run across the monitor.

Now then JavaScript has a few different ways to solve this problem and help us create a decent loop that doesn’t lock the processor. To our rescue is the setTimeout function. There are some issue with the setTimeout function but for now we will ignore these issue and use this technique. So here is the JavaScript code for our loop that will work both in Node and our browser:

function gameloop(){
	...
	if(isRunning === true){
		setTimeout(gameloop, 1000/60);
	}
}

So this would be the start of our game loop. Now there are a couple of issues with this game loop which any game loop developer will be screaming out at me but hold on we will continue to refine this loop. Before we jump into the problems with this loop we need to look at what it is doing. The … section is just a place holder for our engine logic and will be filled out in more detail as we progress.

The if statement in this code is important to me because I don’t like writing an infinite loop and relying on some crazy logic to break out of the loop. There should always be an inroad to closing the loop through a Boolean variable. So in this code I wrapped the call back function inside of a check to see if the game engine is still running.

The loop part of this function is the setTimeout function calling itself. The key here is to remember the setTimeout method takes a callback function and amount of time in milliseconds which there are 1000 ms per 1 second. So if we want to run 60 frames per second then dividing 1000 by 60 will give use the amount we want to wait until the next frame starts.

So now then, you game loop developers who are jumping up and down screaming at me for writing the loop this way, here are your answers and for those just learning here are the problems with this loop.

The big glaring issue here is that we may not get 60 fps out of this loop because we do all the game logic first before we set the callback. So in essence if our game logic takes 12 ms to compute the next frame will be called at 29 ms. This will slow down the animations on the screen significantly enough that our players will see this problem. We could change the loop to look like this:

function gameloop(){
	if(isRunning === true){
		setTimeout(gameloop, 1000/60);
	}
	...
}

Now we will run all the game logic while the next frame waits. The down side to this is that what happens when our game logic runs for longer than ~17ms (1000/60)? Well this could get very messy very quickly and the game logic could start to try and process everything twice, three time, or more depending on how much over we are with the calculation in each frame.

Going back to the original code we will now work on fixing the timeout issue. We need a way to measure the amount of time our game logic is taking to render the frame and then subtract that from the amount of time to wait. Here is an updated version of the game loop that will track the time it takes and adjust the amount of time to wait between frames:

function engineRun(){
	var t0 = performance.now();
	...
	var t1 = performance.now();
	if(isRunning === true){
		var waitTime = (1000/60) - (t1-t2);
		setTimeout(engineRun, waitTime);
	}
}

Now we are tracking the amount of time it takes to process our game loop and then adjusting the wait time between frames so that we hold close to 60 fps. But what happens when it takes longer than 17 ms to process each frame? We need to fix this unless you know of some way we can make the process go back in time and render the frame (rhetorical question!). For now we will add a check to see if the wait time is greater than 0 and then call the setTimeout function else it will just call itself and turn into a like while loop.

function engineRun(){
	var t0 = performance.now();
	...
	var t1 = performance.now();
	if(isRunning === true){
		var waitTime = (1000/60) - (t1-t2);
		if(waitTime > 0){
			setTimeout(engineRun, waitTime);
		}else{
			engineRun();
			console.error('Engine is at max processing!');
		}
	}
}

Now we are going to check if waitTime is greater than 0 and call the setTimeout function. If not then we will issue an error to the console letting the developer know there is an issue in how long it is taking to process each frame and then go and render the next frame.

That was a lot just talking about the main game loop. But as you will see a good game loop is the foundation of your game engine and if not properly built can cause you a mass amount of headaches. Event still this loop could cause use problems when a lot of items are on the screen to process in one frame. How to deal with if your frames are taking more then 17 ms to process would be up to you as the game programmer and maybe in another tutorial we will look at this topic in further details.

Here is the finished code we will use for both the game server and the user’s browser:

function gameEngine(cfg){
	var me = this;
	
	me._isRunning = false;
	
	if(cfg !== undefined){
		me.init(cfg);
	}

	// These methods should not be over written
	me.init = function(cfg){
		Object.assign(me, cfg);
		me._isRunning = true;
	};
	
	me.draw = function(){
	};

	me.run = function(){
		var t0 = performance.now();
		me.update();
		me.draw();
		var t1 = performance.now();
		if(isRunning === true){
			var waitTime = (1000/60) - (t1-t2);
			if(waitTime > 0){
				setTime(engineRun, waitTime);
			}else{
				engineRun();
				console.error('Engine is at max processing!');
			}
		}
	};
	
	me.update = function(){
	};
}

So there is a lot of new code. The first thing to notice is we have now wrapped the engine into a nice object that we can instantiate multiple times. Not needed but I like to keep my options open when writing code and this will allow it to be used multiple times on the same web page.

To use the game engine all we would have to do is write code like this:

var engine = new gameEngine({fps:60});

This would then create a new object of gameEngine and set the frames per second to 60. Granted right now the assignment of fps does nothing but we could change the core engine code to allow changing FPS. Here is what the code would look like:

function gameEngine(cfg){
	var me = this;
	
	me._isRunning = false;
	me.fps = 30;

	if(cfg !== undefined){
		me.init(cfg);
	}

	// These methods should not be over written
	me.init = function(cfg){
		Object.assign(me, cfg);
		me._isRunning = true;
	};
	
	me.draw = function(){
	};

	me.run = function(){
		var t0 = performance.now();
		me.update();
		me.draw();
		var t1 = performance.now();
		if(isRunning === true){
			var waitTime = (1000/me.fps) - (t1-t2);
			if(waitTime > 0){
				setTime(engineRun, waitTime);
			}else{
				engineRun();
				console.error('Engine is at max processing!');
			}
		}
	};

	me.update = function(){
	};
}

For this we added the game engine variable fps and then swapped out the static 60 in the run loop to now call the me.fps to calculate time between frames. Just so we can see if the FPS is being set I decided to default the FPS to 30 frames a seconds.

Things we need for the Game Engine

The engine is taking shape but now we need something for the engine to use during the update and draw steps. This section we are going to create a very generic Entity class that will support a method called Translate which will tell the game world some basic positioning of the entity. We will also include in the entity a speed vector so that the general update method for the entity will have something to do if we want to move an item around the game world.

Before we can build the Entity class we need two support classes built which will help the Entity class. One is a simple vector2d or in other math terms a point which store the x and y values. Most game engines don’t designate the difference between a point and a vector since they both store the same information. Here is the initial code for the Vector2d class we will use later in our Translate class:

function Vector2d(cfg) {
	var me = this;

	me.x = 0.0;
	me.y = 0.0;

	if (cfg !== undefined) {
		// Make sure x, y are defined
		if (cfg.x !== undefined) {
			me.x = cfg.x;
		}
		if (cfg.y !== undefined) {
			me.y = cfg.y;
		}
	}

	me.toString = function () {
		return '[' + me.x + ', ' + me.y + ']';
	};
}

The Vector2d class is straight forward in that it defines an x and y value, tests to make sure the config value passed is valid with defined x and y items, and a simple function to convert the vector into a string. This is used mostly for debugging purposes. Now we are ready to define the Translate method which should hold the entities position, rotation, and scale. These will be all private variables and we will create accessor functions. Here is the initial design of the Translate class:

function Translate(cfg) {
	var me = this;

	me._position = new Vector2D();
	me._scale = new Vector2D({ x: 1.0, y: 1.0 });
	me._rotation = 0.0;

	me.init = function (cfg) {
		var setVector = function (vec, val) {
			if (val.position.x !== undefined) {
				vec.x = val.x;
			}
			if (val.y !== undefined) {
				vec.y = val.y;
			}
			return vec;
		};

		// Handle the vectors so we don't delete our current objects
		if (cfg.position !== undefined) {
			setVector(me._position, cfg.position);
			// Make sure at the end it doesn't overwrite the vector
			delete cfg.position;
		}
		if (cfg.scale !== undefined) {
			setVector(me._scale, cfg.scale);
			delete cfg.scale;
		}

		// Assign additional parameter from config
		Object.assign(me, cfg);
	};

	if (cfg !== undefined) {
		me.init(cfg);
	}

	me.position = function (vec) {
		if (vec !== undefined) {
			if (vec.x !== undefined) {
				me._position.x = vec.x;
			}
			if (vec.y !== undefined) {
				me._position.y = vec.y;
			}
		}
		return me._position;
	};

	me.scale = function (vec) {
		if (vec !== undefined) {
			if (vec.x !== undefined) {
				me._scale.x = vec.x;
			}
			if (vec.y !== undefined) {
				me._scale.y = vec.y;
			}
		}
	};

	me.rotation = function (rot) {
		if (rot !== undefined) {
			me._rotation = rot;
		}
		return me._rotation;
	};
}

Game World Entities

In the Translate class you can see the definition of the 3 really import data elements that we need to track for every entity in the engine. Soon we will see how we can use the translate class to move entities around the game world. Now we need to define the Entity class and there are a couple of special things we want to do in this class. Here is a list of feature we will want to make this generic game object class do:

  • Manage items location in the game world
  • Have a parent/child hierarchy
    • Child should adjust their position based on parent position
    • Child should adjust rotation based on parent rotation
    • Child should adjust scale based on parent scale
  • Get access to parent entity
  • Add children to entity
    • Auto set parent to myself
  • Remove child from myself and un-parent child
  • Store a unique ID for searching purposes
  • Draw entity function used by engine
  • Draw entity function used by user
  • Update entity function used by engine/user

That should be a start for the entity class and as we build it we will refine it to support new features. Because the entity class is so complex we will just look at each section of code one at a time. If you want to skip to the end here is the link to the repository.

The first thing to note is that we need to setup the configuration properly. Since the class defines the init function as a variable we need to make sure the definition of init comes before the check if a configuration object was passed. To start the class add the following code to entity.js:

function Entity(cfg){
	var me = this;
	// Items that can be provided by the config object
	////////////////////////////////////////////////////
	me._translate = new Translate({
		position: { x: 0, y: 0 },
		rotation: 0,
		scale: { x: 1.0, y: 1.0 }
	});
	me._speed = new Vector2D();

	me.init = function (cfg) {
		if (cfg.translate !== undefined) {
			me._translate.init(cfg.translate);
			delete cfg.translate;
		}

		Object.assign(cfg);
	};
	if (cfg !== undefined) {
		if(cfg.translate)
		me.init(cfg);
	}
}

In this block of code we are just doing a basic initialization of the entity. We check if the config is defined and then auto run the initialization of the Entity object. The thing we had to be careful in this section of code is checking to see if a translate object was passed in the config object. If it has we want to let the translate object handle storing the data for this object so we pass it to the initialization function. Also you can see a special comment added to this code which tells us that anything below that comment line will be modifiable by the config object. This means anything we want to allow the end user to modify would go below this line. We will see a good example with the update and draw functions. Add the following code to after the init definition and before the if(cfg !== undefined) statement:

	me.draw = function (ctx) {
		// Default entity has nothing to draw
	};

	me.update = function (deltaTime) {
		// Update method to be overloaded
		// Do some basic speed calculations
		var pos = this._translate.position();
		pos.x += this._speed.x;
		pos.y += this._speed.y;
	};

These two functions expect to be over written with our game logic and are only here to make sure every entity has a basic functionality and won’t crash the core engine. Granted in the engine we will want to make sure these two functions exists before calling them just in case some programmer decides they want to delete them from the object.

For now that should give us enough functionality for the user to define we now need to start working on functions that should never be over written. These are more utility functions that the core game engine will rely on to simulate our game world. One of these specific functions is the engineDraw function. Yes I know we already defined a Draw function why add an engineDraw? Well in order to make sure specific changes to the context happen we will want a way to translate our hierarchy so that any changed will be done to the object based on their location to the parent object. This functionality make it so easy to move multiple objects around without having to track changes on everything.

Wait a second did you say context? What is that and why are you now just telling us about this feature? This is a little jump ahead of the game (no pun intended) but we can’t do the drawEngine without having a context passed to the function and moving things around in the game world. The context comes from the HTML canvas tag and is initialized when the core game engine starts up. This will be a topic we will cover later when we start talking about drawing entities to the game world and presenting this to our players.

Now with the context behind us we will now look at this nasty engineDraw function and discuss some of the cool context features that make this so amazing. Here we go more code which should be added after the if(cfg !== undefined){} code block:

	me.engineDraw = function (ctx) {
		// Save contex state
		ctx.save();
		var pos = new Vector2D({ x: me.translate().position().x, y: me.translate().position().y });

		var rot = me.translate().rotation() * me._180PI;
		ctx.translate(pos.x, pos.y);
		if (rot !== 0) {
			ctx.rotate(rot);
		}

		var oldAlpha = ctx.globalAlpha;
		ctx.globalAlpha = me.alpha;

		this.draw(ctx);
		if (me._children.length > 0) {
			for (var i = 0; i < me._children.length; ++i) {
				me._children[i].entity.engineDraw(ctx);
			}
		}

		if (rot !== 0) {
			ctx.rotate(-rot);
		}
		ctx.translate(-pos.x, -pos.y);
		ctx.globalAlpha = oldAlpha;
		// Restore context state
		ctx.restore();
	};

There is a lot going on in this code block and we will now break it down into what is really happening going line by line. So get your cup of coffee or what every favorite caffeinated drink you like and get ready for a long winded explanation. You have been warned!

The first relevant piece of code is the ctx.save() call. This is just telling the context we want to save the current state so that we can quickly and easily return to this point when we are done. You’ll notice the last line of code in this definition is the call to the ctx.restore() function. This is a really nice feature because it allows us to make a ton of changes to the context and then return to the point before we made those changes. This isn’t a 100% perfect solutions but for completeness in the context world we have added them even though many of the functions we will call will always restore the context to the original values before we exit.

The next line of code is pulling the objects current position. We use this position to translate the canvas by the position’s x and y amounts so that we do any actual drawing to the canvas it will be at [0,0] for our objects. This makes it so when we want to rotate and object we can rotate it around the center point of an object. The center point is usually just the width divided by 2 and the height divided by 2. For example a box that is 20 wide by 40 high would have a center point of [10, 20]. We also will allow for the center point to be changed which makes for some really neat rotation animations.

The next line is converting the rotation which is normally store in degrees so then we need to convert it to radians. In the entity we define the const me._180PI which works out to be PI/180. For now it is stored in the entity but technically we should store it somewhere else so that this math is only done once and not every time we instantiate an object.

Now we translate the canvas to the object position which makes the canvas ready to draw. Not much to this function other then we just are passing the x and y values of the position to the function. After that we check to see if rotation is not equal to 0 and then perform the converted rotation. Canvas is kind of strange in that when we call the rotate function it rotates the whole image. There really isn’t an easier way to rotate an object because most of the canvas draw methods don’t have a degree or radian option available.

Next we save the original alpha of the global alpha setting on canvas. We will restore this value when we are done which is why we save it. Next we just set the global alpha to our value. So now anything drawn to the canvas will use this alpha value.

The big deal line is now called which then send the context to the user defined draw function. We will cover this in more detail when we create our first game world object (the ball). The next section of code is really why we created this whole engineDraw function. We want to now check if there are any children parented to this entity. If there are then we will loop through each item and call their engineDraw function. This is where the beauty of the hierarchy comes into play. The translate and rotate setting of our parent will now affect all our children.

Now that is all done it is time to close down the engineDraw function and start restoring the original state. If we forget to do this step then the game world look very screwed up and not what we would expect. The key here is everything we do to the canvas we need to undo in the reverse order. The last thing we did was rotate the canvas so we will want to rotate the canvas in the opposite direction. Next we will translate the canvas back to the position before we moved it by taking the negative of the [x,y] position.  The other thing we restore now is the global alpha. So now we are done.

That was a lot of code to explain but we made it. Only took me a couple of cups of coffee to get this done so hopefully you did better reading this then I did writing it. So now comes the engineUpdate method. It is WAY easier to go through. Here is the code and it should go after the engineDraw function:

	// Do not overwrite this module it will break the parent child relationship!
	me.engineUpdate = function (deltaTime) {
		this.update(deltaTime);
		if (this._children.length > 0) {
			for (var i = 0; i < this._children.length; ++i) {
				this._children[i].engineUpdate(deltaTime);
			}
		}
	};

See this is a very simple function. It just calls the user definable function update and then check to see if the entity has any children and calls the engineUpdate function for all children. We do introduce the concept of deltaTime which is a common calculation that we use to help smooth out sprite movement but we will get into that in more details when we discuss the update part of the game loop.

Now that all the engine specific things are done we need to discuss some of the entity utilities. With all this great power in the hierarchy we will need a way to manage this hierarchy. This is where the add and remove function come in. We will call them add and rm because I don’t like to type a lot of text when I’m programming.

	me._nextId = 0;
	me.add = function (child) {
		if (this._children.push !== undefined) {
			var idx = me.idx + ":" + me._nextId++;
			child.id = idx;
			this._children.push(child);
			child._parent = this;
		}
	};

	// Remove child form children list
	me.rm = function (id) {
		ent = null;
		if (id !== undefined) {
			for (var i = 0; i < this._children.length; ++i) {
				if (this._children[i].id === id) {
					// Remove item from the array
					ent = this._children.slice(i, 1);
					// Unparent entity
					ent.parent = null;
					// Stop searching we have found the entity
					break;
				}
			}
		}
		return ent;
	};

The add function is fairly straight forward, first it checks to see if the function push is defined in the child array. I do this because Safari on some version doesn’t support this feature and typically causes my pages to crash. We will add a fix for those browsers that don’t support this function but for now we protect our code so it doesn’t crash!

Next we define a unique id and I like to attach the parent’s ID to the child so that we can walk up the parent chain if we needed. After that we push the entity to the back of the child list and set the entities parent to me.

The remove isn’t so easy. We need to make sure first that we have a semi valid id if not just close and return the null value. If we do find the entity it would be nice to return it to the original caller just in case they want to reuse this junk again, isn’t this what we call recycling? This search is VERY slow so when we talk about place where we could speed up the game engine this would be one place if we are adding and removing entities a lot during the game. This kind of search is kind of a brute force which means we potentially could go through all the items in the array. You may be asking why didn’t I just do an indexOf call on the array? Well that would be fine if we sent the whole object in to be removed but since we are expecting just the object ID this will not be the case. Also it is always good to learn this algorithm so you know what to look for and things to avoid when you’re programming. Again in future tutorials we will address this nasty algorithm and clean it up.

Once we do find the id in our array we splice it off the array and un-parent it from the entity. After that it gets returned to be recycled. The last part of the entity class we are going to discuss is the speed accessor/setter function. This function just checks if a valid argument was passed and then sets the speed. When that is all done it will then return the current speed object.

	me.speed = function (spd) {
		if (spd !== undefined) {
			if (spd.x !== undefined) {
				me._speed.x = spd.x;
			}
			if (spd.y !== undefined) {
				me._speed.y = spd.y;
			}
		}

		return me._speed;
	};

So there we have it the entity class. Looking through this code I’m starting to see a lot of repetitive code going on in the engine which we might want to refactor. We will leave that for another tutorial since this one is a lot longer than I expected. For now we will wrap up where we so stay tuned for the next addition to this tutorial series on build a JavaScript game engine.

For those of you that have been following along and running the Grunt watch task. There have been several times the Grunt JSHint task runs and said everything was okey. But then when I opened the project in the browser it threw several errors. The thing to take away here is to always test your code in the browser or Node.js depending on the final engine that will run our code.