Change
Okay, so I’m going to switch over to a very simple format, with very short examples of how you do certain things. The long article format is just too much for everyone to digest and takes too much time for me to write, so I tend to put it off forever.
Types of Games
Most games fall into two patterns: turn-driven or time-driven.
Turn-driven games have distinct periods where user input is taken, then periods where game updates are made, and the two do not overlap in anyway. The user-input section waits for the user to make their choice, and the user then waits for the update section to finish before they take their next turn. Many puzzle games and most board games are going to be of this type. For example, in chess with an AI player, the game waits for the player to move a white piece. Once the player moves, the AI takes over and calculates a move for a black piece, during which time the player is stuck and cannot make any moves. Once the AI has moved the a black piece, it’s up to the player to make a move decision again, and the AI cannot progress until the user has decided.
Time-driven games work completely differently. They are constantly updating the game, never waiting for the user to first make a selection or hit a button or waggle their joystick. If a user does perform some kind of input, the input is not processed separately, it is taken into account for the next update. Think of a game of Asteroids, in which the big, giant rocks float around the screen all on their own until the user decides to turn her ship and blast them.
There is actually a third class of game called alternate-reality games (ARG) that do not really update and do not really take user input–not in the same way these other games do–but they are way outside of the scope of this project. ARGs are more literature projects than programming projects.
Turn-driven games are relatively simple to create, as the user drives everything and performance issues do not cause noticeable artifacts like frame-rate stutter or audio clipping and buzzing. When we get into handling user input, you’ll learn all you need to make turn-driven games. Actually, time-driven games turn out to have many of the same features as turn-driven games, just with the additional features of not waiting around for the user first, having its own pump to drive the game forward.
We will be focusing on time-driven games, as they are the most technically challenging.
The Game Loop
The most important part of a time-driven game is the Game Loop. This is a combination of a number of things:
- It executes a specific block of code over and over again, simulating the passage of time. This is the Game Update.
- It keeps track of how much time has advanced since the last time the Game Update occurred and uses that information to make appropriate updates. Times between updates on real systems vary, so to maintain consistency, you must scale your updates by that elapsed time.
- Maintain responsiveness to user input, including such things as closing the game window. This can catch people off guard, as it means you can’t just use an infinite “while(true){}” loop for your game. You need to play nice and give time to let the operating system have a chance to respond to user input.
Luckily, if you follow a few basic principles, you’ll quickly get the game loop out of the way and never have to think about it again.
Code
In any game, the game loop will look something like this:
var TIME_STEP = 1; // this will probably be too slow, so we will later scale this higher var FRAME_SCALE = 1000/60; // approxiamate 60fps var lastUpdate; document.onload = function() { lastUpdate = new Date().getTime(); setTimeout(gameLoop, FRAME_SCALE); }; function gameLoop() { var now = new Date().getTime(); var delta = now - lastUpdate; lastUpdate = now; for(var t = 0; t < delta; t += TIME_STEP) { gameUpdate(TIME_STEP); } delta = (new Date().getTime()) - now; // figure out how long our update took setTimeout(gameLoop - delta, FRAME_SCALE); // take the update length into account for the next update } function gameUpdate(dt) { // do something with dt }
The essential parts here are:
- The onload function kickstarts the loop. This isn’t strictly necessary. We could have the game start when the user clicks a button instead. I prefer to get it running as soon as possible and manage all input through the game, rather than mixing two different styles of input.
- The lastUpdate variable keeps track of how time is progressing. We set it first in the onload function so that it has a valid value for the first execution of the gameLoop function.
- The gameLoop function receives the time event, calculates the difference between lastUpdate and now, and then updates the game a given number of times before giving up and asking for a new time event.
- The for-loop around the gameUpdate function. You might be tempted to use delta in place of TIME_STEP and not have a loop around it at all. This works for the most part, but when things start moving very quickly and if the system starts to run slow, then there is potential for some bad results with such large time steps, such as balls warping through paddles as if they don’t exist. But updating in small increments, we are getting a chance to check all of the intermediate steps.
And that’s about it. The gameUpdate function is where the vast majority of your game code will live, in some shape or another. We will look at that at another time.