Chapter 4. Using HTML5 to Catch a Snake

This chapter is the first part of a two-part series, where we'll build the first version of a game, and then spice it up with more HTML5 APIs in the next chapter. Both versions will be complete and playable, but since covering all of the APIs in the same game within one chapter would make for a very large chapter, we'll break things up into smaller chunks, and write two separate games.

The first version of the game will cover five new concepts, namely, HTML5's 2D canvas API, offline application cache, web workers, typed arrays, and requestAnimationFrame. The canvas element allows us to draw 2D as well as 3D graphics, and manipulate image data at a very low level, gaining access to individual pixel information. Offline application cache, also known as app cache, allows us to cache specific assets from a server into the user's browser, so that the application can work even when no internet access is available. Web workers is a thread-like mechanism that allows us to execute JavaScript code in a separate thread from the main UI thread. This way, the user interface is never blocked, and users don't see a page not responsive warning. Typed arrays is a new native JavaScript data type similar to arrays, but much more efficient, and specifically designed to handle binary data. Finally, requestAnimationFrame is an API offered by the browser to help us perform time-based animation. Instead of using a JavaScript timer (setTimeout or setInterval) multiple times a second in order to perform animations, we can let the browser do the heavy lifting, optimizing the animation beyond what we could achieve in JavaScript alone.

The game

You've certainly seen or played this game before. You control a snake in a 2D grid only moving up, down, left, or right. When you change the direction in which the snake's head is moving, each part of the snake's body gradually changes direction as well, following the head. If you run into a wall, or into the snake's own body, you lose. If you guide the snake's head over a fruit, the snake's body gets larger. The larger the snake gets, the more challenging the game becomes. Additionally, the speed at which the snake moves can be increased for an extra challenge. In order to stay true to the old school nature of this classic game, we opted for old school graphics and typefaces, as shown in following screenshot:

The game

The image shows the look and feel of the game. When the game first starts, the snake has a total body length of zero—only the head is present. At first, the snake is randomly placed somewhere within the game grid, and is not given a starting direction to move towards. The player can control the snake with the arrow keys, and once the snake starts moving in a particular direction, the snake cannot be stopped. For example, if the snake is moving to the right, the player can move it up or down (but not backwards). If the player wishes to move the snake to the left (once it is currently moving to the right), the only possible ways are to first move the snake up, then to the left, or down, then left.

Whenever there are no fruits on the game grid, one is randomly added to the grid. That fruit stays there until the player eats it, at which point, a new fruit is added to the grid. For added difficulty, we could make a fruit disappear, if the snake can't get to it within so many seconds.

API usage

A general description and demonstration of each of the APIs used in the game are given as follows. For an explanation of how each of the functionality was incorporated into the final game, look at the following code section. For the complete source code for this game, check out the book's page at the Packt Publishing website.

Before requestAnimationFrame was introduced, the main method developers used to create animations in JavaScript was by using a timer to repeatedly call a function that gradually updated attributes of the element(s) being animated. While this is a straightforward method, what the browser provides through requestAnimationFrame has a couple of added benefits. First of all, the browser uses a single animation cycle to handle the rendering of a page, so any rendering we do using that same cycle will result in a smoother animation, since the browser can optimize the animation for us. Also, since the rendering would be done by the browser's internal rendering mechanism, our animation would not run when the browser tab running our animation is not shown. This way we don't waste battery life animating something that is not even visible.

How to use it

Using requestAnimationFrame is very simple, and similar to setTimeout. We call the requestAnimationFrame function on the global window object, passing a callback function that is executed whenever the browser is ready to run another animation cycle. When the callback is invoked, it is passed in a timestamp, which is normally used inside the animation function we register with requestAnimationFrame.

There are two common ways in which requestAnimationFrame is used, both of which achieve the same result. In the first method, you define your animation function with no references to requestAnimationFrame. Then, a second function calls that animation function, followed by a call to requestAnimationFrame.

function myAnimationLoop(time) {
   // 1. Perform the animation
   myAnimation(time);

   // 2. Register with request animation frame
   requestAnimationFrame(myAnimationLoop);
}

function myAnimation(time) {
   // Perform animation here
}

The second pattern that is commonly used is very similar, except that it only includes the main animation function. That function itself takes care of calling requestAnimationFrame when needed.

function myAnimation(time) {
   // 1. Perform the animation
   myAnimation(time);

   // 2. Register with request animation frame
   requestAnimationFrame(myAnimationLoop);
}

