Stage 2

Stage 2 Overview

In this stage we'll animate our flappy square!

We'll walk through a series of challenges that move our flappy square toward an animation that simulates gravity, causing it to fall off of our canvas.

Lesson: Using Functions

Right now we only have one wall. In our game we're going to need an endless stream of walls for our flappy square to fly through.

Calling `context.fillRect()` over and over and over again in our code is not going to work.

If there is a chunk of code that you are going to use over and over again, putting that code into a function can make your life much easier. Then, to run the code, all you have to do is call the function by typing one line.

In this example, we create a function that will draw a French flag. Because we might want to draw a French flag in lots of different places, we make the x- and y-coordinates of the flag variables. When we call drawFrenchFlag(30, 100), the 30 is assigned to the variable x and the 100 is assigned to the variable y inside of the function. Now we can easily draw as many French flags as we want!

Functions are a core concept in software development. If you feel uncertain about them you should click on the "Functions" quick reference button below and click on the "Full Lesson" button in the pop-up.

Quick Reference:

Message Log

Challenge 1

Let's write three functions that draw our boundary, our flappy square, and our wall.

Our boundary will be drawn in the `drawBoundary()` function which will not need any parameters. All of the information we need for this function comes from our boundary hash variable.

Our flappy square function will be called `drawSquare()` and will not require any parameters either. We'll use our square hash variable within our function to draw the square like so:

``````function drawSquare() {
context.fillRect(square.x, square.y, square.size, square.size);
}``````

Lastly our walls will be drawn in the `drawWall(x)` function which will accept one parameter, the "x" coordinate of the wall.

Once your functions are in place we can call the `drawWall(x)` function three times from a `drawWalls()` function, creating walls every 125 pixels, starting from the left border of the boundary.

So if you wrote code like this:

``````drawWall(100);
drawWall(200);``````

Then the value of 100 and then 200 would be stored in the "x" variable in the drawWall(x) function. The drawWall(x) function would then use that x variable to draw one wall at 100 pixels and another wall at 200 pixels.

Once complete your game should look like the example provided above.

Note: Functions are a core concept in software development. If you feel uncertain about them you should click on the "Functions" quick reference button below and click on the "Full Lesson" button in the pop-up.

Quick Reference:

Previous Challenge: View your code from Stage 1 Challenge 6 to use on this challenge.

Code Missing: You have not yet entered any code in to the previous challenge: Stage 1 Challenge 6
Stage 1 Challenge 6
Message Log
Canvas (your drawing will display here)

A Solution: Here's the code I wrote to complete this challenge. View One Possible Solution

var canvas = document.getElementById('flappy_square_stage2_challenge1'); var context = canvas.getContext('2d'); var boundary = { minX: 25, minY: 25, width: 425, height: 275 }; var square = { x: 50, y: 100, size: 20 }; var wall = { width: 50, height: 100 }; function drawBoundary() { context.strokeRect(boundary.minX, boundary.minY, boundary.width, boundary.height); } function drawSquare() { context.fillRect(square.x, square.y, square.size, square.size); } function drawWall(x) { context.fillRect(x, boundary.minY, wall.width, wall.height); context.fillRect(x, boundary.minY + boundary.height - wall.height, wall.width, wall.height); } function drawWalls() { drawWall(boundary.minX + 125); drawWall(boundary.minX + (125 * 2)); drawWall(boundary.minX + (125 * 3)); } drawBoundary(); drawSquare(); drawWalls();

Using context.translate(x, y)

Between our boundary and our walls we're doing a lot of math to position everything correctly.

For example the example solution above uses the following code in our drawBoundary() function:

``````function drawBoundary() {
context.strokeRect(boundary.minX, boundary.minY, boundary.width, boundary.height);
}``````

In drawWall(x) we are have:

``context.fillRect(x, boundary.minY + boundary.height - wall.height, wall.width, wall.height);``

And in drawWalls() we are have:

``````drawWall(boundary.minX + 125);
drawWall(boundary.minX + (125 * 2));
drawWall(boundary.minX + (125 * 3));``````

