back to the canvas element animating within the canvas

166
Back to the canvas element Animating within the canvas

Upload: alberta-butler

Post on 12-Jan-2016

266 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Back to the canvas element Animating within the canvas

Back to the canvas element

Animating within the canvas

Page 2: Back to the canvas element Animating within the canvas

Motivating example• View this page in a -moz- or -webkit- browser: http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g16/main.html• In the canvas on this page, a shark eats three divers• The "drama" has a soundtrack, sound effects and introductory and postscript

segments• In this set of slides, we develop a set of object classes which can be used to build

– other dramas like this– interactive dramas/games

Page 3: Back to the canvas element Animating within the canvas

Animation objects

• By the time we reach the end of these slides, we will have seen how to implement a set of object types which will make it easy to produce animations in several canvases on one page

• We will have defined a set of object types which includes the following: – Drama

• A Drama has a background, which is painted onto a canvas, and a list of entities called Actors which move into, around and out of the canvas

– Actor• An Actor is an entity which moves into, around and out of a canvas

Page 4: Back to the canvas element Animating within the canvas

A simple example - no animation yet• View this animation in a -moz- or -webkit- browser: http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g1/main.html• In this simple example,

– a seascape is painted as a background onto the canvas– a diver is placed on this background

Page 5: Back to the canvas element Animating within the canvas

The HTML file• An external .js file is used to manipulate the canvas

<!DOCTYPE HTML>

<html>

<head>

<meta charset="utf-8">

<title>Canvas example</title>

<script src="main.js" type="text/Javascript" language="javascript">

</script>

</head>

<body>

<canvas id="myCanvas" width="500px" height="650px"

style="border:solid 1px #ccc;">

Your browser does not support the canvas element

</canvas>

</body>

</html>

Page 6: Back to the canvas element Animating within the canvas

Architecture of the .js file• The canvas and context are global variables

• An event listener is used to fire an initialization function, init(), when the page has been loaded

• The init() function calls one function to draw the seascape background and another to place the diver at (250,150)

var canvas;

var context;

function drawSeascape() { ... }

function drawDiverAt(destX,destY) { ... }

function init()

{ canvas=document.getElementById('myCanvas');

context=canvas.getContext('2d');

drawSeascape();

drawDiverAt(250,150);

}

window.addEventListener('load', init, false);

Page 7: Back to the canvas element Animating within the canvas

The drawSeascape and drawDiverAt functions• Both of these use the standard drawImage() method provided by the

context object

function drawSeascape()

{ var seascape = new Image();

seascape.src = 'seascape.jpg';

context.drawImage(seascape,0,0,canvas.width,canvas.height); }

function drawDiverAt(destX,destY)

{ var diverImg = new Image();

diverImg.src = 'diver.png';

var diverWidth = 100;

var diverHeight = 100;

context.drawImage(diverImg, destX,destY,diverWidth,diverHeight); }

Page 8: Back to the canvas element Animating within the canvas

Reminder: the setTimeout() function

• This takes two arguments:– a piece of JavaScript to be executed– a delay which must elapse before the code is

executed• the delay must be specified in milliseconds

• FormatsetTimeout(" ...some Javascript code ... ",delay);

Page 9: Back to the canvas element Animating within the canvas

A first attempt at animation• View this animation in a -moz- or -webkit- browser: http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g2/main.html• Here,

– a seascape is painted as a background onto the canvas– a is animated down through the canvas

Page 10: Back to the canvas element Animating within the canvas

The HTML file is unchanged

<!DOCTYPE HTML>

<html>

<head>

<meta charset="utf-8">

<title>Canvas example</title>

<script src="main.js" type="text/Javascript" language="javascript">

</script>

</head>

<body>

<canvas id="myCanvas" width="500px" height="650px"

style="border:solid 1px #ccc;">

Your browser does not support the canvas element

</canvas>

</body>

</html>

Page 11: Back to the canvas element Animating within the canvas

Architecture of the .js file• There are several global variables

• An initialization function is fired when the page has been loaded

var framesPerSecond=25;

var canvas;

var context;

var diverX;

var diverY;

var diverYIncrementPerFrame;

function drawSeascape() { ... }

function drawDiverAt(destX,destY) { ... }

function animateDiverDownPastCanvas() { ... }

function moveDiverDown() { ... }

function init() { ... }

window.addEventListener('load', init, false);

Page 12: Back to the canvas element Animating within the canvas

The init() function• This paints the background and places the diver outside the canvas frame

• The number of frames and the Y-increment per frame are computed

• Then animateDiverDownPastCanvas() is called

function init()

{ canvas=document.getElementById('myCanvas');

context=canvas.getContext('2d');

drawSeascape();

diverX=250;

diverY=-150;

drawDiverAt(diverX,diverY);

durationOfAnimationInSeconds=7;

numberOfFrames = durationOfAnimationInSeconds*framesPerSecond;

distanceToTraverse=canvas.height-diverY;

diverYIncrementPerFrame = distanceToTraverse/(numberOfFrames);

animateDiverDownPastCanvas();

}

Page 13: Back to the canvas element Animating within the canvas

The drawSeascape and drawDiverAt functions are unchanged

function drawSeascape()

{ var seascape = new Image();

seascape.src = 'seascape.jpg';

context.drawImage(seascape,0,0,canvas.width,canvas.height); }

function drawDiverAt(destX,destY)

{ var diverImg = new Image();

diverImg.src = 'diver.png';

var diverWidth = 100;

var diverHeight = 100;

context.drawImage(diverImg, destX,destY,diverWidth,diverHeight); }

Page 14: Back to the canvas element Animating within the canvas

The drawSeascape and drawDiverAt functions

• animateDiverDownPastCanvas() computes how often a frame should be drawn

• Then it uses indirect recursion, through moveDiverDown, to call itself at this frequency until the diver has fallen past the bottom of the canvas

• The diver is moved to its various positions by moveDiverDown()

function animateDiverDownPastCanvas()

{ var millisecondsPerFrame=1000/framesPerSecond;

if (diverY<650)

{ setTimeout("moveDiverDown()",millisecondsPerFrame); } }

function moveDiverDown()

{ drawSeascape();

diverY=diverY+diverYIncrementPerFrame;

drawDiverAt(diverX,diverY);

animateDiverDownPastCanvas(); }

Page 15: Back to the canvas element Animating within the canvas

Using objects

• Animations of any real utility will quickly become quite complex

• To control complexity, it is essential to use object-oriented programming

• Before proceeding, we will review some aspects of object definition in Javascript

• On the next few slides, we will consider the simple example of an object to represent the concept of a Box

Page 16: Back to the canvas element Animating within the canvas

The Box() interface definition• Our concept of a Box has 2 attributes (width and height) and 3 methods (for

reporting the width, height and area of the box)

• We decide to have a constructor which has two required parameters, representing the width and height of the new Box to be created

• The IDL definition is as follows:

NamedConstructor=Box(number width, number height)

interface Box {

attribute number width;

attribute number height;

number getWidth();

number getHeight();

number getArea();

};

Page 17: Back to the canvas element Animating within the canvas

Implementing the Box concept in JavaScript, version 1• Strictly speaking, there are no object classes in Javascript, only

individual objects• Functions are used to build objects• In the code below, the concept of a Box is defined and used

function Box(width,height)

{ this.width=width;

this.height=height;

this.getWidth = function () { return this.width; }

this.getHeight = function () { return this.height; }

this.getArea = function () { return this.width*this.height; }

}

...

var box1= new Box(100,200);

alert(box1.getArea()); /*outputs 20000 */

Page 18: Back to the canvas element Animating within the canvas

Implementing the Box concept in JavaScript, version 2• Sometimes an object may have private attributes• The Box object created below has a private attribute, area, which is

not defined through in the IDL interface and is not available outside

function Box(width,height)

{ this.width=width;

this.height=height;

var area = width*height;

this.getWidth = function () { return this.width; }

this.getHeight = function () { return this.height; }

this.getArea = function () { return area; }

}

...

var box1= new Box(100,200);

alert(box1.getArea()); /*outputs 20000 */

Page 19: Back to the canvas element Animating within the canvas

The Boxx() interface definition• Sometimes an object's attributes are all private

• For example, a box has a width, height and area, but they are not available through the IDL interface below

– the constructor method does not take any parameters– there are methods for setting the width and height of a box– there is no way to directly affect the area of a box

NamedConstructor=Boxx()

interface Boxx {

void setWidth(in number width);

void setHeight(in number height);

number getWidth();

number getHeight();

number getArea();

};

Page 20: Back to the canvas element Animating within the canvas

Implementing the Boxx concept in JavaScript, version 1

function Boxx()

{ var width;

var height;

this.setWidth = function (x) { this.width = x; }

this.setHeight = function (x) { this.height = x; }

this.getWidth = function () { return this.width; }

this.getHeight = function () { return this.height; }

this.getArea = function () { return area; }

}

...

var box1= new Boxx();

box1.setWidth(100);

box1.setWidth(200);

alert(box1.getArea()); /*outputs 20000 */

Page 21: Back to the canvas element Animating within the canvas

Implementing the Boxx concept in JavaScript, version 2• An object can also have private methods• Here, for example, the Boxx concept is implemented using a private

method, area(), which is not available outside the objectfunction Boxx()

{ var width;

var height;

function area() { return width*height; }

this.setWidth = function (x) { this.width = x; }

this.setHeight = function (x) { this.height = x; }

this.getWidth = function () { return this.width; }

this.getHeight = function () { return this.height; }

this.getArea = function () { return area(); }

}...

var box1= new Boxx();

box1.setWidth(100);

box1.setWidth(200);

alert(box1.getArea()); /*outputs 20000 */

Page 22: Back to the canvas element Animating within the canvas

Using objects to introduce multiple divers• View this animation in a -moz- or -webkit- browser: http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g3/main.html• Here,

– a seascape is painted as a background onto the canvas– several divers, of different appearances and sizes, appear, in different

places, on the canvas• As we shall see, objects are used to represent the divers

Page 23: Back to the canvas element Animating within the canvas

The HTML file is unchanged

<!DOCTYPE HTML>

<html>

<head>

<meta charset="utf-8">

<title>Canvas example</title>

<script src="main.js" type="text/Javascript" language="javascript">

</script>

</head>

<body>

<canvas id="myCanvas" width="500px" height="650px"

style="border:solid 1px #ccc;">

Your browser does not support the canvas element

</canvas>

</body>

</html>

Page 24: Back to the canvas element Animating within the canvas

Architecture of the .js file• Constructor functions are introduced for two types of object:

– a Background object is painted on the canvas, as a backdrop for the actors– an Actor is an entity which can be drawn on top of the backdrop

• An initialization function is called when the page is loaded

function Actor(imageSrc,width,height,x,y) { ... }

function Background(imageSrc) { ... }

function init() { ... }

window.addEventListener('load', init, false);

Page 25: Back to the canvas element Animating within the canvas

The Background() interface definition• A Background has 1 public attribute and 1 public method

• The constructor has one required parameter

NamedConstructor=Background(DOMString imageSrc)

interface Background { attribute DOMString imageSrc;

void draw();

};

Page 26: Back to the canvas element Animating within the canvas

The Actor() interface definition• An Actor has 5 public attributes and 2 public methods

• The constructor has one required parameter and four optional parameters

NamedConstructor=Actor(DOMString imageSrc,

[number width, number height, number x, number y] )

interface Actor { attribute DOMString imageSrc;

attribute number width;

attribute number height;

attribute number x;

attribute number y;

void draw();

void translate(in number x, in number y); };

Page 27: Back to the canvas element Animating within the canvas

Using these objects in the init() function• Three actors are created: the first is drawn three times in different places,

the second is drawn twice and the third is drawn once

• Notice that the Actor() constructor is used with differing numbers of optional parameters

function init()

