Adding simulated gravity, elasticity, and friction to your objects adds a sense of realism that otherwise would not exist in 2D. These properties are major forces in nature that people feel and understand at nearly every moment of their lives. This means that people who play games expect objects to act in a particular way when these properties are applied. Our job is to simulate those effects as closely as possible, while minimizing the processing power necessary to create them. While there are some very complicated physics equations we could use to create these effects, we will use simplified versions that work well with the limited resources available to HTML5 Canvas in a web browser.

A very simple, yet seemingly realistic gravitational effect can be
achieved by applying a constant gravity value to the `y`

velocity of an object moving on a vector. To
do this, select a value for gravity, such as `.1`

, and then add that value to the `y`

velocity of your object on every call to
`drawScreen()`

.

For this example, let’s simulate a ball with a `radius`

of `15`

pixels being shot from a cannon that rests
near the bottom of the canvas. The ball will move at a `speed`

of `4`

pixels per frame, with an `angle`

of
`305`

degrees. This means it will move
up and to the right on the canvas. If we did not apply any gravity, the
ball would simply keep moving on that vector until it left the canvas
(actually, it would keep moving, we just would not see it any
longer).

You have seen the code to create an effect like this already. In
the `canvasApp()`

function, we would
create the starting variables like this:

var speed = 4; var angle = 305; var radians = angle * Math.PI/ 180; var radius = 15; var vx = Math.cos(radians) * speed; var vy = Math.sin(radians) * speed;

Next, we create the starting point for the ball as `p1`

, and then create a dynamic object that
holds all the values we created for the `ball`

object:

var p1 = {x:20,y:theCanvas.width-radius}; var ball = {x:p1.x, y:p1.y, velocityx: vx, velocityy:vy, radius:radius};

If we want to add gravity to the application, we would first
create a new variable named `gravity`

and set it to a constant value of `.1`

:

var gravity = .1;

Next, in the `drawScreen()`

function, we apply this gravity value to the `ball`

object when it is drawn to the canvas
(`ball.velocityy += gravity`

). We want
the ball to stop moving when it hits the “ground” (the bottom of the
canvas), so we test to see whether the `y`

position of the `ball`

plus the `radius`

of the ball (the outer edge) has passed
the bottom of the canvas (```
ball.y + ball.radius
<= theCanvas.height
```

). If so, we stop the ball’s
movement:

if (ball.y + ball.radius <= theCanvas.height) { ball.velocityy += gravity; } else { ball.velocityx = 0; ball.velocityy = 0; ball.y = theCanvas.height - ball.radius; }

Next, we apply the constant `x`

velocity and the new `y`

velocity to
`ball`

, and draw it to the
canvas:

ball.y += ball.velocityy; ball.x += ball.velocityx; context.fillStyle = "#000000"; context.beginPath(); context.arc(ball.x,ball.y,ball.radius,0,Math.PI*2,true); context.closePath(); context.fill();

Figure 5-17 shows what the path looks like when simple gravity is applied to a ball moving on a vector. We have added the points to illustrate the path.

You can test out Example 5-14 with the
file *CH5EX14.html* in the code
distribution, or type in the full code listing below.

Example 5-14. Simple gravity