In each of these examples we have to do math to shift the everything we are drawing to take into account the boundary.

The canvas's context has a powerful method, context.translate(x, y) that allows you to shift the origin of the canvas.

This means that the point (0, 0) is no longer the top left corner of the canvas.

For example, if you were to call `context.translate(50, 200);` then from then on if you were to call `context.translate(50, 50, 10, 10);` it would draw a square at the point (100, 250) instead of (50, 50);

This means we can shift our origin point to be at the top left corner of our boundary instead of the top left corner of the canvas. This will greatly simplify our code.

In this example we take the flags drawn in the previous example and use context.translate(x, y) to shift the origin to the top left corner of the flag. From there we can more easily draw the flag without having to do any math to position the rectangles.

Quick Reference:

Message Log

Challenge 2

Let's use context.translate(x, y) to make our code a bit simpler and easier to read.

Translate the origin from the top left corner of the canvas (0, 0) to the top left corner of the boundary (boundary.minX, boundary.minY). From that point on the origin (0, 0) will now be the top left corner of the boundary.

This means you'll need to change the rest of your code to take into account the new origin. This should make the code much simpler.

In particular you'll need to adjust the "x" and "y" values in your square hash variable: `square.x`

In the end your flappy square game should look just like it did after the last challenge but the code should be simplified.

Quick Reference:

Previous Challenge: View your code from Stage 2 Challenge 1 to use on this challenge.

Code Missing: You have not yet entered any code in to the previous challenge: Stage 2 Challenge 1
Stage 2 Challenge 1
Message Log
Canvas (your drawing will display here)

A Solution: Here's the code I wrote to complete this challenge. View One Possible Solution

var canvas = document.getElementById('flappy_square_stage2_challenge2'); var context = canvas.getContext('2d'); var boundary = { minX: 25, minY: 25, width: 425, height: 275 }; var square = { x: 25, y: 75, size: 20 }; var wall = { width: 50, height: 100 }; function drawBoundary() { context.strokeRect(0, 0, boundary.width, boundary.height); } function drawSquare() { context.fillRect(square.x, square.y, square.size, square.size); } function drawWall(x) { context.fillRect(x, 0, wall.width, wall.height); context.fillRect(x, boundary.height - wall.height, wall.width, wall.height); } function drawWalls() { drawWall(125); drawWall(125 * 2); drawWall(125 * 3); } context.translate(boundary.minX, boundary.minY); drawSquare(); drawBoundary(); drawWalls();

Challenge 3

You may have noticed that the lesson on `context.translate(x, y)` calls the method context.save() and the method `context.restore()`

``````function drawFrenchFlag(x, y) {
context.save(); // Save the drawing state before making any changes
context.translate(x, y); // Move the origin of the coordinate system to the top left corner of the flag

...

context.restore(); // Restore the drawing state to the way it was when we saved it
}``````

We need to call `context.save()` and `contest.restore()` in order to reset the canvas origin.

If we call `context.translate()` more than once then each translation will build on top of the last. So this:

``````context.translate(20, 20);
context.translate(20, 20);
context.translate(20, 20);
context.fillRect(0, 0, 50, 50);``````

Would cause a square to be drawn at (60, 60).

While this isn't causing any problems yet, it will cause problems as soon as we begin animating our game (in the next section).

So in preparation for the animation let's wrap our translation with a call to `context.save()` and `context.restore()`

Call `context.save()` before the translation and then `context.restore()` once you're done drawing everything.

In the end your flappy square game should look just like it did after the last challenge.

Quick Reference:

Previous Challenge: View your code from Stage 2 Challenge 2 to use on this challenge.

Code Missing: You have not yet entered any code in to the previous challenge: Stage 2 Challenge 2
Stage 2 Challenge 2
Message Log
Canvas (your drawing will display here)

A Solution: Here's the code I wrote to complete this challenge. View One Possible Solution