{ var background= new Background('seascape.jpg');

background.draw();

var diver1=new Actor("diver1.png");

diver1.x=250; diver1.y=400; diver1. draw() ;

diver1.translate(50,0); diver1. draw() ;

diver1.translate(25,0); diver1. draw() ;

var diver2=new Actor("diver2.png",50,50);

diver2.x=50; diver2.y=600; diver2. draw() ;

var diver3=new Actor("diver3.png",50,50,400,300);

diver3. draw() ;

diver3.translate(50,0); diver3. draw() ; }

Page 28: Back to the canvas element Animating within the canvas

The Background() implementation• Notice that a private attribute, called image, is used in the implementation of

the object

• The constructor method also checks whether or not the required parameter was provided and, if not, tries to degrade gracefully

function Background(imageSrc)

{ if (imageSrc)

{ this.imageSrc=imageSrc; }

else { this.imageSrc="";

alert('Warning: no image source defined'); }

var image = new Image();

this.draw = function ()

{ var canvas=document.getElementById('myCanvas');

var context=canvas.getContext('2d');

image.src=this.imageSrc;

context.drawImage(image,0,0,canvas.width,canvas.height); }

}

Page 29: Back to the canvas element Animating within the canvas

The Actor() implementation• A private attribute, called image, is used in the implementation of the object

function Actor(imageSrc,width,height,x,y)

{ if (width) { this.width=width; } else { this.width=100; }

if (height) { this.height=height; } else { this.height=100; }

if (x<0 || x>0) { this.x=x; } else { this.x=0; }

if (y<0 || y>0) { this.y=y; } else { this.y=0; }

if (imageSrc) { this.imageSrc=imageSrc; } else { this.imageSrc=""; }

var image = new Image();

this.draw = function ()

{ var canvas=document.getElementById('myCanvas');

var context=canvas.getContext('2d');

image.src=this.imageSrc;

context.drawImage(image,this.x,this.y,this.width,this.height);

}

this.translate = function (x,y)

{ this.x=this.x+x; this.y=this.y+y; }

}

Page 30: Back to the canvas element Animating within the canvas

Animating an Actor• View this animation in a -moz- or -webkit- browser: http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g5/main.html• An (extended version of the) Actor object is used to represent a diver• The diver is animated across the seascape• Buttons are provided for pausing and resuming the animation

Page 31: Back to the canvas element Animating within the canvas

Only change to HTML file is addition of buttons<!DOCTYPE HTML>

<html>

<head>

<meta charset="utf-8">

<title>Canvas example</title>

<script src="main.js" type="text/Javascript" language="javascript">

</script>

</head>

<body>

<canvas id="myCanvas" width="500px" height="650px"

style="border:solid 1px #ccc;">

Your browser does not support the canvas element

</canvas>

<button type="button" id="button1">Pause</button>

<button type="button" id="button2">Resume</button></body>

</html>

Page 32: Back to the canvas element Animating within the canvas

Architecture of the .js file• Two global variables, framesPerSecond and milliSecondsPerFrame, are

used to define key aspects of the animation– if all browsers supported CONST, framesPerSecond should be a constant

• The background and diver are global objects

• Two functions, startAnimation() and continueAnimation(), are provided for overall animation control

• Two functions , pause() and resume(), are provided to respond to buttons

var framesPerSecond = 25;

var millisecondsPerFrame = 1000/framesPerSecond;

var background;

var diver1;

function Actor(imageSrc,width,height,x,y) { ... }

function Background(imageSrc) { ... }

function startAnimation() { ... }

function continueAnimation() { ... }

function pause() { ... }

function resume() { ... }

function init() { ... }

window.addEventListener('load', init, false);

Page 33: Back to the canvas element Animating within the canvas

The Background() concept is unchanged

• The Background() interface is unchangedNamedConstructor=Background(DOMString imageSrc)

interface Background { attribute DOMString imageSrc;

void draw();

};

• So its implementation is unchangedfunction Background(imageSrc)

{ if (imageSrc)

{ this.imageSrc=imageSrc; }

else { this.imageSrc=""; alert('Warning: no image source defined'); }

var image = new Image()

this.draw = function ()

{ var canvas=document.getElementById('myCanvas');

var context=canvas.getContext('2d');

image.src=this.imageSrc;

context.drawImage(image,0,0,canvas.width,canvas.height); } }

Page 34: Back to the canvas element Animating within the canvas

Improving the concept of an Actor• Now that we have recognized the concept of an Actor, it would make sense to

give it some of the features we found in CSS animations, such as

* the duration of the Actor's animation

* the Actor's play state (to pause/resume its movement)

* a sequence of keyframes to specify transformations• To specify the duration of the animation, we will need a public method like this:

void setAnimationDuration(in number duration);

• To specify the play state, we will need a public method like this:

void setAnimationPlayState(in DOMString playstate);

• For now, we will not consider the full concept of a sequence of keyframes

• Instead, we will consider the notion of a straight-line trajectory, along which the Actor moves during its animation

• To use the trajectory, we could provide three methods like this:

void setAnimationTrajectory(in number x1, in number y1,

in number x2, in number y2);

void goToStartOfTrajectory();

void moveToNextPointOnTrajectory();

Page 35: Back to the canvas element Animating within the canvas

Improving the concept of an Actor (contd.)• Whenever we have methods for setting attributes of an object, it usually makes

sense to have corresponding methods for accessing the values of these attributes

• We have identified the need for

void setAnimationDuration(in number duration);

void setAnimationPlayState(in DOMString playstate);

void setAnimationTrajectory(in number x1, in number y1,

in number x2, in number y2);

• So, it might seem that we also need these three methods

number getAnimationDuration();

DOMString getAnimationPlayState();

trajectory getAnimationTrajectory();

• In fact, however, we will find that we need just this one

DOMString getAnimationPlayState();

Page 36: Back to the canvas element Animating within the canvas

The new Actor() interface definition• An Actor has the same set of public attributes as before and the constructor is

the same as before

• However there are now 6 different public methods

NamedConstructor=Actor(DOMString imageSrc,

[number width, number height, number x, number y] )

interface Actor { attribute DOMString imageSrc;

attribute number width;

attribute number height;

attribute number x;

attribute number y;

void setAnimationTrajectory(in number x1, in number y1,

in number x2, in number y2);

void goToStartOfTrajectory();

void moveToNextPointOnTrajectory();

void setAnimationDuration(in number duration);

void setAnimationPlayState(in DOMString playstate);

DOMString getAnimationPlayState(); };

Page 37: Back to the canvas element Animating within the canvas

Before we ...

• Before we consider how to implement the new Actor concept, let's see it in use

Page 38: Back to the canvas element Animating within the canvas

Using the Actor concept in the init() function• We set event listeners on the pause/resume buttons

• We create the backdrop although we do not yet draw it

• We construct an actor and set the trajectory and duration of its animation

• Then, we start the animation

function init()

{ button1=document.getElementById('button1');

button1.addEventListener('click', pause, false);

button2=document.getElementById('button2');

button2.addEventListener('click', resume, false);

background=new Background('seascape.jpg');

diver1=new Actor("diver1.png");

diver1.setAnimationTrajectory(100,150,400,500);

diver1.setAnimationDuration(7);

startAnimation();

}

Page 39: Back to the canvas element Animating within the canvas

Starting the animation• To animate the Actor, we

– draw the previously-created background

– place it at the start of its trajectory

– set its animation play state to 'running'

– and set a Timeout to continue the animation after the elapse of the appropriate number of milliseconds for one frame

function startAnimation()

{

background.draw();

diver1.goToStartOfTrajectory();

diver1.setAnimationPlayState('running');

setTimeout("continueAnimation()",millisecondsPerFrame);

}

Page 40: Back to the canvas element Animating within the canvas

Continuing the animation• When generating each subsequent frame of the animation, we must

– first redraw the backdrop before we – place the Actor at the next point in its trajectory

• It is possible that the next point is the end of the trajectory and, if so, the animation play state will be set to ‘finished’ when Actor is moved

• So, – we check whether the animation play state is still ‘running’ before– we set a Timeout to draw yet another frame

function continueAnimation() { background.draw(); diver1.moveToNextPointOnTrajectory(); if ( diver1.getAnimationPlayState() == 'running' ) { setTimeout("continueAnimation()",millisecondsPerFrame); } }

Page 41: Back to the canvas element Animating within the canvas

The pause() and resume() functionsfunction pause() { diver1.setAnimationPlayState('paused'); }• When the pause button is clicked, the pause() function sets the

Actor’s play state to ‘paused’• Thus, next time continueAnimation() is executed, it will not

issue a further timeout-delayed call to itself, so the animation will halt

function resume()

{ diver1.setAnimationPlayState('running'); continueAnimation(); }• When the resume button is clicked, the resume() function

• sets the Actor’s play state to ‘running’ • and restarts the animation by calling continueAnimation()

Page 42: Back to the canvas element Animating within the canvas

Before we look at implementing new Actor concept• Before we look at implementing the new Actor concept, ...• we need to have another look at

* the meaning of the keyword this in Javascript and ...

* how it is used when implementing objects

Page 43: Back to the canvas element Animating within the canvas

The keyword this in Javascript, part 1• Consider the output from this simple HTML page http://www.cs.ucc.ie/j.bowen/cs4506/slides/thisExamples/ex1.html<html>

<head><title>Keyword *this* example</title>

<script>

var product = function() { return this.width * this.height; }

</script>

</head>

<body>

<script>

box1 = new Object(); box1.width=10; box1.height=10;

box1.area=product;

alert(box1.width); alert(box1.area);

</script>

</body>

</html>

• Notice that box1 gets its own copy of the function definition

Page 44: Back to the canvas element Animating within the canvas

The keyword this in Javascript, part 2• var someName = function(...) { ... } is usually written in shorthand form as

function someName(...) { ... }

• See output from http://www.cs.ucc.ie/j.bowen/cs4506/slides/thisExamples/ex2.html

<html>

<head><title>Keyword *this* example</title>

<script>

function product() { return this.width * this.height; }

</script>

</head>

<body>

<script>

box1 = new Object(); box1.width=10; box1.height=10;

box1.area=product;

alert(box1.width); alert(box1.area);

</script>

</body>

</html>

Page 45: Back to the canvas element Animating within the canvas

The keyword this in Javascript, part 3• See http://www.cs.ucc.ie/j.bowen/cs4506/slides/thisExamples/ex3.html

• Notice when each copy of the function is executed, this refers to the object which owns the function

<html><head><title>Keyword *this* example</title><script>

function product() { return this.width * this.height; }

</script>

</head><body><script>

box1 = new Object(); box1.width=10; box1.height=10;

box1.area=product;

alert(box1.width); alert(box1.area());

box2 = new Object(); box2.width=20; box2.height=10;

box2.area=product;

alert(box2.width); alert(box2.area());

</script>

</body>

</html>

Page 46: Back to the canvas element Animating within the canvas

The keyword this in Javascript, part 4• See http://www.cs.ucc.ie/j.bowen/cs4506/slides/thisExamples/ex4.html

• Here, too, when the function is executed, this refers to the object which owns the function, in this case the global top-level object, the window object

<html>

<head><title>Keyword *this* example</title>

<script>

function f1() { return this.location; }

</script>

</head>

<body>

<script> alert( f1() ); </script>

</body>

</html>

Page 47: Back to the canvas element Animating within the canvas

The keyword this in Javascript, part 5• We have just seen that, when a function is executed, the keyword

this is interpreted to mean the object which owns the function• But, when we wrote constructor functions, we used the keyword

this with a different meaning• Consider, for example, this constructor, which takes four

parameters and returns an object with four public attributes, representing a rectangular plate made of a material of some density function Plate(w,h,t,d)

{ this.width=w;

this.height=h;

this.thickness=t;

this.density=d; }