a<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CH5EX14: Simple Gravity</title> <script src="modernizr-1.6.min.js"></script> <script type="text/javascript"> window.addEventListener('load', eventWindowLoaded, false); function eventWindowLoaded() { canvasApp(); } function canvasSupport () { return Modernizr.canvas; } function canvasApp() { if (!canvasSupport()) { return; } function drawScreen () { context.fillStyle = '#EEEEEE'; context.fillRect(0, 0, theCanvas.width, theCanvas.height); //Box context.strokeStyle = '#000000'; context.strokeRect(1, 1, theCanvas.width-2, theCanvas.height-2); if (ball.y + ball.radius <= theCanvas.height) { ball.velocityy += gravity; } else { ball.velocityx = 0; ball.velocityy = 0; ball.y = theCanvas.height - ball.radius; } ball.y += ball.velocityy; ball.x += ball.velocityx; context.fillStyle = "#000000"; context.beginPath(); context.arc(ball.x,ball.y,ball.radius,0,Math.PI*2,true); context.closePath(); context.fill(); } var speed = 4; var gravity = .1; var angle = 305; var radians = angle * Math.PI/ 180; var radius = 15; var vx = Math.cos(radians) * speed; var vy = Math.sin(radians) * speed; theCanvas = document.getElementById("canvasOne"); context = theCanvas.getContext("2d"); var p1 = {x:20,y:theCanvas.width-radius}; var ball = {x:p1.x, y:p1.y, velocityx: vx, velocityy:vy, radius:radius}; setInterval(drawScreen, 33); } </script> </head> <body> <div style="position: absolute; top: 50px; left: 50px;"> <canvas id="canvasOne" width="500" height="500"> Your browser does not support HTML5 Canvas. </canvas> </div> </body> </html>

The last example showed what a cannonball might look like if it was shot out, landed on a surface, and stuck there with no reaction. However, even a heavy cannonball will bounce when it hits the ground.

To create a bouncing effect we do not have to change the code very
much at all. In `drawScreen()`

, we
first apply `gravity`

on every frame;
then, instead of stopping the ball if it hits the bottom of the canvas,
we simply need to reverse the `y`

velocity of `ball`

when it hits the
ground.

In *CH5EX14.html* you would
replace this code…

if (ball.y + ball.radius <= theCanvas.height) { ball.velocityy += gravity; } else { ball.velocityx = 0; ball.velocityy = 0; ball.y = theCanvas.height - ball.radius; }

…with this:

ball.velocityy += gravity; if ((ball.y + ball.radius) > theCanvas.height) { ball.velocityy = -(ball.velocityy) }

This code will send the ball bouncing back “up” the canvas. Since
it is still traveling on the vector, and gravity is applied every time
`drawScreen()`

is called, the ball will
eventually come down again as the applied `gravity`

overtakes the reversed `y`

velocity.

Figure 5-18 shows what the cannonball looks like when the bounce is applied.

To achieve a nice-looking bounce for this example, we also
changed the `angle`

of the vector in
`canvasApp()`

to `295`

:

var angle = 295;

Example 5-15 offers the full code.

Example 5-15. Simple gravity with a bounce

<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CH5EX15: Gravity With A Bounce</title> <script src="modernizr-1.6.min.js"></script> <script type="text/javascript"> window.addEventListener('load', eventWindowLoaded, false); function eventWindowLoaded() { canvasApp(); } function canvasSupport () { return Modernizr.canvas; } function canvasApp() { if (!canvasSupport()) { return; } function drawScreen () { context.fillStyle = '#EEEEEE'; context.fillRect(0, 0, theCanvas.width, theCanvas.height); //Box context.strokeStyle = '#000000'; context.strokeRect(1, 1, theCanvas.width-2, theCanvas.height-2); ball.velocityy += gravity; if ((ball.y + ball.radius) > theCanvas.height) { ball.velocityy = -(ball.velocityy) } ball.y += ball.velocityy; ball.x += ball.velocityx; context.fillStyle = "#000000"; context.beginPath(); context.arc(ball.x,ball.y,ball.radius,0,Math.PI*2,true); context.closePath(); context.fill(); } var speed = 5; var gravity = .1; var angle = 295; var radians = angle * Math.PI/ 180; var radius = 15; var vx = Math.cos(radians) * speed; var vy = Math.sin(radians) * speed; theCanvas = document.getElementById("canvasOne"); context = theCanvas.getContext("2d"); var p1 = {x:20,y:theCanvas.width-radius}; var ball = {x:p1.x, y:p1.y, velocityx: vx, velocityy:vy, radius:radius}; setInterval(drawScreen, 33); } </script> </head> <body> <div style="position: absolute; top: 50px; left: 50px;"> <canvas id="canvasOne" width="500" height="500"> Your browser does not support HTML5 Canvas. </canvas> </div> </body> </html>

In physics, the *elasticity* of a bouncing ball
refers to how much energy is conserved when a ball bounces off a
surface. We already covered a bit about conservation of energy when we
discussed balls colliding, but when we are simulating objects falling,
we need to take a slightly different path with our code. In Example 5-15, we applied 100%
elasticity and the ball bounced forever (actually, this was only implied
since we did not consider elasticity at all). However, in real life,
balls usually lose some of their energy every time they bounce off a
surface. The amount of energy conserved depends on the material the ball
is made from, as well as the surface it is bouncing on. For example, a
rubber Super Ball is much more elastic than a cannonball and will bounce
higher on the first bounce off a surface. Both will bounce higher off a
concrete surface than a surface made of thick mud. Eventually, both will
come to rest on the surface as all the energy is transferred away from
the ball.

We can simulate simple elasticity by applying a constant value to
the ball when it bounces off the ground. For this example, we will set
the `speed`

of the ball to `6`

pixels per frame, and the `angle`

to `285`

. We will keep our `gravity`

at `.1`

, but set a new variable named `elasticity`

to `.5`

. To make this more straightforward, we will
also assume that the surface the ball is bouncing on does not add or
subtract from the elasticity of the ball.

In `canvasApp()`

we would set the
new properties like this:

var speed = 6; var gravity = .1; var elasticity = .5; var angle = 285;

We then add the new `elasticity`

property to the `ball`

object because,
unlike `gravity`

, elasticity describes
a property of an object, not the entire world it resides within. This
means that having multiple balls with different values for elasticity
would be very easy to implement:

var ball = {x:p1.x, y:p1.y, velocityx: vx, velocityy:vy, radius:radius, elasticity: elasticity};

In the `drawScreen()`

function,
we still add the `gravity`

value to the
`y`

velocity (`velocityy`

). However, instead of simply
reversing the `y`

velocity when the
`ball`

hits the bottom of the canvas,
we also multiply the `y`

velocity by
the `elasticity`

value stored in the
`ball.elasticity`

property. This
applies the elasticity to the bounce, preserving the `y`

velocity by the percentage value of `elasticity`

for the object:

ball.velocityy += gravity; if ((ball.y + ball.radius) > theCanvas.height) { ball.velocityy = -(ball.velocityy)*ball.elasticity; } ball.y += ball.velocityy; ball.x += ball.velocityx;

In Figure 5-19 you can see what this application looks like when executed in a web browser.

With `gravity`

applied, the
bounce is not exactly as you might expect. Gravity is always pulling
down on our object, so the effect of a loss of `y`

velocity due to an elastic bounce is
pronounced.

The full code is shown in Example 5-16.

Example 5-16. Simple gravity with bounce and elasticity

<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CH5EX16: Gravity With A Vector With Bounce And Elasticity</title> <script src="modernizr-1.6.min.js"></script> <script type="text/javascript"> window.addEventListener('load', eventWindowLoaded, false); function eventWindowLoaded() { canvasApp(); } function canvasSupport () { return Modernizr.canvas; } function canvasApp() { if (!canvasSupport()) { return; } function drawScreen () { context.fillStyle = '#EEEEEE'; context.fillRect(0, 0, theCanvas.width, theCanvas.height); //Box context.strokeStyle = '#000000'; context.strokeRect(1, 1, theCanvas.width-2, theCanvas.height-2); ball.velocityy += gravity; if ((ball.y + ball.radius) > theCanvas.height) { ball.velocityy = -(ball.velocityy)*ball.elasticity; } ball.y += ball.velocityy; ball.x += ball.velocityx; context.fillStyle = "#000000"; context.beginPath(); context.arc(ball.x,ball.y,ball.radius,0,Math.PI*2,true); context.closePath(); context.fill(); } var speed = 6; var gravity = .1; var elasticity = .5; var angle = 285; var radians = angle * Math.PI/ 180; var radius = 15; var vx = Math.cos(radians) * speed; var vy = Math.sin(radians) * speed; theCanvas = document.getElementById("canvasOne"); context = theCanvas.getContext("2d"); var p1 = {x:20,y:theCanvas.width-radius}; var ball = {x:p1.x, y:p1.y, velocityx: vx, velocityy:vy, radius:radius, elasticity: elasticity}; setInterval(drawScreen, 33); } </script> </head> <body> <div style="position: absolute; top: 50px; left: 50px;"> <canvas id="canvasOne" width="500" height="500"> Your browser does not support HTML5 Canvas. </canvas> </div> </body> </html>

Now that we have a ball traveling on a vector that is affected by
both gravity and elasticity, we have one more element to add to make the
animation more realistic. In the previous example, the `y`

velocity was affected by gravity and
elasticity, but the ball still traveled on the x-axis without any
degradation in velocity. We will fix this issue now by adding friction
into the equation.

In physics, *friction* is a force that resists
the motion of an object. We have already discussed friction as it
applies to colliding balls, and this implementation is similar except
that it affects only the `x`

velocity.
For our purposes, we will achieve simple friction by degrading the
`x`

velocity as gravity degrades the
`y`

velocity.

Taking the code from Example 5-16, in `canvasApp()`

we create a new variable named
`friction`

. This is the amount of
pixels to degrade the `x`

velocity on
every frame:

var friction = .008;

Notice that the amount is quite small. Friction does not have to
be a large value to look realistic—it just needs to be applied uniformly
each time `drawScreen()`

is called. In
`drawScreen()`

, we apply `friction`

to the `x`

velocity like this:

ball.velocityx = ball.velocityx - ( ball.velocityx*friction);

This is the same type of proportional application of friction we
used with the colliding balls, but again, this time we applied it only
to the `x`

velocity.

Figure 5-20 shows what this final version of our application looks like when executed in a web browser.

Example 5-17 gives
the full code for *CH5EX17.html*, the
final code of our simple gravity, simple elasticity, and simple friction
example.

Example 5-17. Gravity with a vector with bounce friction

<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CH5EX17: Gravity With A Vector With Bounce Friction</title> <script src="modernizr-1.6.min.js"></script> <script type="text/javascript"> window.addEventListener('load', eventWindowLoaded, false); function eventWindowLoaded() { canvasApp(); } function canvasSupport () { return Modernizr.canvas; } function canvasApp() { if (!canvasSupport()) { return; } function drawScreen () { context.fillStyle = '#EEEEEE'; context.fillRect(0, 0, theCanvas.width, theCanvas.height); //Box context.strokeStyle = '#000000'; context.strokeRect(1, 1, theCanvas.width-2, theCanvas.height-2); ball.velocityx = ball.velocityx - ( ball.velocityx*friction); ball.velocityy += gravity; if ((ball.y + ball.radius) > theCanvas.height) { ball.velocityy = -(ball.velocityy)*ball.elasticity; } ball.y += ball.velocityy; ball.x += ball.velocityx; context.fillStyle = "#000000"; context.beginPath(); context.arc(ball.x,ball.y,ball.radius,0,Math.PI*2,true); context.closePath(); context.fill(); } var speed = 6; var gravity = .1; var friction = .008; var elasticity = .5; var angle = 285; var radians = angle * Math.PI/ 180; var radius = 15; var vx = Math.cos(radians) * speed; var vy = Math.sin(radians) * speed; theCanvas = document.getElementById("canvasOne"); context = theCanvas.getContext("2d"); var p1 = {x:20,y:theCanvas.width-radius}; var ball = {x:p1.x, y:p1.y, velocityx: vx, velocityy:vy, radius:radius, elasticity: elasticity}; setInterval(drawScreen, 33); } </script> </head> <body> <div style="position: absolute; top: 50px; left: 50px;"> <canvas id="canvasOne" width="500" height="500"> Your browser does not support HTML5 Canvas. </canvas> </div> </body> </html>

Start Free Trial

No credit card required