1.68k likes | 1.86k Vues
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
E N D
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
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
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
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>
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);
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); }
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 • Format setTimeout(" ...some Javascript code ... ",delay);
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
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>
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);
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(); }
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); }
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(); }
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
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(); };
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 */
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 */
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(); };
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 */
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 object function 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 */
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
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>
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);
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(); };
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); };
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() ; }
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'); } varimage = 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); } }
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; } }
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
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>
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);
The Background() concept is unchanged • The Background() interface is unchanged NamedConstructor=Background(DOMString imageSrc) interface Background { attribute DOMString imageSrc; void draw(); }; • So its implementation is unchanged 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); } }
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();
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 numbergetAnimationDuration(); DOMStringgetAnimationPlayState(); trajectory getAnimationTrajectory(); • In fact, however, we will find that we need just this one DOMStringgetAnimationPlayState();
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); DOMStringgetAnimationPlayState(); };
Before we ... • Before we consider how to implement the new Actor concept, let's see it in use
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(); }
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); }
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); } }
The pause() and resume() functions function 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()
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
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
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>
The keyword this in Javascript, part 3 • Seehttp://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>
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>
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
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
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
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