24 Games and Animations
The canvas opens up all kinds of possibilities for detailed animations. In this chapter we’ll talk about how to implement an animation, and how to construct a simple 2D game, which can be thought of as an animation that’s partly under user control.
Animation refers to any picture in which elements seem to move or change over time. The basic technique in any form of animation is to present a series of animation frames (i.e. pictures) one after another very quickly, with each frame slightly different from the last. For example, an object might move very slightly to the right or grow slightly from frame to frame while the rest of the picture remains unchanged. If this is done quickly enough, the human eye will be fooled into seeing smooth motion.
To animate a canvas element using JavaScript, we need three things:
- Variables that specify important details about the animation (e.g. x and y positions, colors, sizes, of animated objects etc.)
- A function that updates these variables and then redraws the canvas using the new values.
- A way to repeatedly trigger the update function.
We’ll start with item 3 from the list above.
JavaScript Timers
JavaScript functions are called in response to events. If you want a function to be called repeatedly, or just once but at some time in the future, you can schedule a timer event. The window object contains a setTimeout function to trigger an event some time in the future, and a setInterval function to trigger an event repeatedly at set intervals.
To trigger an event repeatedly, you can use the following function call:
setInterval(function, interval);
The arguments are the function you want to call when a timer event happens, and the interval in milliseconds between each timer event.
For example, if you want to call a function named tick every half a second (500 milliseconds), the command would be:
setInterval(tick, 500);
To call a function just once, but at some time in the future, use the following command to schedule a single timer event:
setTimeout(function, interval);
The parameters are the same as setInterval, but the function you specify will only be called once, after interval milliseconds.
Do it Yourself
Try the following in the JavaScript console:
function hi() { console.log("Hello"); } setTimeout(hi,2000);
Now wait two seconds for your message to appear.
Try the same thing on a repeating timer:
setInterval(hi,1000);
Now you will get a message every second. You can reload the page to make it stop.
(Note that in some consoles, repeating identical messages are presented on one line with a count of the number of times they’ve been output).
Both setTimeout and setInterval return an integer id for the timer you have created. If you want to stop a timer, you can do so with the clearTimeout function. But you have to have saved the id of the timer in a variable.
Do it Yourself
Try this in the JavaScript console to make an infinite counter:
let i=1; let timerId = setInterval(function(event) { console.log(i++) }, 500);
With the timerId variable you can stop the timer at any time, like this:
clearTimeout(timerId);
This will also work for setTimeout.
Do it Yourself
Get the timerExample.html file from the Full Stack Example Pack and load it into a browser.
You should see the word “tick” appear every 0.5 seconds, and after just over 5 seconds you should see “*** BOOM ***” appear. You can silence the ticking (but not the boom) by pressing the “quiet” button.
Examine the code for this file make sure you can answer these questions:
- Which functions stop and start the timers?
- When does each of these functions get called?
- What is the purpose of the boom and tick functions?
- How does the stop function know which timer to stop?
Now add code to the file so that there is a “defuse” button that, when pressed, will prevent the boom message from being displayed (if it hasn’t already been displayed).
Animation Variables
Animation is about repeatedly updating variables and redrawing the scene. The animation variables need to be global (or at least not local to the update function). To reduce clutter, it’s a good idea to encapsulate all the relevant variables for any object you are animating into a single JavaScript object.
Suppose we’re animating a simple ball object. Our object might look like this:
let ball = { x: 100, y: 200, xSpeed: -1, ySpeed: -1, red: 255, green: 0, blue: 0, size: 10 };
The single ball variable now holds all the important information about a single ball: the x and y location, some drawing information (size and color), and a speed vector represented as xSpeed and ySpeed. On each animation frame, the x and y values will be changed according to this speed.
The Update Function
Animation is about repeatedly updating variables and redrawing the scene. The last piece of this system is an update function. This function will be called repeatedly by setInterval and do the following:
- Update the animation variables (for example, ball.x = ball.x + ball.xSpeed).
- Clear the canvas (ctx.clearRect(0, 0, c.width, c.height)).
- Redraw the scene on the canvas using the updated animation variables.
For the appearance of smooth motion, the setInterval function should be used to trigger a call to updateAnimation about 60 times per second. That works out to an interval of approximately 16 milliseconds.
Do it Yourself
Get the simpleCanvasAnimation.html file from the Full Stack Example Pack and load it into a browser.
- When and how does the animation start?
- When and how does the animation stop?
- What would happen if you skipped the clear canvas step?
Now try the following:
- Make the ball move faster and in a different direction.
- Make the ball grow as it moves.
- Make the ball fade to a different color as it moves.
Do it Yourself
The same techniques used to animate a drawing on a canvas can be used to animate an entire HTML element.
If you give an element position: absolute, you can move it around the screen by updating its top and left CSS properties. The animation is actually a little easier to accomplish because there is no canvas to clear – the browser will take care of updating its view whenever you change the CSS properties.
Get the simpleCSSAnimation.html file from the Full Stack Example Pack and load it into a browser.
- When and how does the animation start?
- When and how does the animation stop?
- What would happen if you skipped the clear canvas step?
Now try the following:
- Make the ball move faster and in a different direction.
- Make the ball grow as it moves.
- Make the ball fade to a different color as it moves.
Aside: Images and Sounds
To make a nicer animations, you might want to load images from a file and then draw them on the canvas. To make a better overall experience for the user, you also might want to play sounds in response to certain events.
To draw an image, you have to create an Image object, then load an image file into it, then draw it. Creating the image object and loading the image should be done once and stored in a global variable.
let myImage = new Image(); myImage.src = "images/myImageFile.jpg";
Once the image is loaded, you can draw it on the canvas in response to an event.
ctx.drawImage(myImage, x, y);
To play a sound, you have to create an Audio object, then load an audio file into it, then play it. Creating the Audio object and loading the audio file should be done once and stored in a global variable.
let myAudio = new Audio("sounds/myAudioFile.mp3");
Once the audio is loaded, you can play it any time. This is done in two steps.
myAudio.load(); // reset the sound myAudio.play(); // play the sound
But there is one problem lurking in the above instructions. When your app is online, the HTML page will load first and the sounds and images will only begin to be downloaded from your site when you create the Audio and Image objects. What will happen if you try to draw your images or play your sounds before they have loaded?
To deal with this problem for images, you can set up a load event listener like this:
let myImageReady = false; myImage.addEventListener("load", function(event) { myImageReady = true; });
The above listener sets a global Boolean flag called myImageReady to true to let the rest of the app know the image is ready. Other listeners can check this flag before responding to events that would require the image.
The solution for audio is similar but uses the canplaythrough event:
let myAiduoReady = false; myAudio.addEventListener("canplaythrough", function(event) { myAudioReady = true; });
In this case, myAudioReady will be set to true once the sound can be played through from beginning to end.
Do it Yourself
Try out soundAndVision.html from the Full Stack Example Pack to see images and audio in action. You will have to make sure you also have images/bird.egg, images/bird.png, and sounds/squeeze-toy-5.mp3 in place in order for everything to work.
Note that this silly little app is actually surprisingly complicated under the hood, and requires an array of egg objects in order to redraw the entire screen every time the mouse moves.
Your First Canvas Game
Now you have everything you need to make a simple game in the HTML5 canvas. Here’s a list of basic components you can adapt to suit your needs:
- A set of global variables containing the moveable objects in the game. Each game object might store the position, speed, direction, size, color, and other attributes required to draw and animate that object.
- You will also also need variables to control the state of the game (game on, paused, game over, current score, and so on). You could encapsulate these variables into a single game object.
- A set of functions that are called in response to keyboard and mouse events. These function should make changes to the global variables that control the player objects in the game.
- An update function that is called once every 16 milliseconds.
The primary job of update is to draw the current frame. But before it does that, it should update the positions of all the objects based on the relevant variables. It should also check for collisions between objects or between objects and the edges of the screen and make changes as a result (reverse speeds to simulate a bounce, increase the score, mark an alien as “dead”, end the game, etc.)
Here are a few extra tips to help you out:
- An object’s direction and speed can be represented as a vector, using a 2-element array or two variables for xSpeed and ySpeed. Add these variables to x and y respectively to move an object one step.
- You don’t have to use integers. If you set the x and y speeds to -0.5 and 0.25 you will get an object that moves slowly to the left and down.
- To make an object bounce, you can reverse its speed (if it’s positive make it negative and vice-versa). If you reverse both, it will bounce back the way it came. If you reverse only the x speed it will seem to bounce off a vertical surface. If you reverse the y speed it will seem to bounce off a horizontal surface. This simple idea can be fiddly to implement correctly, though.
- Gravity can be simulated by adding a small amount (e.g. 0.01) to the ySpeed of an object at every call to updateGame.
- Collisions can be approximated by measuring how close two objects are to one another. Use their x and y locations to compute the distance between them using the length of a line segment formula. If they are too close, it’s a collision.
That’s it. The rest you will have to work out for yourself. Happy gaming!
<exercises chapter=”animation”>
- Fetch either sampleCanvasAnimation.html or sampleCSSAnimation.html from the Full Stack Example Pack.
- Modify it so that the ball moves down and to the right.
- Now modify it so that the ball shrinks as it moves.
- Finally, modify it so that the ball starts off as black and ends as red (Hint: define a red variable to start at 0 and grow to 255. Then set the color using “rgb(___, 0 , 0)” with the value of your variable in the blank.
- Modify sampleCanvasAnimation.html or sampleCSSAnimation.html (or update moveme.html) from the Full Stack Example Pack so that the ball moves and bounces when it hits the edge of the screen. (This can be a bit tricky. It might work to multiply the speeds by -1, or it might be better to force them to positive or negative versions of themselves.)
- Get flyin.html from the Full Stack Example Pack and add code to it to make the h1 elements fly in from the side of the screen. Start with them all moving together, then see if you can extend it to make them fly in at different times.
- Create your own canvas animation from scratch with at least two elements moving or changing at once.
- Catch The Rabbit Revisited
Go back to catchTheRabbit.html from the Full Stack Example Pack. Delete 3 of the rabbits and create a new version of the game where the rabbit runs to a different (randomly selected) location on the screen when you mouse over it. Use window.innerWidth and window.innerHeight to get the height and width of the screen. For global variables, create a rabbit object with rabbit.x and rabbit.y to represent the rabbit’s position and rabbit.targetX and rabbit.targetY to represent where the rabbit is going. Each call to updateAnimation should just move rabbit.x and rabbit.y closer to the targets. When the user mouses over the rabbit, set a new target. - Checkerboards Revisited
Get the canvasTestBed4.html file from the Full Stack Example Pack. Write a function that creates an 8×8 checker board pattern in the canvas when the button 1 is clicked (if you haven’t already done that). Now make the modifications necessary so that after button 1 is clicked the checkerboard reverses every 250 milliseconds. Extend your solution so that when you press button 1 again, it stops the animation. - Greeting Card Project
- Create a web app to display custom greeting cards for a mobile phone user (with a screen size of 320px by 480px).When the user loads the page, they should see a heading and a form that lets them customize their card. The form should let them enter a date, the name of the event, the name of the recipient and other information as you see fit.
- There should be a “make card” button that, when they hit it, hides the form and reveals a canvas showing the “front” of the card, with a simple but elegant design that incorporates the text they entered. The card design should include some type of animated element (something moving, changing color, growing or shrinking, etc).
- Then, when the user clicks the card, make it “open” to show them a different design on the inside.
- Pong Project
- Go back to the Pong game from the previous chapter’s exercises and get both paddles moving in response to separate keyboard inputs. The best way to do this is to treat the entire game like an animation. When a user presses the “up” button for a paddle, set its y speed to a negative amount. When they release the key, set the ySpeed to 0.
- Then get the Pong ball moving. Make it bounce off walls and off the paddles.
- Put sounds into your Pong game and use images for the paddles and ball.
- Finish the game! It should pause and update the score when one of the vertical walls is hit. It should end when one player has 5 points.
- For an extra challenge, move on to a brick-breaker game like Breakout or Arkanoid next.
</exercises>