• Here, we use this to refer to the object that will be built by the constructor

• Let's contrast these two meanings of this in one simple program

Page 48: Back to the canvas element Animating within the canvas

The keyword this in Javascript, part 5• See http://www.cs.ucc.ie/j.bowen/cs4506/slides/thisExamples/ex5.html

<html><head><title>Keyword *this* example</title>

<script>

function f1() { return this.location; }

function Plate(w,h,t,d)

{ this.width=w; this.height=h; this.thickness=t; this.density=d; }

</script></head><body><script>

alert(f1());

plate1= new Plate(10,20,5,2);

alert(plate1.width);

</script></body></html>

• When f1 is executed, this refers to the window

• When Plate is executed, this refers to the object that is being built

• The second interpretation of this is a consequence of the use of the keyword new

Page 49: Back to the canvas element Animating within the canvas

The keyword this in Javascript, part 6• We have just seen that this has a different meaning when

constructor functions are executed (under control of the keyword new) than it has when ordinary functions are executed

• Okay, we can accept this distinction• However, there is a complication

as shall see, a problem arises when we define private methods

Page 50: Back to the canvas element Animating within the canvas

The keyword this in Javascript, part 7• To see the intricacy involved in using this in constructor functions,

suppose we wish to extend the concept of a Plate that we have just defined,– to include public methods for accessing the volume and weight

of a plate• One approach is on the next slide

Page 51: Back to the canvas element Animating within the canvas

The keyword this in Javascript, part 8• We provide access to the volume and weight of the Plate through two

public methods, getVolume() and getWeight() function Plate(w,h,t,d)