var canvas = document.getElementById('flappy_square_stage2_challenge3'); var context = canvas.getContext('2d'); var boundary = { minX: 25, minY: 25, width: 425, height: 275 }; var square = { x: 25, y: 75, size: 20 }; var wall = { width: 50, height: 100 }; function drawBoundary() { context.strokeRect(0, 0, boundary.width, boundary.height); } function drawSquare() { context.fillRect(square.x, square.y, square.size, square.size); } function drawWall(x) { context.fillRect(x, 0, wall.width, wall.height); context.fillRect(x, boundary.height - wall.height, wall.width, wall.height); } function drawWalls() { drawWall(125); drawWall(125 * 2); drawWall(125 * 3); } context.save(); context.translate(boundary.minX, boundary.minY); drawSquare(); drawBoundary(); drawWalls(); context.restore();

Lesson: Basics of Animation

There are three required aspects to basic animation in canvas we need to cover:

• Drawing frames in rapid sequence.
• Clearing the canvas in between frames.
• Making small changes to the scene in each frame.

In this lesson we're going to focus on the 1st and 3rd step of basic animation.

We're going to draw a series of frames, each a bit different from each other, creating the illusion of continuous motion.

Drawing frames in rapid sequence can be done a number of ways. For our purposes we'll use the setInterval(function, intervalTime) method. `setInterval(function, intervalTime)` runs the specified code at a regular interval (e.g. every second). You can use setInterval in two different ways. First you can pass it code directly:

``````setInterval(function() {
... some code ...
}, 500)``````

Or you can pass it a reference to an existing function:

``````function myFunction() {
... some code ...
}

setInterval(myFunction, 500);
``````

In this case the function will be called as if you ran `myFunction()` every 500 milliseconds.

We're going to use setInterval() to draw frames in rapid sequence. We'll pass it a reference to the function `programSteps` which contains the instructions for each frame of our game.

We can change the speed of our animation by adjusting the intervalTime in setInterval(function, intervalTime)

For example:

``````setInterval(programSteps, 1000)
``````

will run slowly, drawing a new frame every second, while

``setInterval(programSteps, 10)``

will run very quickly, drawing 100 frames every second. The faster an animation runs the smoother it looks as you can draw frames that change very little, giving the illusion of continuous motion.

Try playing around with different interval speeds to see how it affects the fluidity of the animation.

Quick Reference:

Message Log

Challenge 4

Now we're going to start animating our game.

In this challenge we're going to focus on using setInterval(function, intervalTime) to animate just our flappy square.

We'll make the flappy square move down on the canvas. Soon we'll make it's downward movement look more realistic by mathematically simulating gravity, but for now we'll just make it move down one step at a time.

Use setInterval(function, intervalTime), as provided to draw the flappy square a bit lower in each frame as if it were falling.

Have your flappy square start at (50, 100) and move down 25 pixels every 300 milliseconds.

As we are not yet clearing the canvas in between frames we'll be creating multiple flappy squares. In the next challenge we'll look at cleaning that up to create a true animation.

Your animation should look like the example to the right.

Quick Reference:

Previous Challenge: View your code from Stage 2 Challenge 3 to use on this challenge.

Code Missing: You have not yet entered any code in to the previous challenge: Stage 2 Challenge 3
Stage 2 Challenge 3
Message Log
Canvas (your drawing will display here)

A Solution: Here's the code I wrote to complete this challenge. View One Possible Solution

var canvas = document.getElementById('flappy_square_stage2_challenge4'); var context = canvas.getContext('2d'); var interval; var boundary = { minX: 25, minY: 25, width: 425, height: 275 }; var square = { x: 25, y: 75, size: 20 }; var wall = { width: 50, height: 100 }; function drawBoundary() { context.strokeRect(0, 0, boundary.width, boundary.height); } function drawSquare() { context.fillRect(square.x, square.y, square.size, square.size); } function drawWall(x) { context.fillRect(x, 0, wall.width, wall.height); context.fillRect(x, boundary.height - wall.height, wall.width, wall.height); } function drawWalls() { drawWall(125); drawWall(125 * 2); drawWall(125 * 3); } function adjustPosition() { square.y += 25; } function programSteps() { adjustPosition(); context.save(); context.translate(boundary.minX, boundary.minY); drawBoundary(); drawSquare(); drawWalls(); context.restore(); } function runProgram() { interval = setInterval(programSteps, 300); } // The following code is provided for you. // It creates an eventListener that listens // for the canvas to come into "focus", which // happens when you click on it. // This allows us to stop and start each individual // animation on this whole page separately. function startAnimation() { runProgram(); } function pauseAnimation() { clearInterval(interval); } canvas.addEventListener('focus', startAnimation); canvas.addEventListener('blur', pauseAnimation); canvas.focus();

