A Framework for Animation and Gaming Using HTML5
Donnie Santos and I teach programming courses at the same college. Not surprisingly, many students mention gaming and animation as reasons for their interest in programming. Gaming, anime, and other forms of animation generate a lot of buzz, which is why we often introduce animation concepts into our programming courses. Some of the more recent buzz revolves around the Canvas element in HTML 5, which allows for dynamic, scriptable rendering of 2D shapes and bitmap images. In this article, we provide a framework for creating basic animation in HTML 5 Canvas.
In HTML 5, Canvas generally corresponds to the functionality of Flash in earlier browsers. Canvas is intended to be standard across all browsers, as opposed to Flash, which is proprietary. In theory, installing the Flash player provides a reliable model for animation, regardless of the platform. As with everything else in HTML 5, the hope is that all browsers will eventually comply with the specification, including Canvas. Meanwhile, the current performance of Canvas with respect to Flash is open to debate.
The framework that we present here will eventually lead to an implementation of a modified Pong game. This framework consists of three primary gaming components: a rectangle (paddle), a circle (ball), and some basic text. The concepts are simple, yet central to any animation. Our intent is to give you a concise and compact amount of code that you can easily get up and running. Then it's up to you to research, explore, and expand the game.
The Canvas Framework: HTML
One strength of this game framework is that it doesn't require a lot of code, and this application is almost exclusively JavaScript. The lone HTML file is pretty concise:
<HTML> <BODY> <canvas id="myCanvas" width="600" height="600" style="border:1px solid #000000;" /> <script src="JQuery.js"></script> <script src="scripts.js"></script> </BODY> </HTML>
The canvas for the application will be 600 × 600 pixels. Basically, the canvas is the area of the screen on which we'll draw the animation. In 2D graphics, we'll call the paint() method over and over to draw on the canvas. This act of continuously painting will create the effect of animation.
The only other lines of code in the HTML are the callouts to JQuery and the application's JavaScript file, scripts.js.
The Animation Code in Chrome: JavaScript
The heart of this application is in the JavaScript. Considering the core functionality, it's not much code. This is the entire script:
// Global scope and variables var Main = {}; Main.Canvas = document.getElementById('myCanvas'); Main.Context = Main.Canvas.getContext('2d'); // Mouse coordinates Main.MX = 0; Main.MY = 0; // Keep track of the mouse Main.Canvas.onmousemove = function(event) { if (event.offsetX) { mouseX = event.offsetX; mouseY = event.offsetY; } else if (event.layerX) { mouseX = event.layerX; mouseY = event.layerY; } Main.MX = mouseX; Main.MY = mouseY; } // Do the animation Main.Animate = function() { Main.Context.clearRect(0, 0, Main.Canvas.width, Main.Canvas.height); // Draw the rectangle Main.Context.fillStyle = "#FF0000"; Main.Context.fillRect(0, 0, 150, 75); // Draw the circle Main.Context.beginPath(); Main.Context.arc(395, 150, 60, 0, 2 * Math.PI); Main.Context.stroke(); // Draw the current position of the mouse Main.Context.font = "30px Arial"; Main.Context.fillText("X: " + Main.MX + " Y: " + Main.MY, 100, 150); requestAnimFrame(function() { Main.Animate(); }); } // Browser compatibility window.requestAnimFrame = (function(callback) { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60); }; })(); // When page is loaded $(document).ready(function() { Main.Animate(); });
Before we start explaining the code details, go ahead and load the application in the Chrome browser. Using Chrome is important because we used that platform for these examples. Once you get the code running in Chrome, you can expand your testing to other browsers.
When you execute this application, the screen in Figure 1 appears in the browser.
Figure 1 The initial page in Chrome.
The canvas (600 × 600) is immediately apparent as defined by the border. Notice the three game elements: the rectangle and circle are the game pieces, and the text indicates the position of the mouse. When you move the mouse, its text coordinates are updated, proof that the screen is being repainted continuously.
Game Concepts
Let's take a look into the code. Here's the first line in the application:
var Main = {};
To make life easier, we create a sort-of-global scope called Main, which allows us to reference components at the application (global) level. This technique may not technically be required, but it makes the code much easier to construct and follow in relationship to the design. This reasoning will become apparent in the next several lines of code.
The second line represents the actual canvas:
Main.Canvas = document.getElementById('myCanvas');
Here we assign the element from the HTML to Main.Canvas. We could do this (perhaps more efficiently) in JQuery, but for this basic example we'll stick with document.getElementById.
Canvas is a predefined object that comes with methods defined by the specification, such as getContext(). If you use the getContext() method with the parameter '2d', you can acquire a context object:
Main.Context = Main.Canvas.getContext('2d');
The names Main.Canvas and Main.Context are not specifically required (you can name them anything you want), but we use them here for clarity.
For all of your canvas applications, it's good practice to create globally scoped MX and MY variables to keep track of the mouse's location:
Main.MX = 0; Main.MY = 0;
Loading the Document
When the application code is loaded into the browser (when the application is ready), we execute the following code:
$(document).ready(function() { Main.Animate(); });
The call to Animate() is the only line of code in this method, because it's a recursive process.
The Animate Method
The final Pong game will include much more functionality in the Animate() method, but at this point we'll strip the code down to the basics for illustration purposes. Obviously, more variables will be declared in the final Pong application, as well as more code.
At this point, we're simply painting three primary components of our Pong game—a rectangle (paddle), a circle (ball), and the text for the mouse coordinates:
Main.Animate = function() { Main.Context.clearRect(0, 0, Main.Canvas.width, Main.Canvas.height); // Draw the rectangle Main.Context.fillStyle = "#FF0000"; Main.Context.fillRect(0, 0, 150, 75); // Draw the circle Main.Context.beginPath(); Main.Context.arc(395, 150, 60, 0, 2 * Math.PI); Main.Context.stroke(); // Draw the current position of the mouse Main.Context.font = "30px Arial"; Main.Context.fillText("X: " + Main.MX + " Y: " + Main.MY, 100, 150); requestAnimFrame(function() { Main.Animate(); }); }
In addition to painting the paddle, ball, mouse coordinates, and current score, which are specific to this application, a couple of lines are included in all animations. The following line clears the entire context (screen) using the globally defined coordinates:
Main.Context.clearRect(0, 0, Main.Canvas.width, Main.Canvas.height);
The other really interesting line in this method is the last one:
requestAnimFrame(function() { Main.Animate(); });
The window in HTML/JavaScript (the DOM) requires that you call this method at the end of your Animate() method:
window.requestAnimFrame = (function(callback) { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60); }; })();
We won't go into detail here regarding this method; essentially it handles browser differences and various compatibility issues. The one feature that we will point out in this method is the following line of code, which controls the speed at which the screen is repainted:
callback, 1000 / 60
You may have noticed that the Animate() method actually calls itself in an endless loop. This is a prime example of recursion. Since the Animate() method is called over and over, the screen is continuously repainted. Closing the window is the only way to stop the application.
The Framework
The following code is pretty basic, but consider it a framework to be included in all animations:
Main.Animate = function() { // Animation Code Goes Here requestAnimFrame(function() { Main.Animate(); }); } window.requestAnimFrame = (function(callback) { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60); }; })(); $(document).ready(function() { Main.Animate(); });
The Pong Game
All the animation code for the Pong game is listed at the end of this section in the Main.Animate() method. Actually, not much code has to be added. As noted earlier, this is a modified Pong game:
- A single ball moves around the screen.
- The left side of the screen holds a single paddle.
- The mouse controls the ball's location.
- The screen displays the coordinates of the paddle and the ball, along with the current score.
The game starts by launching a ball, which bounces around the confines of the Canvas. When the ball hits a wall, it changes direction. The point of the game is to use the mouse to control the paddle and strike the ball before it hits the "left wall." Each time the ball strikes the paddle, the game records a "hit." If the paddle misses the ball and bounces off the left wall, the game records a "miss."
Note the C-style comments in the code, which explain the important concepts of this game. The amount of code is small enough that the comments are the best form of illustration. As noted earlier, we won't explain every aspect of the code here; we leave the details for you to explore. However, the following list provides the important design requirements for the functionality of this Pong game:
- The size and shape of the paddle and ball have been adjusted for this specific game.
- Variables keep track of the ball's direction and angle.
- Additional code controls the paddle with the mouse.
- Code checks when the ball intersects the plane of the paddle.
- Code checks whether the ball strikes the paddle.
- If the ball strikes the paddle, it changes the direction and angle of the ball.
- If the ball strikes the paddle, the score of "hits" is incremented.
- If the ball strikes the left wall, the score of "misses" is incremented.
Following is the code that implements these requirements:
// Do the animation Main.Animate = function() { Main.Context.clearRect(0, 0, Main.Canvas.width, Main.Canvas.height); // Draw Rectangle Main.Context.fillStyle = "#FF0000"; Main.Context.fillRect(0, Main.MY, 25, 50); // Draw Circle Main.Context.beginPath(); // When ball crosses the paddle width, // check to see if paddle intersects path if ( (Main.CX-Main.CRAD == 25) && (Main.XINC == -1) ) { // if ball hits paddle, change increment (both X & Y) if ( (Main.CY>Main.MY) && (Main.CY<(Main.MY+50)) ){ Main.XINC = Main.XINC * (-1); Main.YINC = Main.YINC * (-1); Main.HITS = Main.HITS + 1; } else Main.MISSES = Main.MISSES + 1; } // If we hit a wall in x coordinate, then change x direction if ( (Main.CX < 0+Main.CRAD) || (Main.CX > 600-Main.CRAD)) Main.XINC = Main.XINC * (-1); Main.CX = Main.CX + (Main.XINC); // If we hit a wall in y coordinate, then change y direction if ( (Main.CY < 0+Main.CRAD) || (Main.CY > 600-Main.CRAD)) Main.YINC = Main.YINC * (-1); Main.CY = Main.CY + Main.YINC; Main.Context.arc(Main.CX, Main.CY, Main.CRAD, 0, 2 * Math.PI); Main.Context.stroke(); // Display the location of the mouse and circle Main.Context.font = "10px Arial"; Main.Context.fillText("Mouse: X: " + Main.MX + " Y: " + Main.MY, 50, 25); Main.Context.fillText("Ball: X: " + Main.CX + " Y: " + Main.CY, 350, 25); // Display the score Main.Context.font = "30px Arial"; Main.Context.fillText("Hits: " + Main.HITS + " Misses: " + Main.MISSES, 50, 100); // Browser compatibility requestAnimFrame(function() { Main.Animate(); }); }
Executing this code launches the game in the browser, as shown in Figure 2.
Figure 2 Running the animation in Chrome.
Conclusion
Now that we've explained the framework and basic concepts, it's time for the reader to start exploring. Once the code provided in this article is up and running, you should experiment, revising the specifications of the Pong game to add new functionality. Use the animation framework we've provided and create a baseline set of code that you can incorporate into any animation application.
Finally, test the application with various browsers (different vendors as well as versions) to identify issues that may affect distribution of the applications you might publish. It would also be interesting to see how your Canvas applications compare to Flash in programming, portability, and performance.
Our hope is that this concise animation framework will whet your programming appetite for the Canvas specification, as well as your interest in gaming and other forms of animation. The w3schools site provides a wealth of information about Canvas, HTML 5, and many other topics.