The reason that the time argument is useful is, because most of the time, you want the animation to run more or less at the same rate on different computers. requestAnimationFrame attempts to run as close to 60 times per second as possible. However, based on the code you execute inside it, that rate may drop significantly. Obviously, faster hardware would be able to execute your code much faster, and thus, display it to the screen more often than some slower hardware would. In order to make up for this possibility, we can use actual time to control how often the animation code runs. This way, we can specify a cap refresh rate, which, if a particular computer is able to run faster than this rate, can simply slowdown that computer, and all users experience about the same animation.

One possible implementation of this technique is shown in the following steps. Although it may seem like a lot of steps, the concept is really quite simple. The gist of it is this: we set two variables, one that keeps track of the cap speed that the animation will run (measured in frames per second (fps)), and the other keeps track of when the last time was, that a frame was rendered. Then, whenever the animation function executes, we take the current time, subtract the last time that a frame was rendered, and check if their difference is greater than, or equal to the ideal fps we have chosen. If it is less than our desired fps, we don't animate anything, but still register requestAnimationFrame to call us back in the future.

This we do until enough time has elapsed so that our frames per second rate can be achieved (in other words, so that the fastest frame rate we can possibly run would be our fps). If the system is running slower than that, there's nothing we can do about it. What this technique does is control the maximum speed.

Once requestAnimationFrame has called our animation function, and enough time has passed since the last time a frame was rendered, we update all the data we need to, for the animation, render the animation to the screen (or let the browser do it, if it can), and update the variable that keeps track of when a frame was last updated.

// 1. Create some element
var el = document.createElement("h1");
el.textContent = "I Love HTML5!";
el.style.position = "absolute";

// 2. Attach it to the document
document.body.appendChild(el);

// 3. Set some variables to control the animation
var loop = 0;
var lastFrame = 0;
var fps = 1000 / 60;

// 4. Perform the animation one frame at a time
function slideRight(time) {

   // 5. Control the animation to a set frames per second
   if (time - lastFrame >= fps) {

      var left = parseInt(el.style.left);

      // 6. Perform the animation while some condition is true
      if (left + el.offsetWidth < document.body.offsetWidth) {
         el.style.left = (left + loop) + "px";
         loop += 5;

         // 7. Perform the time control variable
         lastFrame = time;
      } else {

         // 8. If the animation is done, return from this function
         el.style.left = document.body.offsetWidth - el.offsetWidth;
         return true;
      }
   }

   // 9. If the animation is not done yet, do it again
   requestAnimationFrame(slideRight);
}

// 10. Register some event to begin the animation
el.addEventListener("click", function(){
   el.style.left = 0;
   loop = 0;
   slideRight(0);
});

This simple code snippet creates a Document Object Model (DOM) element, sets some text to it, and registers an onclick handler to it. When the click handler is called, we reset some styling properties of the element (namely, placing the element on the far left side of the screen), and get the animation routine started. The animation routine moves the element to the right a little bit every frame, until the element has reached the right side of the screen. If the element has not yet reached the right side of the screen, or in other words, if the animation is not yet completed, we perform the animation (move the element a few pixels), then register itself with requestAnimationFrame, thus continuing the cycle. Once the animation is complete, we simply stop calling requestAnimationFrame.

A key point to remember is that, one of the major optimizations that the browser does with requestAnimationFrame is to only call it when there is anything to render (in other words, when the tab holding the page is active relative to other tabs). Thus, if the user switches tabs while the animation is in progress, the animation will be paused until the tab is selected again.

In other words, what we ought to do is have requestAnimationFrame call the code that handles the rendering of the game, but not the code that updates the game state. This way, even if the browser is not rendering, the values related to the animation still get animated, but we don't waste CPU and GPU power, rendering something not visible. But as soon as the browser tab becomes active again, the latest data state will be rendered, as if it had been rendering the whole time.

This technique is especially useful for games, as we may not want the entire game to pause when a user switches browser tabs. Then again, we can always benefit from saving the user's battery, which we can achieve by not rendering data to the screen when we don't need to.

Note

Keep in mind that requestAnimationFrame will, by definition, cap the frame rate of your animation loop to the refreshing rate of the monitor. Thus, requestAnimationFrame is not intended to replace native timer implementations, particularly in cases when we'd like the callback function to be invoked at a rate independent from, and possibly higher than a monitor's refresh rate.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.129.253