Lesson: A Complete Animation Cycle

As mentioned in the previous lesson, there are three required aspects to basic animation:

• Drawing frames in rapid sequence.
• Clearing the canvas in between frames.
• Making small changes to the scene in each frame.

In this lesson we're going to complete the cycle by clearing the canvas in between frames.

The method context.clearRect(x, y, width, height) allows us to clear an area of the canvas. For our purposes we'll use it to clear the entire canvas.

In this example we show the example from the last lesson in reverse. If this were run without clearing the canvas you wouldn't see any changes, but since we're clearing the canvas in between each frame we see the drawing get smaller and smaller.

In order to clear the entire canvas we use the following code:

``context.clearRect(0, 0, canvas.width, canvas.height);``

which says "clear an area starting at (0,0) that is as wide as the canvas and as tall as the canvas".

Quick Reference:

Message Log

Challenge 5

As mentioned in the previous lesson, there are three required aspects to basic animation:

• Drawing frames in rapid sequence.
• Clearing the canvas in between frames.
• Making small changes to the scene in each frame.

In this lesson we're going to complete the cycle by clearing the canvas in between frames.

The method context.clearRect(x, y, width, height) allows us to clear an area of the canvas. As the lesson above demonstrates, we want to use it to clear the entire canvas.

So once again, we'll start the flappy square at (50, 100) and move down 10 pixels every 300 milliseconds.

This time, though, use context.clearRect() to clear the canvas in between frames.

Use the method to clear a rectangle starting at (0, 0) with a width of canvas.width and a height of canvas.height.

You'll need to call it from within

``````function programSteps() {
...
}``````

so that it gets called with each frame.

Your animation should look like the example at the beginning of this explanation.

Quick Reference:

Previous Challenge: View your code from Stage 2 Challenge 4 to use on this challenge.

Code Missing: You have not yet entered any code in to the previous challenge: Stage 2 Challenge 4
Stage 2 Challenge 4
Message Log
Canvas (your drawing will display here)

A Solution: Here's the code I wrote to complete this challenge. View One Possible Solution

var canvas = document.getElementById('flappy_square_stage2_challenge5'); var context = canvas.getContext('2d'); var interval; var boundary = { minX: 25, minY: 25, width: 425, height: 275 }; var square = { x: 25, y: 75, size: 20 }; var wall = { width: 50, height: 100 }; function drawBoundary() { context.strokeRect(0, 0, boundary.width, boundary.height); } function drawSquare() { context.fillRect(square.x, square.y, square.size, square.size); } function drawWall(x) { context.fillRect(x, 0, wall.width, wall.height); context.fillRect(x, boundary.height - wall.height, wall.width, wall.height); } function drawWalls() { drawWall(125); drawWall(125 * 2); drawWall(125 * 3); } function adjustPosition() { square.y += 25; } function programSteps() { context.clearRect(0, 0, canvas.width, canvas.height); adjustPosition(); context.save(); context.translate(boundary.minX, boundary.minY); drawBoundary(); drawSquare(); drawWalls(); context.restore(); } function runProgram() { interval = setInterval(programSteps, 300); } // The following code is provided for you. // It creates an eventListener that listens // for the canvas to come into "focus", which // happens when you click on it. // This allows us to stop and start each individual // animation on this whole page separately. function startAnimation() { runProgram(); } function pauseAnimation() { clearInterval(interval); } canvas.addEventListener('focus', startAnimation); canvas.addEventListener('blur', pauseAnimation); canvas.focus();