{ this.width=w;

this.height=h;

this.thickness=t;

this.density=d;

this.getVolume= function() { return this.thickness * this.getArea(); }

this.getWeight function() { return this.density * this.thickness * this.getArea(); }

this.getArea = function () { return this.width * this.height; }

• The two methods are defined in terms of another public method, called getArea()

• There is no problem with using this in the way we use it here• Suppose, however, we wish to keep getArea() private• On the next slide, we will look at what might seem like an obvious

approach

Page 52: Back to the canvas element Animating within the canvas

The keyword this in Javascript, part 9• We replace the public method getArea() by a private one, area()• We redefine getVolume() and getWeight() in terms of area()function Plate(w,h,t,d)

{ this.width=w;

this.height=h;

this.thickness=t;

this.density=d;

this.getVolume= function() { return this.thickness * area(); }

this.getWeight function() { return this.density * this.thickness * area(); }

function area = function () { return this.width * this.height; } }

• This may seem reasonable but

it will not work• The reason is that, within the definition of a private method, the keyword

this does not have the meaning we expect• If we want to refer to an object within one of its private methods, we need

to use a different approach• First, we need to consider this more carefully

Page 53: Back to the canvas element Animating within the canvas

The keyword this in Javascript, part 10• Consider this page

http://www.cs.ucc.ie/j.bowen/cs4506/slides/thisExamples/ex6.html

• It simplifies things by ignoring the Plate's weight<html><head><title>Keyword *this* example</title><script>

function Plate(w,h,t)

{ this.width=w; this.height=h; this.thickness=t;

this.getVolume= function() { return this.thickness * area(); }

function area() { return this.width * this.height; } }

</script></head><body>

<script>

plate1= new Plate(10,20,5);

alert( plate1.getVolume() );

</script></body></html>

• The program cannot calculate the area of the plate • This is because, when area() is executed, this is interpreted as

referring to the global top-level object, the window (which does not have attributes called width and height)

• We demonstrate this on the next slide

Page 54: Back to the canvas element Animating within the canvas

The keyword this in Javascript, part 11• Consider the output from this page

http://www.cs.ucc.ie/j.bowen/cs4506/slides/thisExamples/ex7.html

<html><head><title>Keyword *this* example</title><script>

function Plate(w,h,t)

{ this.width=w; this.height=h; this.thickness=t;

this.getVolume= function() { return this.thickness+'*'+area(); }

function area() { return this.location; } }

</script></head><body>

<script>

plate1= new Plate(10,20,5);

alert( plate1.getVolume() );

</script></body></html>

• From the output, we can see that the keyword this is interpreted as referring to the window object

Page 55: Back to the canvas element Animating within the canvas

The keyword this in Javascript, part 12• What's happening?• In ECMAScript Edition 3, the keyword this has a "feature" which

most authors call a bug:When a function is defined inside another function, the keyword this is

interpreted as referring, not to the object which owns the outer function, but to the global top-level object

• Most authors seem to state that this aspect of the language is to be changed in a future version of the language

• Whether this is the case or not requires a close reading of the latest specification for the ECMAScript language (Version 5.1, June 2011)

http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf

(Local copy is here:

http://www.cs.ucc.ie/j.bowen/cs4506/slides/ecmaScript5.1.pdf )

• Luckily, there is a way we can overcome this bug

Page 56: Back to the canvas element Animating within the canvas

The keyword this in Javascript, part 13• We overcome the bug in this page

http://www.cs.ucc.ie/j.bowen/cs4506/slides/thisExamples/ex8.html

by avoiding use of this inside the private method (the inner function)<html><head><title>Keyword *this* example</title><script>

function Plate(w,h,t)

{ this.width=w; this.height=h; this.thickness=t;

var self = this;

this.getVolume= function() { return this.thickness * area(); }

function area() { return self.width * self.height; } }

</script></head><body><script>

plate1= new Plate(10,20,5);

alert( plate1.getVolume() );

</script></body></html>

• We capture our intended semantics by assigning a local variable in the outer function to the owner of the outer function

• This variable is within the scope of the inner function so, using it, we can access the intended object

• Now, at last, we can look at implementing the Actor concept

Page 57: Back to the canvas element Animating within the canvas

Reminder: the new Actor() interface definition

• Here is the interface for the we specified for the new Actor concept• There five public attributes (which can be set by the constructor) and

six public methodsNamedConstructor=Actor(DOMString imageSrc,

[number width, number height, number x, number y] )

interface Actor { attribute DOMString imageSrc;

attribute number width;

attribute number height;

attribute number x;

attribute number y;

void setAnimationTrajectory(in number x1, in number y1,

in number x2, in number y2);

void goToStartOfTrajectory();

void moveToNextPointOnTrajectory();

void setAnimationDuration(in number duration);

void setAnimationPlayState(in DOMString playstate);

DOMString getAnimationPlayState(); };

Page 58: Back to the canvas element Animating within the canvas

Implementing the new Actor concept, part 1• A top-level view of implementing these five public attributes and six

public methodsfunction Actor(imageSrc,width,height,x,y)

{ if (imageSrc) { this.imageSrc=imageSrc; } else { this.imageSrc=""; }

if (width) { this.width=width; } else { this.width=100; }

if (height) { this.height=height; } else { this.height=100; }

if (x) { this.x=x; } else { this.x=0; }

if (y) { this.y=y; } else { this.y=0; }

var x1, y1, x2, y2, animationDuration, animationPlayState;

this.setAnimationTrajectory = function (x1,y1,x2,y2) {... }

this.goToStartOfTrajectory = function () {... }

this.moveToNextPointOnTrajectory = function () { ... }

this.setAnimationDuration = function (duration) {... }

this.setAnimationPlayState = function (playState) {... }

this.getAnimationPlayState = function () {... } }

• Notice the use of local variables to represent the private attributes

Page 59: Back to the canvas element Animating within the canvas

Implementing the new Actor concept, part 2• We have methods to animate an Actor along a trajectory• But a programmer may wish to see an actor before animating it• So, if the programmer specifies a position for an Actor when using

the constructor, we should probably draw the Actor at the positionfunction Actor(imageSrc,width,height,x,y)

{ if (imageSrc) { this.imageSrc=imageSrc; } else { this.imageSrc=""; }

if (width) { this.width=width; } else { this.width=100; }

if (height) { this.height=height; } else { this.height=100; }

if (x) { this.x=x; } else { this.x=0; } if (y) { this.y=y; } else { this.y=0; }

if ((typeof x=='number') && (typeof y=='number')) { draw(); }

var x1, y1, x2, y2, animationDuration, animationPlayState;

this.setAnimationTrajectory = function (x1,y1,x2,y2) {... }

this.goToStartOfTrajectory = function () {... }

this.moveToNextPointOnTrajectory = function () { ... }

this.setAnimationDuration = function (duration) {... }

this.setAnimationPlayState = function (playState) {... }

this.getAnimationPlayState = function () {... }

function draw() { ... } }

• So, already, we see the need for a private method

Page 60: Back to the canvas element Animating within the canvas

Implementing the new Actor concept, part 3• The draw() method will need to access the width and height of the

Actor, its current position and the name of the file for the image • Therefore, we need a private attribute through which the method

can access its parent object; let's call it selffunction Actor(imageSrc,width,height,x,y)

{ if (imageSrc) { this.imageSrc=imageSrc; } else { this.imageSrc=""; }

if (width) { this.width=width; } else { this.width=100; }

if (height) { this.height=height; } else { this.height=100; }

if (x) { this.x=x; } else { this.x=0; } if (y) { this.y=y; } else { this.y=0; }

if ((typeof x=='number') && (typeof y=='number')) { draw(); }

var x1, y1, x2, y2, animationDuration, animationPlayState;

this.setAnimationTrajectory = function (x1,y1,x2,y2) {... }

this.goToStartOfTrajectory = function () {... }

this.moveToNextPointOnTrajectory = function () { ... }

this.setAnimationDuration = function (duration) {... }

this.setAnimationPlayState = function (playState) {... }

this.getAnimationPlayState = function () {... }

var self = this;

function draw() { ... } }

Page 61: Back to the canvas element Animating within the canvas

Implementing the new Actor concept, part 4• The private method draw() can be implemented as follows

function draw()

{

var canvas=document.getElementById('myCanvas');

var context=canvas.getContext('2d');

var image = new Image();

image.src=self.imageSrc;

context.drawImage(image, self.x, self.y, self.width, self.height);

}

• It uses local variables to represent the canvas, the context and the image file

• It uses the private attribute, self, of the Actor object to access the information about the actor which is needed by the standard drawImage() method that is provided by the context

Page 62: Back to the canvas element Animating within the canvas

Implementing the new Actor concept, part 5• The public method setAnimationTrajectory() is very easy to

implement• It takes four parameters representing the coordinates of the end

points of the trajectory and sets the appropriate private variables equal to these

this.setAnimationTrajectory =

function (someX1,someY1,someX2,someY2)

{ x1 = someX1;

y1 = someY1;

x2 = someX2;

y2 = someY2;

}

Page 63: Back to the canvas element Animating within the canvas

Implementing the new Actor concept, part 6• The public method goToStartOfTrajectory() could be implemented

very simply as follows this.goToStartOfTrajectory = function () { this.x=x1;

this.y=y1;

draw(); }

• However, since we will also have another method, moveToNextPointOnTrajectory(), for placing an Actor at some point along its trajectory, it would make sense to have a private method for placing the Actor at any arbitrary point

• This private method can be implemented as

function placeAt(x,y) { self.x=x; self.y=y; draw(); }

• Then we can use this private method to implement goToStartOfTrajectory() as follows

this.goToStartOfTrajectory = function () { placeAt(x1,y1); }

Page 64: Back to the canvas element Animating within the canvas

Implementing the new Actor concept, part 7• The public method moveToNextPointOnTrajectory() can be

implemented very simply as follows

this.moveToNextPointOnTrajectory =

function () { placeAt(this.x+xIncrementPerFrame(),

this.y+yIncrementPerFrame());

if (reachedEndOfTrajectory())

{ this.setAnimationPlayState('finished'); }

}

• We add the appropriate increment to the x and y coordinates of the Actor and then use placeAt() to deposit the Actor in the new location

• Then we check to see if the Actor has reached the end of its trajectory and, if so, we set its play state to 'finished'

• This method definition requires three private methods xIncrementPerFrame() , xIncrementPerFrame() and reachedEndOfTrajectory() which are defined on the next two slides

Page 65: Back to the canvas element Animating within the canvas

Implementing the new Actor concept, part 8• The private methods xIncrementPerFrame() and

yIncrementPerFrame() can be implemented as follows

function xIncrementPerFrame()

{ if (x1 && x2 && animationDuration)

{ return (x2-x1) / (animationDuration*framesPerSecond); }

else { return 'undefined'; } }

function yIncrementPerFrame()

{ if (y1 && y2 && animationDuration)

{ return (y2-y1) / (animationDuration*framesPerSecond); }

else { return 'undefined'; } }

• For any dimension, the correct increment per frame is simply the result of dividing the total difference between the start and end of the trajectory by the number of frame transitions in the animation

Page 66: Back to the canvas element Animating within the canvas

Implementing the new Actor concept, part 9• The private method reachedEndOfTrajectory() can be implemented as

followsfunction reachedEndOfTrajectory()

{ if (self.x && self.y && x1 && y1 && x2 && y2 )

{ var xIncreasing = (x2 >=x1);

var yIncreasing = (y2 >=y1);

if ( ( (xIncreasing && (self.x >= x2) ) ||

(!xIncreasing && (self.x <= x2) ) ) &&

( (yIncreasing && (self.y >= y2) ) ||

(!yIncreasing && (self.y <= y2) ) ) )

{ return true; } else { return false; }

}

else { return undefined; }

}

• For each dimension, we check whether it increases/decreases along the trajectory and then make the appropriate comparison between the current value of the dimension and the value at the end of the trajectory

Page 67: Back to the canvas element Animating within the canvas

Implementing the new Actor concept, part 10• All that remains now is to implement the public methods

setAnimationDuration(), setAnimationPlayState() and getAnimationPlayState()

• These can be implemented very simply, as follows

this.setAnimationDuration = function (duration)

{animationDuration = duration; }

this.setAnimationPlayState = function (playState)

{animationPlayState = playState; }

this.getAnimationPlayState = function ()

{return animationPlayState; }

• The new Actor concept has been fully implemented and the program is complete

Page 68: Back to the canvas element Animating within the canvas

Animating several Actors• Since we have an object-based Actor, we can animate several of them• View this animation in a -moz- or -webkit- browser: http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g6/main.html• Three divers are animated and buttons are provided to pause/resume

animation of the black diver• As we shall see, however, animation of several Actors requires one

extra method for the Actor object

Page 69: Back to the canvas element Animating within the canvas

Only change to HTML file is change to button labels<!DOCTYPE HTML>

<html>

<head>

<meta charset="utf-8">

<title>Canvas example</title>

<script src="main.js" type="text/Javascript" language="javascript">

</script>

</head>

<body>

<canvas id="myCanvas" width="500px" height="650px"

style="border:solid 1px #ccc;">

Your browser does not support the canvas element

</canvas>

<button type="button" id="button1">Pause the black diver</button>

<button type="button" id="button2">Resume the black diver</button>

</body>

</html>

Page 70: Back to the canvas element Animating within the canvas

Architecture of the .js file• There is only one change to the architecture of the .js file:

• The global variable representing the single diver is replaced by a global variable which will represent a list of all the divers that are created

var framesPerSecond = 25;

var millisecondsPerFrame = 1000/framesPerSecond;

var background;

var actorList;

function Actor(imageSrc,width,height,x,y) { ... }

function Background(imageSrc) { ... }

function startAnimation() { ... }

function continueAnimation() { ... }

function pause() { ... }

function resume() { ... }

function init() { ... }

window.addEventListener('load', init, false);

Page 71: Back to the canvas element Animating within the canvas

The Background() concept is unchanged

• The Background() interface is unchangedNamedConstructor=Background(DOMString imageSrc)

interface Background { attribute DOMString imageSrc;

void draw();

};

• So its implementation is unchangedfunction Background(imageSrc)

{ if (imageSrc)

{ this.imageSrc=imageSrc; }

else { this.imageSrc=""; alert('Warning: no image source defined'); }

var image = new Image()

this.draw = function ()

{ var canvas=document.getElementById('myCanvas');

var context=canvas.getContext('2d');

image.src=this.imageSrc;

context.drawImage(image,0,0,canvas.width,canvas.height); } }

Page 72: Back to the canvas element Animating within the canvas

Improving the concept of an Actor• The need for some small change to the Actor concept will become apparent

when we flesh out the rest of the program, so

• let's do that first

• and revisit the Actor concept later

Page 73: Back to the canvas element Animating within the canvas

The init() function• Only change is that we construct several actors and place them into a list before

starting the animationfunction init()

{button1=document.getElementById('button1');

button1.addEventListener('click', pause, false);

button2=document.getElementById('button2');

button2.addEventListener('click', resume, false);

background=new Background('seascape.jpg');

var diver1=new Actor("diver1.png");

diver1.setAnimationTrajectory(100,150,400,500); diver1.setAnimationDuration(7);

var diver2=new Actor("diver2.png");

diver2.setAnimationTrajectory(400,150,100,500); diver2.setAnimationDuration(14);

var diver3=new Actor("diver3.png");

diver3.setAnimationTrajectory(200,150,200,600); diver3.setAnimationDuration(10);

actorList=new Array();

actorList.push(diver1); actorList.push(diver2); actorList.push(diver3);

startAnimation(); }

Page 74: Back to the canvas element Animating within the canvas

The pause() and resume() functions• The only change to these functions is to take account of the fact

that, whereas before diver1 was a global variable, we can now access the first diver only through the global actorList array

function pause() { actorList[0].setAnimationPlayState('paused'); }

function resume()

{actorList[0].setAnimationPlayState('running'); continueAnimation(); }

Page 75: Back to the canvas element Animating within the canvas

Starting the animation• The only change from before is that,

– instead of moving just diver1 to the start of its trajectory and setting its animation play state to 'running'

– we must do this for all of the Actors in the actorList

function startAnimation()

{

background.draw();

for (i=0;i<actorList.length;i++)

{ actor=actorList[i];

actor.goToStartOfTrajectory();

actor.setAnimationPlayState('running'); }

setTimeout("continueAnimation()",millisecondsPerFrame);

}

Page 76: Back to the canvas element Animating within the canvas

Continuing the animation• As before, we redraw the background• Then, for each each actor, we check to see if its animation is still running

– If so, we move it to the next point on its trajectory– If not, we redraw it at its current (x,y) position

• Finally, if at least one actor’s animation is still running, we set a timeout to continue the animation

function continueAnimation() { background.draw();

var somePlayStateStillRunning = false;

for (i=0;i<actorList.length;i++)

{ actor=actorList[i];

if ( actor.getAnimationPlayState()=='running' )

{somePlayStateStillRunning=true;

actor.maybeMoveToNextPointOnTrajectory(); }

else { actor.placeAt(actor.x,actor.y); } }

if (somePlayStateStillRunning)

{ setTimeout("continueAnimation()", millisecondsPerFrame); } }

Page 77: Back to the canvas element Animating within the canvas

The Actor: new methods

• Studying the code that we have just seen, we see that

* we need one extra method

void placeAt(in number x, in number y)

* we need to replace

void moveToNextPointOnTrajectory();

by

void maybeMoveToNextPointOnTrajectory();

Page 78: Back to the canvas element Animating within the canvas

The new Actor() interface definition

• In the new interface, there are five public attributes and seven public methods

NamedConstructor=Actor(DOMString imageSrc, [number width, number height, number x, number y] )

interface Actor { attribute DOMString imageSrc; attribute number width; attribute number height; attribute number x; attribute number y; void setAnimationTrajectory(in number x1, in number y1, in number x2, in number y2); void placeAt(in number x, in number y) void goToStartOfTrajectory(); void maybeMoveToNextPointOnTrajectory(); void setAnimationDuration(in number duration); void setAnimationPlayState(in DOMString playstate); DOMString getAnimationPlayState(); };

Page 79: Back to the canvas element Animating within the canvas

Top level view of Actor implementation• We still need the private members we saw before• So, here is a top-level view of the implementation:function Actor(imageSrc,width,height,x,y)

{ if (imageSrc) { this.imageSrc=imageSrc; } else { this.imageSrc=""; }

if (width) { this.width=width; } else { this.width=100; }

if (height) { this.height=height; } else { this.height=100; }

if (x) { this.x=x; } else { this.x=0; }

if (y) { this.y=y; } else { this.y=0; }

if ((typeof x=='number') && (typeof y=='number')) { draw(); }

var x1, y1, x2, y2, animationDuration, animationPlayState;

this.setAnimationTrajectory = function (x1,y1,x2,y2) {... }

this.placeAt = function (in number x, in number y) {… }

this.goToStartOfTrajectory = function () {... }

this.maybeMoveToNextPointOnTrajectory = function () { ... }

this.setAnimationDuration = function (duration) {... }

this.setAnimationPlayState = function (playState) {... }

this.getAnimationPlayState = function () {... }

var self = this;

function draw() { ... } }

Page 80: Back to the canvas element Animating within the canvas

The placeAt() method• placeAt() used to be a private method, but now it is public, so its

implementation must be changed, to this:

this.placeAt = function(x,y) { self.x=x; self.y=y; draw(); }

• goToStartOfTrajectory() used to call the private version of placeAt(), so now it must be changed to call the public version: this.goToStartOfTrajectory = function () { this.placeAt(x1,y1); }

Page 81: Back to the canvas element Animating within the canvas

The moveToNextPointOnTrajectory() method• If the Actor’s animation is still running,

• we do move to the next point on its trajectory• but, otherwise, we redraw the Actor at its current (x,y) position

this.maybeMoveToNextPointOnTrajectory = function () { if (animationPlayState == 'running') { moveToNextPointOnTrajectory(); } else { this.placeAt(this.x,this.y); } }

• So, the moveToNextPointOnTrajectory() method is still needed, but as a private method

• So we rewrite it as such, as follows:

function moveToNextPointOnTrajectory() { self.placeAt(self.x+xIncrementPerFrame(), self.y+yIncrementPerFrame()); if (reachedEndOfTrajectory()) { self.setAnimationPlayState('finished'); } }

Page 82: Back to the canvas element Animating within the canvas

Other methods are unchanged

• The implementations of the other methods are unchanged and will not be considered any further

Page 83: Back to the canvas element Animating within the canvas

An animation library

Page 84: Back to the canvas element Animating within the canvas

An animation library• The motivating example we saw earlier was here: http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g16/main.html• It uses a library of objects which is located here:

http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g16/dramatics.js• The interfaces for the objects in this library are described here: http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g16/dramaticsInterface.txt

• We will examine the features provided by this library and consider how they are implemented

• Henceforth, we will refer to the library as the g16dramatics library

Page 85: Back to the canvas element Animating within the canvas

Using the library• Consider this example, which uses the g16dramatics library: http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g17/main.html• The library provides two kinds of object:

– Drama• A Drama has a background, which is painted onto a canvas, and a list of entities

called Actors which move into, around and out of the canvas– Actor

• An Actor is an entity which moves into, around and out of a canvas• This example application uses two instances of a Drama, each of which

involves one instance of an actor

Page 86: Back to the canvas element Animating within the canvas

The HTML file• The HTML file contains two canvases. It calls the g16dramatics

library and its own private Javascript file, main.js

<!DOCTYPE html>

<html><head><meta charset="utf-8">

<title>Canvas example</title>

<script src="../g16/dramatics.js" type="text/javascript"></script>

<script src="main.js" type="text/javascript"></script>

</head>

<body>

<canvas id="canvas1" width="500px" height="650px">

Your browser does not support the canvas element

</canvas>

<canvas id="canvas2" width="500px" height="650px">

Your browser does not support the canvas element

</canvas>

</body></html>

Page 87: Back to the canvas element Animating within the canvas

The main.js file• The init() function creates two Dramas, gives each one an Actor with

specific characteristics and then starts both performing both• We have already seen the four Actor methods that are usedfunction init()

{ var canvas1=document.getElementById('canvas1');

var drama1 = new Drama (canvas1,'seascape.jpg');

var diver1=new Actor("diver1.png","a black diver");

diver1.setAnimationTrajectory(100,150,400,450);

diver1.setAnimationIterationCount(3); diver1.setAnimationDuration(27);

drama1.addActor(diver1);

var canvas2=document.getElementById('canvas2');

var drama2 = new Drama(canvas2,'seascape.jpg');

var diver2=new Actor("diver2.png","a red diver");

diver2.setAnimationTrajectory(400,150,100,450);

diver2.setAnimationIterationCount(4); diver2.setAnimationDuration(36);

diver2.setAnimationDirection("alternate"); drama2.addActor(diver2);

drama1.startPerformance();

drama2.startPerformance(); }

window.addEventListener('load', init, false);

Page 88: Back to the canvas element Animating within the canvas

The g16dramatics constructor functions

Drama(DOMCanvas canvas, DOMString backgroundImageSource,

[DOMString name] )

• The two required parameters for the Drama constructor are– the canvas where the Drama will appear

– the address of the image file containing the backdrop for the Drama

• The optional parameter, a name, may be used in messages given to the user about the progress of performing the Drama

Actor(DOMString imageSrc,[DOMString name, number width, number height] )

• The required parameter for the Actor constructor is the address of the image file containing a representation of the Actor

• The optional name parameter may be used in messages given to the user about the progress of the Actor in a drama

• The optional width/height parameters specify the size of the icon used to represent the Actor; the default values are 100/100

Page 89: Back to the canvas element Animating within the canvas

Some Drama methods

• Each Drama has a list of its Actors• The addActor() method adds an Actor to the list of Actors

void addActor(in Actor actor);

• A Drama's startPerformance() method "does what it says on the tin"

void startPerformance();

This involves• showing an introduction to the Drama, if one has been defined• then animating the Actors in the Drama• then, when all the Actors have stopped, showing an epilogue for the Drama,

if one has been defined

Page 90: Back to the canvas element Animating within the canvas

Using the library again• Consider this example, which also uses the g16dramatics library:

http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g18/main.html

• Here, the Drama has a programmer-defined introduction and epilogue

Page 91: Back to the canvas element Animating within the canvas

The HTML file• As before, the HTML file calls the g16dramatics library and has its

own private Javascript file, main.js• This time, the canvas has a border, to provide a frame for the

introduction

<!DOCTYPE html>

<html><head><meta charset="utf-8">

<title>Canvas example</title>

<script src="../g16/dramatics.js" type="text/javascript"></script>

<script src="main.js" type="text/javascript"></script>

</head>

<body>

<canvas id="canvas1" width="500px" height="650px"

style="border:solid 1px black">

Your browser does not support the canvas element

</canvas>

</body></html>

Page 92: Back to the canvas element Animating within the canvas

Architecture of the main.js file• init() specifies that an application-specific function, showMyIntroduction(),

should be used to give a 2-second long introduction and that another one, showMyEpilogue(), should be used to give a postscript to the drama

function showMyIntroduction() { ... }

function showMyEpilogue() { ... }

function init()

{ var canvas1=document.getElementById('canvas1');

var drama1 = new Drama (canvas1,'seascape.jpg');

drama1.showIntroduction = showMyIntroduction;

drama1.lengthOfIntroduction = 2000;

drama1.showEpilogue = showMyEpilogue;

var diver1=new Actor("diver1.png","a black diver");

diver1.setAnimationTrajectory(100,150,400,450);

diver1.setAnimationIterationCount(2); diver1.setAnimationDuration(10);

drama1.addActor(diver1); drama1.startPerformance(); }

window.addEventListener('load', init, false);

Page 93: Back to the canvas element Animating within the canvas

Defining the introduction and epilogue

• A Javascript program can always add a new property to an object• For example, if we added these two lines to init(), it would produce

an alert containing the string 'cloudy' drama1.weather = 'cloudy';

alert(drama1.weather);

• The significance of showIntroduction, lengthOfIntroduction and showEpilogue is that

* the names of these properties are already known in the g16dramatics library

* the showPerformance() method checks to see if the showIntroduction and showEpilogue methods exist and, if so, executes them

* by default these two methods do not exist but, by defining them, we can affect the performance of a drama

* the introduction will last for the length of time specified (in milliseconds) by lengthOfIntroduction

Page 94: Back to the canvas element Animating within the canvas

Defining the introduction and epilogue (contd.)

• Each of the application-specific introduction and epilogue methods simply writes some text onto the canvas

function showMyIntroduction()

{var canvas1=document.getElementById('canvas1');

var context=canvas1.getContext('2d');

context.fillStyle="rgba(255,0,0,30)";

context.font="50px arial";

context.fillText("My animation",100,canvas1.height/2); }

function showMyEpilogue()

{var canvas1=document.getElementById('canvas1');

var context=canvas1.getContext('2d');

context.fillStyle="rgb(255,0,0)";

context.font="80px arial";

context.fillText("The End",100,canvas1.height/2); }

Page 95: Back to the canvas element Animating within the canvas

Some drama events• The g16dramatics library implements a range of events, including

startOfPerformance and endOfPerformance • Consider this program which uses the library

http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g19/main.html • It defines two dramas, one in each of two canvases• The endOfPerformance event for the LHS drama triggers the start of

performing the RHS drama• The endOfPerformance event for the RHS drama then triggers a re-

start of performing the LHS drama• And so on, indefinitely

Page 96: Back to the canvas element Animating within the canvas

The HTML file• There is nothing special about how the two canvases are created

<!DOCTYPE html>

<html><head><meta charset="utf-8">

<title>Canvas example</title>

<script src="../g16/dramatics.js" type="text/javascript"></script>

<script src="main.js" type="text/javascript"></script>

</head>

<body>

<canvas id="canvas1" width="500px" height="650px">

Your browser does not support the canvas element

</canvas>

<canvas id="canvas2" width="500px" height="650px">

Your browser does not support the canvas element

</canvas>

</body></html>

Page 97: Back to the canvas element Animating within the canvas

Architecture of the main.js filevar drama1, drama2;

function restartOther(e)

{ if (e.detail==drama1) { drama2.startPerformance(); }

else if (e.detail==drama2) { drama1.startPerformance(); } }

function init() { ... }

window.addEventListener('load', init, false);

window.addEventListener('endOfPerformance', restartOther, false);

• The two drama variables are global, so they can be accessed by a function which is triggered when the performance of any drama ends

• An endOfPerformance event is a DramaEvent, whose interface is defined as follows:

interface DramaEvent: { attribute Drama detail }

• Its sole field, detail, is the Drama whose performance has ended• restartOther() checks to see which drama has just ended and then

starts/restarts performing the other one

Page 98: Back to the canvas element Animating within the canvas

The init() function• init() creates two dramas, each containing one actor, but it starts

performing only the first drama

function init()

{var canvas1=document.getElementById('canvas1');

drama1 = new Drama(canvas1,'seascape.jpg');

var diver1=new Actor("diver1.png","black diver");

diver1.setAnimationTrajectory(100,150,400,450);

diver1.setAnimationIterationCount(1); diver1.setAnimationDuration(5);

drama1.addActor(diver1);

var canvas2=document.getElementById('canvas2');

drama2 = new Drama(canvas2,'seascape.jpg');

var diver2=new Actor("diver2.png","red diver");

diver2.setAnimationTrajectory(400,150,100,450);

diver2.setAnimationIterationCount(1); diver2.setAnimationDuration(5);

drama2.addActor(diver2);

drama1.startPerformance(); }

Page 99: Back to the canvas element Animating within the canvas

Frame analysis• The g16dramatics library allows each frame to be analysed, and

consequent actions to be taken, before the next frame is drawn• Consider this program which uses the library

http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g20/main.html • It produces an alert message when the two divers crash into each other

Page 100: Back to the canvas element Animating within the canvas

The HTML file• There is nothing special about the HTML file

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8">

<title>Canvas example</title>

<script src="../g16/dramatics.js" type="text/javascript"></script>

<script src="main.js" type="text/javascript"></script>

</head>

<body>

<canvas id="canvas1" width="500px" height="650px">

Your browser does not support the canvas element

</canvas>

</canvas>

</body>

</html>

Page 101: Back to the canvas element Animating within the canvas

Architecture of the main.js file• Each time a Drama draws a frame, it checks to see if a property

called analyse is set• Usually this property is not set but, in this case, the Drama's analyse

property is set, to the application-specific function checkIfDiversCrash()

var checkIfDiversCrash = function () { ... }

function init()

{ var canvas1=document.getElementById('canvas1');

var drama1 = new Drama(canvas1,'seascape.jpg');

var diver1=new Actor("diver1.png","black diver");

diver1.setAnimationTrajectory(100,150,400,450);

diver1.setAnimationIterationCount(1); diver1.setAnimationDuration(5);

drama1.addActor(diver1);

var diver2=new Actor("diver2.png","red diver");

diver2.setAnimationTrajectory(400,150,100,450);

diver2.setAnimationIterationCount(1); diver2.setAnimationDuration(5);

drama1.addActor(diver2);

drama1.analyse = checkIfDiversCrash;

drama1.startPerformance(); }

window.addEventListener('load', init, false);

Page 102: Back to the canvas element Animating within the canvas

The application-specific analysis functionvar checkIfDiversCrash = function ()

{ var cast = this.getActorList();

var blackDiver=cast[0]; var redDiver=cast[1];

var centreX = blackDiver.getX()+(blackDiver.width/2);

var centreY = blackDiver.getY()+(blackDiver.height/2);

if (centreX >= redDiver.getX() &&

centreX <= redDiver.getX()+redDiver.width &&

centreY >= redDiver.getY() &&

centreY <= redDiver.getY()+redDiver.height)

{ alert('The two divers have crashed into each other'); }

}

• The analysis checks if the centre of the blackDiver overlaps with any part of the redDiver and, if so, it produces an alert message

• Notice that the keyword this will be interpreted correctly at execution time,

because the copy of the function being executed is owned by the Drama object

Page 103: Back to the canvas element Animating within the canvas

Doing something more in response to a crash

• The application-specific analysis we have seen would not be very user-friendly

• If the two divers crash, they will continue to do so in several subsequent frames because it will take several animation steps for them to move clear of each other

• The user will, therefore, be confronted with a tediously long sequence of alert messages

• It would be better to do something which would pre-empt this• We will see one approach in the next example

Page 104: Back to the canvas element Animating within the canvas

Modifying an Actor in a frame analysis• Consider this program

http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g21/main.html • When the two divers crash into each other, the black diver eliminates the

red one and then continues on his way• The only difference between this program and the previous one is that

there is a different analysis function

Page 105: Back to the canvas element Animating within the canvas

The revised analysis function• By default, every Actor is visible and tangible• These properties can be used via these methods:

getVisibility(), hide(), reveal(), getTangibility(), makeIntangible(), makeTangible()

• The revised analysis function ignores the red diver if it is intangible• But, if the red diver is tangible and overlaps the black diver,

– the function "kills" it by making it intangible, hiding it and stopping its animation

– The red diver will not, therefore, "bump into" the black diver any more

var checkIfDiversCrash = function ()

{var cast = this.getActorList(); var blackDiver=cast[0]; var redDiver=cast[1];

if (redDiver.getTangibility()=='tangible')

{ var centreX = blackDiver.getX()+(blackDiver.width/2);

var centreY = blackDiver.getY()+(blackDiver.height/2);

if (centreX >= redDiver.getX() && centreX <= redDiver.getX()+redDiver.width &&

centreY >= redDiver.getY() && centreY <= redDiver.getY()+redDiver.height)

{ redDiver.makeIntangible(); redDiver.hide();

redDiver.setAnimationPlayState("stopped"); } }

}

Page 106: Back to the canvas element Animating within the canvas

Modifying an Actor's imageSrc in a frame analysis• Consider this program

http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g22/main.html • When the two divers crash into each other, the black diver "kills" the red

one and continues on his way – but the "ghost" of the murdered diver remains

• The only difference between this program and the previous one is a single replacement line in the analysis function

Page 107: Back to the canvas element Animating within the canvas

The revised analysis function• An Actor's imageSrc attribute is read/writable• In this version of the analysis function, we change the red diver's image

to a ghostly one rather than hiding him completely

var checkIfDiversCrash = function ()

{var cast = this.getActorList(); var blackDiver=cast[0]; var redDiver=cast[1];

if (redDiver.getTangibility()=='tangible')

{ var centreX = blackDiver.getX()+(blackDiver.width/2);

var centreY = blackDiver.getY()+(blackDiver.height/2);

if (centreX >= redDiver.getX() && centreX <= redDiver.getX()+redDiver.width &&

centreY >= redDiver.getY() && centreY <= redDiver.getY()+redDiver.height)

{ redDiver.makeIntangible(); redDiver.imageSrc="ghostDiver.png";

redDiver.setAnimationPlayState("stopped"); } }

}

Page 108: Back to the canvas element Animating within the canvas

Modifying an Actor's trajectory in a frame analysis• Consider this program

http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g23/main.html • As before, when the two divers crash into each other, the black diver

"kills" the red one and continues on his way – BUT, this time, the "ghost" of the murdered diver "ascends to heaven"

• The difference between this program and the previous one is a single line change in the analysis function

Page 109: Back to the canvas element Animating within the canvas

The revised analysis function• An Actor's trajectory can be changed by repeated usage of

setAnimationTrajectory()– Note that, when an Actor is given a new trajectory, the following are

reset to their default values and may have to be changed again if a default value is not what is required: animationIterationCount (1), animationDirection (normal), animationDuration (5)

• In this version of the analysis function, we change the red diver's trajectory as well as changing his image to a ghostly one

var checkIfDiversCrash = function () {var cast = this.getActorList(); var blackDiver=cast[0]; var redDiver=cast[1]; if (redDiver.getTangibility()=='tangible') {var centreX = blackDiver.getX()+(blackDiver.width/2); var centreY = blackDiver.getY()+(blackDiver.height/2); if (centreX >= redDiver.getX() && centreX <= redDiver.getX()+redDiver.width && centreY >= redDiver.getY() && centreY <= redDiver.getY()+redDiver.height) { redDiver.makeIntangible(); redDiver.imageSrc="ghostDiver.png"; redDiver.setAnimationTrajectory(redDiver.getX(),redDiver.getY(), redDiver.getX(),-100); } } }

Page 110: Back to the canvas element Animating within the canvas

More on Drama events• Now that we have seen the use of the analysis function, we can

consider the full range of events of type DramaEvent

Drama Events: startOfPerformance, endOfPerformance, startOfAnalysis, endOfAnalysisinterface DramaEvent: { attribute Drama detail; }

• We have already seen events of type startOfPerformance and

endOfPerformance• If Drama’s analyse property is set, an event of type startOfAnalysis

is triggered at the start of each execution of the analysis function and an event of type endOfAnalysis is triggered at the end of the execution

• The single field in a DramaEvent object, the detail field, contains the Drama which was involved in the event, that is, the Drama whose performance just started/ended, or in which the analysis of a frame just started/ended

Page 111: Back to the canvas element Animating within the canvas

Triggering custom events

• startOfAnalysis and endOfAnalysis events happen too frequently for most purposes

• However, we can use an application-specific analysis function to trigger useful custom events

• Custom events can be triggered by a three-step process which involves using the following DOM2 methods:• Event createEvent(in DOMString eventType) /*a document method */

• void initEvent(in DOMString eventTypeArg,

in boolean canBubbleArg,

in boolean cancelableArg); /* an event method */

• boolean dispatchEvent(in Event evt) /* a node method */

Reference: http://www.w3.org/TR/DOM-Level-2-Events/events.html

• We will now see a program in which an application-specific analysis function uses these methods to trigger a custom event in one drama which prompts a reaction in a second drama

Page 112: Back to the canvas element Animating within the canvas

A custom event in one drama causing a reaction in another• Consider this program

http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g24/main.html • When the shark attacks the black diver in the LHS drama, the two divers in

the RHS drama flee to the surface• This happens as follows:

– the shark attack is detected by an analysis function in the LHS drama– this analysis function triggers a custom event of type "sharkAttack"– an eventListener attached to the window responds to this sharkAttack

event by executing a function which changes the trajectories for the divers in the RHS drama

Page 113: Back to the canvas element Animating within the canvas

Architecture of the main.js filevar RHSDrama;

function makeRHSDiversFlee(e) { ... }

function LocationOfSharkAttack(x,y) { ... }

var checkForSharkAttack = function () { ... }

function init() { ... LHSDrama.analyse=checkForSharkAttack; ... }

window.addEventListener('load', init, false);

window.addEventListener('sharkAttack', makeRHSDiversflee, false);

• The RHSDrama variable is global so that makeRHSDiversFlee() can access it

• checkForSharkattack() is the analysis function for the LHSDrama

• when it triggers a sharkAttack event this causes the execution of makeRHSDiversFlee()

Page 114: Back to the canvas element Animating within the canvas

The init() function• The two dramas are set up as normal, with an analysis function being

specified for the LHSDramafunction init()

{ var canvas1=document.getElementById('canvas1');

var LHSDrama = new Drama(canvas1,'seascape.jpg');

var diver1=new Actor("diver1.png","a black diver");

diver1.setAnimationTrajectory(100,150,400,450); diver1.setAnimationDuration(4.9);

LHSDrama.addActor(diver1);

var shark=new Actor("shark.png","a shark",200,200);

shark.setAnimationTrajectory(-100,350,600,350); shark.setAnimationDuration(9);

LHSDrama.addActor(shark);

LHSDrama.analyse=checkForSharkAttack;

var canvas2=document.getElementById('canvas2');

RHSDrama = new Drama(canvas2,'seascape.jpg');

var diver3=new Actor("diver2.png","a red diver");

diver3.setAnimationTrajectory(200,-20,200,650); diver3.setAnimationDuration(5);

RHSDrama.addActor(diver3);

var diver4=new Actor("diver3.png","a red diver");

diver4.setAnimationTrajectory(400,-50,400,650); diver4.setAnimationDuration(5);

RHSDrama.addActor(diver4);

LHSDrama.startPerformance();

RHSDrama.startPerformance(); }

Page 115: Back to the canvas element Animating within the canvas

The analysis function for the LHSDrama• If the shark encounters the diver, a sharkAttack event is created, its

detail field being an object of type LocationOfSharkAttack, although anything would do instead, since we don't use the detail anywhere

var checkForSharkAttack = function ()

{ var cast = this.getActorList();

diver=cast[0];

shark=cast[1];

if (diver.getTangibility()=='tangible')

{ var centreX = diver.getX()+(diver.width/2);

var centreY = diver.getY()+(diver.height/2);

if (centreX >= shark.getX()+20 && centreX <= shark.getX()+shark.width-20 &&

centreY >= shark.getY()+20 && centreY <= shark.getY()+shark.height-20)

{ var event=document.createEvent("CustomEvent");

var detail = new LocationOfSharkAttack(centreX,centreY);

event.initCustomEvent("sharkAttack",true,false,detail);

document.dispatchEvent(event);

diver.hide();

diver.makeIntangible();

diver.setAnimationPlayState("stopped"); } } }

Page 116: Back to the canvas element Animating within the canvas

The rest of the program• The LocationOfSharkAttack constructor builds an object containing the

(x,y) location of the attack, although this information is not used anywhere

function LocationOfSharkAttack(x,y) { this.x=x; this.y=y; }

• The makeRHSDiversFlee() function simply changes the trajectory for each diver in the RHS drama, making it head straight upwards, out of the canvas

function makeRHSDiversFlee(e)

{ var cast = RHSDrama.getActorList();

for (j=0;j<cast.length;j++)

{actor=cast[j];

actor.setAnimationTrajectory(actor.getX(),actor.getY(), actor.getX(),-200); } }

Page 117: Back to the canvas element Animating within the canvas

Other Drama methods• We have already used most of the Drama methods provided by the

g16dramatics library, but there are a few others:

DOMCanvas getCanvas();

// returns the canvas where the Drama is displayed

DOMString getBackgroundImageSource();

// returns the address of the file containing the image which is used

// as the backdrop for the Drama

DOMString getName();

// returns the (optional) name of the Drama

non-negative-integer getFrameNumber();

// intended for use in analysis functions, this method returns the frame

// number of the current frame in the Drama, 0 for the first frame, etc.

void drawBackground();

// draws the backdrop for the Drama

Page 118: Back to the canvas element Animating within the canvas

Other Actor methods• Although we have used only a few of the Actor methods provided by

the g16dramatics library, we have used those which will probably be most useful in most applications

• Most of the other methods will probably be only be useful in analysis functions, since they enable us to inspect a lot of detailed information about the current state of an Actor

• However, the following methods may be more widely useful, especially when building interactive dramas (games):

void placeAt(in number x, in number y); // places the Actor at (x,y)

void goToStartOfTrajectory();

void maybeMoveToNextPointOnTrajectory();

// if Actor's playstate is running, move Actor to next point along its

// trajectory, taking account of animation-direction

void moveToNextPointOnTrajectory();

// if Actor's playstate is not finished, move Actor to next point along its

// trajectory, taking account of animation-direction

void moveOneStepBack();

// if Actor is not at (x1,y1) move it one step back along its trajectory

// towards (x1,y1) regardless of animation-direction

Page 119: Back to the canvas element Animating within the canvas

Actor events• An Actor event is triggered

– when an Actor's animation starts and when it stops;

– at the start and end of each iteration of the Actor's animation;

– at the beginning and end of each pause in an Actor's animation

Actor Events: startOfActorAnimation,startOfActorIteration,endOfActorIteration,

endOfActorAnimation, startOfActorPause, endOfActorPause

interface ActorEvent:

{ attribute Actor detail;

// the Actor which was involved in the event }

Page 120: Back to the canvas element Animating within the canvas

An interactive game• Consider this program

http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g25/main.html

• It is an interactive game that was built with the g16dramatics library

• The diver wants to reach the surface• But each time he is hit by a

barracuda's nose, he is injured and loses blood

• The user has 4 buttons to help the diver reach the surface with as few injuries as possible

Page 121: Back to the canvas element Animating within the canvas

The HTML file• There is nothing special about the HTML file - just four buttons and

a canvas<!DOCTYPE html>

<html><head><meta charset="utf-8"><title>Canvas example</title>

<script src="../g16/dramatics.js" type="text/javascript"></script>

<script src="main.js" type="text/javascript"></script>

</head>

<body>

<h1>Can you reach the bottom?</h1>

<button id="button1">Start / Resume</button>

<button id="button2">Pause</button>

<button id="button3">Up</button>

<button id="button4">Down</button>

<br>

<canvas id="canvas1" width="500px" height="650px">

Your browser does not support the canvas element

</canvas>

</body>

</html>

Page 122: Back to the canvas element Animating within the canvas

Architecture of the main.js file• The drama is global so that it can be accessed by many functions• Another global counts the number of injuries• Barracudas are added randomly and a global variable remembers

when the last one was added• When the diver's animation ends, the number of injuries is reported

var drama1;

var numberOfInjuries=0;

var frameWhenLastBarracudaAdded;

function checkFrame() { ... }

function maybeAddABarracuda() { ... }

function addABarracuda() { ... }

function startOrResume() { ... } function pause() { ... }

function up() { ... } function down() { ... }

function reportNumberOfInjuries(e) { ... }

function randomInteger(lower,upper) { ... }

function init() { ... }

window.addEventListener('load', init, false);

window.addEventListener('endOfActorAnimation', reportNumberOfInjuries, false);

Page 123: Back to the canvas element Animating within the canvas

The init() function• eventListeners are add to the buttons, the diver and three barracudas

are added to the drama, the frame at which this happened being noted• The drama is started – but immediately, the diver is pausedfunction init() {var button1 = document.getElementById("button1"); button1.addEventListener('click',startOrResume,false); var button2 = document.getElementById("button2"); button2.addEventListener('click',pause,false); var button3 = document.getElementById("button3"); button3.addEventListener('click',up,false); var button4 = document.getElementById("button4"); button4.addEventListener('click',down,false);

var canvas1=document.getElementById('canvas1'); drama1 = new Drama(canvas1,'seascape.jpg');

var diver1=new Actor("diver1.png","a black diver"); diver1.setAnimationTrajectory(300,600,300,0); drama1.addActor(diver1);

addABarracuda(); addABarracuda(); addABarracuda(); frameWhenLastBarracudaAdded=0;

drama1.analyse=checkFrame; drama1.startPerformance(); diver1.setAnimationPlayState('paused'); }

Page 124: Back to the canvas element Animating within the canvas

The addBarracuda() function• A random integer between 100 and 500 is chosen as the y-coordinate

for the new barracuda• A random number between 5 and 10 is used as the duration of its travel

across the canvas

function addABarracuda()

{ var barracuda=new Actor("barracuda.png","a barracuda",200,50);

var y=randomInteger(100,550);

barracuda.setAnimationTrajectory(-200,y,500,y);

barracuda.setAnimationIterationCount(1);

var duration=randomInteger(5,10);

barracuda.setAnimationDuration(duration);

drama1.addActor(barracuda);

barracuda.setAnimationPlayState('running'); }

function randomInteger(lower,upper)

{ return lower + Math.round(Math.random() * (upper-lower)); }

Page 125: Back to the canvas element Animating within the canvas

The analyse function checkFrame()• If a barracuda nose overlaps the diver the injury count in incremented and a

blood actor is introduced, with a trajectory to the bottom of the canvas• To prevent duplicate injuries, each barracuda that injures the diver is made

intangible• Whether/not there is an injury, maybe another barracuda is added to the drama

function checkFrame() {var cast = this.getActorList(); var diver=cast[0]; for (j=1;j<cast.length;j++) {var barracuda=cast[j]; var noseX = barracuda.getX()+(barracuda.width); var noseY = barracuda.getY()+(barracuda.height/2); if (barracuda.getTangibility()=='tangible') { if (noseX >= diver.getX()+20 && noseX <= diver.getX()+diver.width-20 && noseY >= diver.getY()+20 && noseY <= diver.getY()+diver.height-20) { numberOfInjuries= numberOfInjuries+1; barracuda.makeIntangible(); var blood=new Actor("blood.png","some blood"); blood.makeIntangible(); blood.setAnimationTrajectory(noseX,noseY,noseX,650); this.addActor(blood); blood.setAnimationPlayState('running'); } } } maybeAddABarracuda(); }

Page 126: Back to the canvas element Animating within the canvas

The maybeAddBarracuda() function• A barracuda is added only if at least 10 frames have elapsed since the

last one was added

function maybeAddABarracuda()

{ var thisFrame=drama1.getFrameNumber();

if (thisFrame >= frameWhenLastBarracudaAdded+10)

{ addABarracuda();

frameWhenLastBarracudaAdded=thisFrame;

}

}

Page 127: Back to the canvas element Animating within the canvas

The reportNumberOfInjuries() function• This function is called when every actor, including all the barracudas and

the bloods, finishes its animation• But it does something only when the actor whose animation just ended

was the diver

function reportNumberOfInjuries(e)

{ if (e.detail.getActorNumber()==0) //if it is the diver who has ended

{ alert('Number of injuries='+numberOfInjuries); }

}

window.addEventListener('endOfActorAnimation', reportNumberOfInjuries, false);

Page 128: Back to the canvas element Animating within the canvas

The button event listeners• Each of these locates the diver in the drama’s cast and applies the

appropriate method

function pause()

{ var cast=drama1.getActorList(); var diver1=cast[0];

diver1.setAnimationPlayState('paused'); }

function startOrResume()

{var cast=drama1.getActorList(); var diver1=cast[0];

if (diver1.getAnimationPlayState()=='paused')

{ diver1.setAnimationPlayState('running'); }

function up()

{ var cast=drama1.getActorList(); var diver1=cast[0];

diver1.moveToNextPointOnTrajectory(); }

function down()

{ var cast=drama1.getActorList(); var diver1=cast[0];

diver1.moveBackOneStep(); }

Page 129: Back to the canvas element Animating within the canvas

Animation and timeout callbacks• An animation is a sequence of frames• When one frame has just been drawn, drawing of the next frame is

scheduled by means of a timeout• Before looking at this, we should revisit the concept of timeouts in

Javascript

Page 130: Back to the canvas element Animating within the canvas

Timeouts in Javascript• Consider this simple HTML page:

http://www.cs.ucc.ie/j.bowen/cs4506/slides/callback/g1/main.html

• A timeout is used to delay the p2 alert by 1000ms

Page 131: Back to the canvas element Animating within the canvas

HTML and Javascriptmain.html

<!DOCTYPE html><html><head><meta charset="utf-8">

<title>Callback example</title>

<script src="main.js" type="text/javascript"></script></head>

<body><h1>Callback example</h1></body></html>

main.js

function start() { alert('p1'); setTimeout("next()",1000); }

function next() { alert('p2'); }

if (window.attachEvent)

{ window.attachEvent('onload',start); }

else { window.addEventListener('load', start, false); }

Page 132: Back to the canvas element Animating within the canvas

setTimeout() and the window object• setTimeout() is a method of the window object• We can show this by redefining it • Consider the second alert from this HTML page:

http://www.cs.ucc.ie/j.bowen/cs4506/slides/callback/g2/main.html

• The source-code is on the next slide

Page 133: Back to the canvas element Animating within the canvas

We redefined window.setTimeout()

• The HTML is the same as before• But the Javascript file included a redefinition of window.setTimeout():

function setTimeout(someString)

{ alert('The first parameter is: '+someString); }

function start() { alert('p1'); setTimeout("next()",1000); }

function next() { alert('p2'); }

if (window.attachEvent)

{ window.attachEvent('onload',start); }

else { window.addEventListener('load', start, false); }

• Instead of redefining, window.setTimeout(), we could supersede it, as will be shown on the next few slides

Page 134: Back to the canvas element Animating within the canvas

Superseding window.setTimeout()• Consider the second alert from this HTML page:

http://www.cs.ucc.ie/j.bowen/cs4506/slides/callback/g3/main.html

• The source-code on the next slide shows that, instead of redefining windows.setTimeout(), we have superseded it

Page 135: Back to the canvas element Animating within the canvas

We superseded window.setTimeout()• The HTML is the same as before• But the Javascript file includes a constructor function which defines a

private method called setTimeout

function Widget()

{ function setTimeout(someString) { alert('The first parameter is: '+someString);

this.start = function () { alert('p1'); setTimeout("next()",1000); } }

function next() { alert('p2'); }

function init() { var w1 = new Widget(); w1.start(); }

if (window.attachEvent)

{ window.attachEvent('onload',init); }

else { window.addEventListener('load', init, false); }

• We can avoid the supersession problem by explicitly calling window.setTimeout(), as is shown in the next few slides

Page 136: Back to the canvas element Animating within the canvas

Explicitly calling window.setTimeout()• Consider the second alert from this HTML page:

http://www.cs.ucc.ie/j.bowen/cs4506/slides/callback/g4/main.html

• It shows that windows.setTimeout() has been executed - the next slide shows that it was called explicitly

Page 137: Back to the canvas element Animating within the canvas

Explicitly calling window.setTimeout()• The HTML is the same as before• Explicitly calling window.setTimeout() avoids calling the private method

which is also called setTimeout

function Widget()

{ function setTimeout(someString) { alert('The first parameter is: '+someString); }

this.start = function () { alert('p1'); window.setTimeout("next()",1000); } }

function next() { alert('p2'); }

function init() { var w1 = new Widget(); w1.start();}

if (window.attachEvent)

{ window.attachEvent('onload',init); }

else { window.addEventListener('load', init, false); }

Page 138: Back to the canvas element Animating within the canvas

The keyword this and window.setTimeout()• Consider the second alert from this HTML page:

http://www.cs.ucc.ie/j.bowen/cs4506/slides/callback/g5/main.html

• Only one alert is produced, even though the source code on the next slide shows that a second one is expected

Page 139: Back to the canvas element Animating within the canvas

Explicitly calling window.setTimeout()• The HTML is the same as before• window.setTimeout() seems to be calling the public method, next(),

which belongs to the widget

function Widget()

{this.start = function () { alert('p1'); window.setTimeout("this.next()",1000); }

this.next = function () { alert('p2'); } }

function init() { var w1 = new Widget(); w1.start(); }

if (window.attachEvent)

{ window.attachEvent('onload',init); }

else { window.addEventListener('load', init, false); }

• But we do not get the p2 alert which would be produced by that method• The problem lies with the interpretation of this in the first argument to

window.setTimeout()

Page 140: Back to the canvas element Animating within the canvas

this and window.setTimeout() (contd.)• Notice that the second alert from this HTML page contains the text

'p3'http://www.cs.ucc.ie/j.bowen/cs4506/slides/callback/g6/main.html

• Let's examine the source code on the next slide

Page 141: Back to the canvas element Animating within the canvas

this and window.setTimeout() (contd.)• The HTML is the same as before• window.setTimeout() seems to be calling the public method, next(),

which belongs to the widget

function Widget()

{this.start = function () { alert('p1'); window.setTimeout("this.next()",1000); }

this.next = function () { alert('p2'); } }

function next() { alert('p3'); }

function init() { var w1 = new Widget(); w1.start(); }

if (window.attachEvent) { window.attachEvent('onload',init); }

else { window.addEventListener('load', init, false); }

• But it is actually calling the global function which is also called next()• This is because, when the code "this.next()" is executed, this is

interpreted to mean the window object• So, there seems to be a problem making setTimeout call a public

method belonging to an object• What about making setTimeout call a private method?

Page 142: Back to the canvas element Animating within the canvas

The execution context for window.setTimeout()• Notice that the second alert from this HTML page contains the text

'p3'http://www.cs.ucc.ie/j.bowen/cs4506/slides/callback/g7/main.html

• Let's examine the source code on the next slide

Page 143: Back to the canvas element Animating within the canvas

The execution context for window.setTimeout()• The HTML is the same as before• window.setTimeout() seems to be calling the private method, next(),

which belongs to the widget

function Widget()

{this.start = function () { alert('p1'); window.setTimeout("next()",1000); }

function next() { alert('p2'); } }

function next() { alert('p3'); }

function init() { var w1 = new Widget(); w1.start(); }

if (window.attachEvent) { window.attachEvent('onload',init); }

else { window.addEventListener('load', init, false); }

• But it is actually calling the global function which is also called next()• Again, this is because of the execution context for setTimeout• When a string containing Javascript code that was passed to

setTimeout is executed, it is interpreted in the global (window) context• So, can we ever make setTimeout call object methods?• Yes, although only in later versions of Javascript

Page 144: Back to the canvas element Animating within the canvas

The first argument to window.setTimeout()• The first browsers which supported window.setTimeout() required that

the first argument be a string• However, later browsers support a version of Javascript in which the

first argument can belong to either of two types– a string, as we have seen

– a function reference

Page 145: Back to the canvas element Animating within the canvas

Using a function reference in setTimeout()• Notice that the second alert from this HTML page contains the text

'p2'http://www.cs.ucc.ie/j.bowen/cs4506/slides/callback/g8/main.html

• Let's examine the source code on the next slide

Page 146: Back to the canvas element Animating within the canvas

Using a function reference in setTimeout()• The HTML is the same as before• But, now, the 1st argument to window.setTimeout() is a reference to the

private method, next(), which belongs to the widget

function Widget()

{this.start = function () { alert('p1'); window.setTimeout(next,1000); }

function next() { alert('p2'); } }

function next() { alert('p3'); }

function init() { var w1 = new Widget(); w1.start(); }

if (window.attachEvent) { window.attachEvent('onload',init); }

else { window.addEventListener('load', init, false); }

• So, although setTimeout is executed in the global (window) context, the correct method is called after the 1000ms delay

• We can also use a function reference to call a public method belonging to an object, as we shall see on the next few slides

Page 147: Back to the canvas element Animating within the canvas

Using a function reference in setTimeout() (contd.)

• Notice that the second alert from this HTML page contains the text 'p2'

http://www.cs.ucc.ie/j.bowen/cs4506/slides/callback/g9/main.html

• Let's examine the source code on the next slide

Page 148: Back to the canvas element Animating within the canvas

Using a function reference in setTimeout() (contd.)

• The HTML is the same as before• But, now, the 1st argument to window.setTimeout() is a reference to the

public method, next(), which belongs to the widget

function Widget()

{this.start = function () { alert('p1'); window.setTimeout(this.next,1000); }

this.next = function () { alert('p2'); } }

function next() { alert('p3'); }

function init() { var w1 = new Widget(); w1.start(); }

if (window.attachEvent) { window.attachEvent('onload',init); }

else { window.addEventListener('load', init, false); }

• So, although setTimeout is executed in the global (window) context, the correct method is called after the 1000ms delay

• We can also use this approach to pass parameters, as we shall see on the next few slides

Page 149: Back to the canvas element Animating within the canvas

Passing parameters via setTimeout()

• Notice the second alert from this HTML page http://www.cs.ucc.ie/j.bowen/cs4506/slides/callback/g10/main.html

• Let's examine the source code on the next slide

Page 150: Back to the canvas element Animating within the canvas

Passing parameters via setTimeout() (contd.)

• The HTML is the same as before• But the 1st argument to window.setTimeout(), which refers to the next()

method belonging to the widget, includes an argument

function Widget()

{this.start = function () { alert('p1'); window.setTimeout(this.next(5),1000); }

this.next = function (arg) { alert('The argument was: '+arg); } }

function next() { alert('p3'); }

function init() { var w1 = new Widget(); w1.start(); }

if (window.attachEvent) { window.attachEvent('onload',init); }

else { window.addEventListener('load', init, false); }

• So, using this approach, arguments can be passed to timeout-delayed function calls

Page 151: Back to the canvas element Animating within the canvas

setTimeout call inside a private method

• Notice the third alert from this HTML page http://www.cs.ucc.ie/j.bowen/cs4506/slides/callback/g11/main.html

• Let's examine the source code on the next slide

Page 152: Back to the canvas element Animating within the canvas

setTimeout call inside a private method (contd.)

• The HTML is the same as before• But we now have a sequence of timeout calls, including one inside a

private method which invokes another private method belonging to the same object

function Widget()

{this.start = function () { alert('p1'); window.setTimeout(next,1000); }

function next() { alert('p2'); window.setTimeout(last,1000); }

function last() { alert('p4'); } }

function next() { alert('p3'); }

function init() { var w1 = new Widget(); w1.start(); }

if (window.attachEvent) { window.attachEvent('onload',init); }

else { window.addEventListener('load', init, false); }

Page 153: Back to the canvas element Animating within the canvas

this in a setTimeout call inside a private method

• Notice the different value in the third alert from this HTML page http://www.cs.ucc.ie/j.bowen/cs4506/slides/callback/g12/main.html

• Let's examine the source code on the next slide

Page 154: Back to the canvas element Animating within the canvas

this in a setTimeout call inside a private method (contd.)

• The HTML is the same as before• But now the second timeout call, which seems to invoke a public

method belonging to the widget, actually invokes a global function

function Widget()

{this.start = function () { alert('p1'); window.setTimeout(next,1000); }

function next() { alert('p2'); window.setTimeout(this.last,1000); }

this.last = function() { alert('p4'); }

}

function next() { alert('p3'); }

function last() { alert('p5'); }

function init() { var w1 = new Widget(); w1.start(); }

if (window.attachEvent) { window.attachEvent('onload',init); }

else { window.addEventListener('load', init, false); }

• What would happen if we use the "self" strategy we saw earlier?

Page 155: Back to the canvas element Animating within the canvas

Using "self" in a setTimeout call in a private method

• Notice the third alert from this HTML page http://www.cs.ucc.ie/j.bowen/cs4506/slides/callback/g13/main.html

• Let's examine the source code on the next slide

Page 156: Back to the canvas element Animating within the canvas

Using "self" in a setTimeout call in a private method(contd.)

• The HTML is the same as before• The Javascript is as follows:

function Widget()

{ this.start = function () { alert('p1'); window.setTimeout(next,1000); }

var self=this;

function next() { alert('p2'); window.setTimeout(self.last,1000); }

this.last = function() { alert('p4'); } }

function next() { alert('p3'); }

function last() { alert('p5'); }

function init() { var w1 = new Widget(); w1.start(); }

if (window.attachEvent) { window.attachEvent('onload',init); }

else { window.addEventListener('load', init, false); }

• The widget method is used

Page 157: Back to the canvas element Animating within the canvas

Using setTimeout in a version of the g16dramatics library• One way to control animation is to use setTimeout, as shown below

function startAnimation()

{frameNumber=0; self.drawBackground();

for (i=0;i<actorList.length;i++)

{actor=actorList[i]; ...

actor.goToStartOfTrajectory(); actor.setAnimationPlayState('running'); }

...

if (self.analyse) { ... }

var callBack=continueAnimation; setTimeout(callBack,millisecondsPerFrame); }

function continueAnimation()

{ ... /*Draw frame and, perhaps, analyse it*/

if (self._getActive())

{ callBack=continueAnimation; setTimeout(callBack,millisecondsPerFrame); } }

• This version works and is available in http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g16/dramaticsOwn.js

Page 158: Back to the canvas element Animating within the canvas

A newer approach to animation

• Although the custom use of setTimeout works, browser developers are starting to provide a new window method called requestAnimationFrame

• The advantages claimed for this new window method are– the browser can optimize concurrent animations, putting them all

into a single repaint cycle, leading to smoother animation;– if an animation is in a tab that's not visible, the browser won't

keep it running, which means less CPU usage, less GPU usage, less memory usage, lower power usage

Page 159: Back to the canvas element Animating within the canvas

requestAnimationFrame() method

• The specification of this method has been changing quite quickly• The latest version of the specification (dated Feb 2012) is here:

http://www.w3.org/TR/2012/WD-animation-timing-20120221/

• (A lot of) it has been implemented by -moz- and -webkit- browsers • Since some browsers, eg Opera, do not yet support it, the best way to

use it is still to use a shim (a wrapper) which – will use the browser version if it exists

– but which will define a custom version using setTimeout if the browser does not provide a version

Page 160: Back to the canvas element Animating within the canvas

A shim for the requestAnimationFrame() method• The shim can be written in various ways, but here is one version:

var framesPerSecond = 60;var millisecondsPerFrame = 1000/framesPerSecond;

var requestAnimFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame || window.requestAnimationFrame || function (callback) {window.setTimeout(callback, millisecondsPerFrame); } ;

• Our shim is called requestAnimFrame• If the browser provides (a possibly prefixed version of)

requestAnimationFrame, we set the shim to that• Otherwise, we define a custom anonymous function as the shim• This custom function uses setTimeout

Page 161: Back to the canvas element Animating within the canvas

Using the shim in the g16dramatics library

• This shim is used in the g16dramatics library• Usage of setTimeout, which we saw in dramaticsOwn.js, a few slides

ago, is replaced by calls to the shim

function startAnimation()

{frameNumber=0; self.drawBackground();

for (i=0;i<actorList.length;i++)

{actor=actorList[i]; ...

actor.goToStartOfTrajectory(); actor.setAnimationPlayState('running'); }

...

if (self.analyse) { ... }

var callBack=continueAnimation; requestAnimFrame(callBack); }

function continueAnimation()

{ ... /*Draw frame and, perhaps, analyse it*/

if (self._getActive())

{ callBack=continueAnimation; requestAnimFrame(callBack); } }

Page 162: Back to the canvas element Animating within the canvas

Using sound• HTML5 provides an audio element

<audio>

<source src ="scream.wav" type ="audio/wav" />

Your browser does not support the audio element.

</audio>

• As usual, its content is displayed if the browser does not recognize the tag

• A source element is used to specify the file which contains the audio• Different browsers support different audio formats but the .wav

format seems widely supported at present• Playback of the audio content is controlled by JavaScript• However, not all browsers support the full API yet

Page 163: Back to the canvas element Animating within the canvas

Using sound in a shark animation

• This is the HTML file for

http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g16/main.html

<!DOCTYPE html>

<html><head><meta charset="utf-8"><title>Canvas example</title>

<script src="dramatics.js" type="text/javascript"></script>

<script src="main.js" type="text/javascript"></script></head><body>

<canvas id="myCanvas" width="500px" height="650px">

Your browser does not support the canvas element

</canvas>

<audio> <source src="scream.wav" type="audio/wav" />

Your browser does not support the audio element.

</audio>

<audio> <source src="jaws.wav" type="audio/wav" />

Your browser does not support the audio element.

</audio></body></html>

Page 164: Back to the canvas element Animating within the canvas

Using sound in a shark animation (contd.)

• Extract from the main.js file for

http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g16/main.html

• Notice that looping is not (yet) supported in Mozilla

function playIntroMusic()

{var v = document.getElementsByTagName("audio")[1];

v.loop=true; // not implemented in -moz- but is in -webkit-

v.currentTime=0; v.play(); }

function playScream()

{ var v = document.getElementsByTagName("audio")[0];

v.currentTime=0; v.play(); }

function showIntroductionToJaws() { ... playIntroMusic(); }

function handleSharkAttack(e) { playScream(); ... }

Page 165: Back to the canvas element Animating within the canvas

Sound looping, even in Mozilla browsers

• Insert this function definition into the Javascript: function loopSoundtrack()

{ var v = document.getElementsByTagName("audio")[1];

v.currentTime=0; }

• Also, insert these lines into init()var v = document.getElementsByTagName("audio")[1];

v.addEventListener('ended', loopSoundtrack, false);

• These changes are used in the following web page, which, even when accessed in a –moz- browser, will play the sound track repeatedly http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g16/mainloop.html

• However, the soundtrack will continue until the page is closed, even after the performance has ended

• On the next slide, we deal with this issue

Page 166: Back to the canvas element Animating within the canvas

Stopping the sound looping

• Insert this function definition into the Javascript: function stopLoopingSoundtrack()

{ var v = document.getElementsByTagName("audio")[1];

v.removeEventListener('ended',loopSoundtrack,false); }

• Also, insert this line at the bottom of the file:window.addEventListener('endOfPerformance', stopLoopingSoundtrack, false);

• These changes are used in the following web page, which will play the sound track repeatedly but will stop when the performance has ended http://www.cs.ucc.ie/j.bowen/cs4506/slides/canvasDiving/g16/mainloop2.html