Lesson: Gravity

Now we need to make our flappy square fall more realistically.

This essentially requires simulating gravity.

Gravity causes objects on earth to move toward the center of the Earth at an accelerating rate, which means that they move faster and faster toward the ground.

The speed of the object toward the ground is called its "velocity". Gravity causes the object's velocity to increase over time.

In this examples when you click "Run" a ball will shoot out at a constant velocity moving right and up while gravity pulls it toward the ground, causing its upward velocity to decrease until it is negative (falling).

In this example gravity is set to 0.4. This means that the upward speed (yVelocity) of our ball will decrease by 0.4 each frame. So in each frame the ball will move up by 0.4 pixels less than the last frame until it is no longer moving at which point it will move down by 0.4 pixels more each frame.

This is an arbitrary value that simply makes the simulation look good as we're not actually trying to simulate gravity accurately.

Also remember that in the canvas the "y" coordinates go up as you move down. This means that subtracting a negative "yVelocity" from our "y" position actually makes our "y" value large, drawing the ball farther down on the canvas.

Quick Reference:

Message Log

Challenge 6

Let's give our flappy square animation a more realistic simulation of gravity.

Using your code from Challenge 5 instead of just moving the flappy square down 25 pixels each frame let's track the y velocity of the flappy square and with each frame let's subtract gravity to increase the velocity downward.

Our flappy square should start at (50, 100) with a yVelocity of 0. Gravity should be set to 0.5. So in each frame you'll subtract gravity from yVelocity and then you'll subtract yVelocity to the y variable. This will cause the y variable to increase at an increasing rate (making the flappy square go down at an increasing rate).

Let's also speed up our animation and instead of running a frame every 300 miliseconds, let's draw a frame every 25 miliseconds.

Your animation should look like the example to the right.

Important note: In Challenge 5 we added 25 to the "y" variable in each frame causing the square to move down. Here we need to subtract "yVelocity" as "yVelocity" will be negative and subtracting a negative is the same as adding a positive (I know this can get confusing with the y-coordinate of the canvas moving in the wrong direction).

Quick Reference:

Previous Challenge: View your code from Stage 2 Challenge 5 to use on this challenge.

Code Missing: You have not yet entered any code in to the previous challenge: Stage 2 Challenge 5
Stage 2 Challenge 5
Message Log
Canvas (your drawing will display here)

A Solution: Here's the code I wrote to complete this challenge. View One Possible Solution

var canvas = document.getElementById('flappy_square_stage2_challenge6'); var context = canvas.getContext('2d'); var interval; var gravity = 0.5; var boundary = { minX: 25, minY: 25, width: 425, height: 275 }; var square = { x: 25, y: 75, size: 20, yVelocity: 0 }; var wall = { width: 50, height: 100 }; function drawBoundary() { context.strokeRect(0, 0, boundary.width, boundary.height); } function drawSquare() { context.fillRect(square.x, square.y, square.size, square.size); } function drawWall(x) { context.fillRect(x, 0, wall.width, wall.height); context.fillRect(x, boundary.height - wall.height, wall.width, wall.height); } function drawWalls() { drawWall(125); drawWall(125 * 2); drawWall(125 * 3); } function adjustPosition() { square.yVelocity += gravity; square.y += square.yVelocity; } function programSteps() { context.clearRect(0, 0, canvas.width, canvas.height); adjustPosition(); context.save(); context.translate(boundary.minX, boundary.minY); drawBoundary(); drawSquare(); drawWalls(); context.restore(); } function runProgram() { interval = setInterval(programSteps, 25); } // The following code is provided for you. // It creates an eventListener that listens // for the canvas to come into "focus", which // happens when you click on it. // This allows us to stop and start each individual // animation on this whole page separately. function startAnimation() { runProgram(); } function pauseAnimation() { clearInterval(interval); } canvas.addEventListener('focus', startAnimation); canvas.addEventListener('blur', pauseAnimation); canvas.focus();