active objects - java: an eventful...

22
CHAPTER 9 Active Objects I n Chapter 7 you learned about Java control constructs to perform repetition. So far, the examples that you have seen have largely involved repeated patterns in drawings—blades of grass, bricks, and grid lines. We now consider another type of application in which repetition plays a major role: animation. In this chapter, we introduce active objects, which, together with the while statement, can be used to create simple animations. 9.1 Animation If you ever owned a “flip book” as a child, you certainly know how animation works. A series of pictures is drawn, each slightly different from the one before it. The illusion of movement is created by quickly flipping through the series of pictures. As an example, consider the snapshots of a ball in Figure 9.1. These are just a few pictures from a series of drawings that illustrate the action of a ball being dropped and falling to the ground. Each image shows the ball at a slightly different (i.e., lower) position than the one before it. If we were to flip through these images quickly, it would appear to us that the ball was actually falling. You might already be imagining how to write a program to display the falling-ball animation. To display the ball, all you need to do is construct a FilledOval. Once you have constructed the ball, you can create the illusion of movement down the screen by repeatedly moving the FilledOval in the downward direction. If ballGraphic is the name of a variable of type FilledOval, then movement can be achieved by repeatedly doing the following: ballGraphic.move( 0, Y DISPLACEMENT ); where Y DISPLACEMENT is a small value. A while loop can be used to achieve the repeated movement. Although these ideas are important components of creating moving images, you need to know one more thing before you can begin to create animations of your own. 226

Upload: dodieu

Post on 27-Mar-2018

215 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

C H A P T E R 9Active Objects

I n Chapter 7 you learned about Java control constructs to perform repetition. So far, theexamples that you have seen have largely involved repeated patterns in drawings—blades

of grass, bricks, and grid lines. We now consider another type of application in which repetitionplays a major role: animation.

In this chapter, we introduce active objects, which, together with the while statement, can beused to create simple animations.

9.1 Animation

If you ever owned a “flip book” as a child, you certainly know how animation works. A seriesof pictures is drawn, each slightly different from the one before it. The illusion of movement iscreated by quickly flipping through the series of pictures. As an example, consider the snapshotsof a ball in Figure 9.1. These are just a few pictures from a series of drawings that illustrate theaction of a ball being dropped and falling to the ground. Each image shows the ball at a slightlydifferent (i.e., lower) position than the one before it. If we were to flip through these imagesquickly, it would appear to us that the ball was actually falling.

You might already be imagining how to write a program to display the falling-ball animation.To display the ball, all you need to do is construct a FilledOval. Once you have constructedthe ball, you can create the illusion of movement down the screen by repeatedly moving theFilledOval in the downward direction. If ballGraphic is the name of a variable of typeFilledOval, then movement can be achieved by repeatedly doing the following:

ballGraphic.move( 0, Y DISPLACEMENT );

where Y DISPLACEMENT is a small value. A while loop can be used to achieve the repeatedmovement. Although these ideas are important components of creating moving images, you needto know one more thing before you can begin to create animations of your own.

226

Page 2: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

Section 9.2 Active Objects 227

Figure 9.1 A falling ball in motion

9.2 Active Objects

Ultimately we will want to create fairly complex animations. For example, we might want tocreate a rain animation, where each raindrop is a like a ball falling from the the top of the canvasto the bottom. How can we construct many balls that all have to fall at the same time? It wouldclearly be very difficult to write a method that needed to be responsible for keeping track of andmoving so many objects simultaneously. This task would certainly be easier if we could create anobject that represented a ball that knew how to move itself to the bottom of the screen. Fortunately,we can do so by defining a class of active objects.

Before looking in detail at a complete animation of rain, let’s consider how we can definea FallingBall class. By doing so, you will see that a rain animation can be achieved largelyby implementing a class of objects (i.e., droplets) that behave like falling balls. The animationwill ultimately appear to be quite complex, because there will be many objects falling. But theimplementation of each individual raindrop is fairly simple.

An object of a FallingBall class should look like a round ball and should know how to moveitself from the top of the canvas to the bottom. Saying something like new FallingBall(...);should not only draw a picture, but should set it in motion down the canvas.

If we had such a class, we could define a FallingBallController class like that shownin Figure 9.2. In it we display some simple instructions in the begin method. In addition, inonMouseClick we construct a new FallingBall, using information about the location of themouse click.

When we look at the class FallingBall, shown in Figure 9.3, we see that it is somewhatdifferent from the classes we wrote earlier. First, rather than extending WindowController, thisclass extends ActiveObject. Whenever we want to create an animation, we will define a classthat extends ActiveObject.

Another difference between this class and those we have defined previously is that at the endof the constructor, we have included the line

start();

The constructor sets up the falling ball by constructing a FilledOval at the location passed inas a parameter. Once the drawing of the ball is created, we need to set it in motion. The start

Page 3: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

228 Chapter 9 Active Objects

public class FallingBallController extends WindowController {

... // Constant declarations omitted...

public void begin() {// Display instructionsnew Text( "Click to make a falling ball...",

INSTR LOCATION, canvas );}

public void onMouseClick( Location point ) {// Make a new ball when the player clicksnew FallingBall( point, canvas );

}}

Figure 9.2 Class that creates a falling ball with each mouse click

statement tells Java that we are ready to initiate the method run, which is defined below theconstructor. Every ActiveObject must have a run method. This specifies the behavior that theobject will have.

The runmethod of our FallingBall class is made up primarily of a single while statement.The loop executes as long as the y coordinate of the upper left corner of the ball is less than thescreen height. That is, it will execute as long as the ball is actually visible on the canvas. The bodyof the loop moves the ball slightly down, then rests briefly. As long as the ball is visible, theselines will be executed: a slight movement downward, a brief rest, a slight movement downward, abrief rest, and so on. Once the while statement terminates, the ball is removed from the canvas,and the run method ends.

The line that allows the falling ball to rest is

pause( DELAY TIME );

where we have defined the constant to have the value 33. This specifies that we want the ballto rest at least 33 milliseconds before moving again. The pause serves an important purpose.It allows us to see the movement of the ball. The computer works at such high speed that if wedidn’t explicitly pause between movements of the ball, it would zip down the canvas so quicklythat we would hardly see it.

To create an animated object like our falling ball, you need to do the following:" define a class that extends ActiveObject," include start(); as the last statement in the constructor." make sure the class includes a run method," be sure to pause occasionally within the run method,

The constructor will likely have the same general form as our constructor in Figure 9.3. That is, itwill do all the setup of the object you are creating, and will then include the start(); statementto activate run.

Page 4: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

Section 9.2 Active Objects 229

// Class for an animated ball that falls down the canvaspublic class FallingBall extends ActiveObject {

// The size of the ballprivate static final int SIZE = 10;

// The delay between successive moves of the ballprivate static final int DELAY TIME = 33;// Number of pixels ball falls in a single moveprivate static final double Y STEP = 4;

// The image of the ballprivate FilledOval ballGraphic;// The canvasprivate DrawingCanvas canvas;

public FallingBall( Location initialLocation, DrawingCanvasaCanvas ) {

canvas = aCanvas;ballGraphic = new FilledOval( initialLocation, SIZE, SIZE,

canvas );start();

}

public void run() {while ( ballGraphic.getY() < canvas.getHeight() ) {

ballGraphic.move( 0, Y STEP );pause( DELAY TIME );

}ballGraphic.removeFromCanvas();

}}

Figure 9.3 Code for a FallingBall class

It may at first seem odd that you have to define a method named run but never actually invokethis method. Instead of calling run, you initiate its execution by invoking start. While thismight seem strange, you have seen something similar to this before. When you write a programthat extends WindowController, you write a begin method but never invoke it explicitly.Instead, it is invoked by the Java system, which first makes some preparations that are essentialto your program’s execution (like creating the window in which it will execute). Invoking startis very much like this. It tells the Java system first to perform the steps necessary to prepare toexecute the run method independently of other activities performed by your program and thento invoke the run method.

Page 5: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

230 Chapter 9 Active Objects

Figure 9.4 A drawing of a raindrop

9.3 Images and VisibleImages

ActiveObjects make it simple to write programs that produce animations. Alas, if you haveDisney or Pixar in mind when we say “animations”, moving FilledOvals or FilledRectsaround the screen may not quite live up to your expectations. We can make things a bit better byusing features of Java and objectdraw that make it easy to display graphics from image files onyour program’s canvas. This will allow us to animate images instead of simple geometric objects.

Support for displaying the contents of an image file on the canvas is provided by two classesnamed Image and VisibleImage. Within a Java program, an Image is used to hold a descriptionof a picture that might be drawn on the canvas. Typically, the description of the image comes froman image file that is either stored on the computer running your program or accessed throughthe web. To access such a file from your program you will use the getImage method. Supposethat we had a drawing of a raindrop like the one shown in Figure 9.4 stored in a file named“raindrop.gif.” We could use Images and VisibleImages to place this drawing on the canvasand animate its motion down the screen. We would first declare an Image variable to refer to thepicture’s description as

private Image rainPicture;

Then, we could associate this variable with the picture by executing the assignment

rainPicture = getImage( "raindrop.gif" );

The getImagemethod expects a String parameter specifying where the image file can be found.If the file is stored on your machine, this will usually just be the name of the file. If you wish toaccess a file from another machine on the web, the file-name argument would be replaced by acomplete URL for the image such as:

rainPicture =getImage( "http://eventfuljava.cs.williams.edu/images/raindrop.gif" );

The relationship between the Image and VisibleImage classes is similar to the relationshipbetween Strings and Text objects. If we have a variable declared as

private String greeting;

and we execute the assignment

greeting = "Howdy"

the word “Howdy” will not immediately appear on the screen. The String certainly describesa message that could be displayed on the screen, but If we actually want it to appear, we have

Page 6: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

Section 9.3 Images and VisibleImages 231

public class FallingRainPicController extends WindowController {

... // Constant declarations omitted...

private Image rainPicture;

public void begin() {// Display instructionsnew Text( "Click to make a falling raindrop...",

INSTR LOCATION, canvas );

rainPicture = getImage( "raindrop.gif" );}

public void onMouseClick( Location point ) {// Make a new raindrop when the player clicksnew FallingRainDrop( rainPicture, point, canvas );

}}

Figure 9.5 Class that creates a falling raindrop picture with each mouse click

to construct a Text object using a construction like:

new Text( greeting, xPos, yPos, canvas );

Similarly, an Image object describes a picture, but we have to construct a VisibleImage usingthe Image to make the picture appear on the screen. For example, to place a copy of our raindropat the center of the top of the canvas we might say

new VisibleImage( rainPicture, 0, canvas.getWidth()/2, canvas );

The first parameter to the VisibleImage constructor must be an Image describing the pictureto be displayed. Next, we either provide two numbers or a Location specifying the coordinateswhere the upper left corner of theVisibleImage should be placed. Finally, as with other graphicalobjects, we must provide the canvas. If we associate a name with a VisibleImage, we canmanipulate it with the same methods we use with FilledRects and FramedOvals, includingmove, moveTo, hide, contains, and others.

The getImage method is associated with the WindowController class. Therefore, it canonly be used within your program’s controller class. In particular, it cannot be accessed from withinan ActiveObject. If we want to use an image in an animation, we will typically use getImageto access the image file in our begin method and then pass the Image as a parameter to theconstructor of some class that extends ActiveObject. As an example, in Figures 9.5 and 9.6 weshow how to revise the code we provided to animate the motion of a ball down the screen, so thatit instead draws our raindrop picture. Note how similar the code for the FallingRainDrop is tothe code of the FallingBall class shown in Figure 9.3.

Separating the object that describes an image from the object that actually causes it to appearon the screen is very helpful when you want to display multiple copies of a picture at the same

Page 7: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

232 Chapter 9 Active Objects

public class FallingRainDrop extends ActiveObject {

// The delay between successive movesprivate static final int DELAY TIME = 33;

// Number of pixels drop falls in a single moveprivate static final double Y SPEED = 4;

// The image of the raindropprivate VisibleImage ballGraphic;

// The canvasprivate DrawingCanvas canvas;

public FallingRainDrop( Image rainPic, Location initialLocation,DrawingCanvas aCanvas ) {

canvas = aCanvas;rainGraphic = new VisibleImage( rainPic, initialLocation,

canvas );start();

}

public void run() {while ( rainGraphic.getY() < canvas.getHeight() ) {

rainGraphic.move( 0, Y SPEED );pause( DELAY TIME );

}rainGraphic.removeFromCanvas();

}}

Figure 9.6 Code for a FallingRainDrop class

time. For example, if we wanted to simulate a rain shower, we could display several rain drops bycreating multiple FallingRainDrops from the single Image named rainPicture. We wouldonly have to invoke getImage once.

9.4 Interacting with Active Objects

We have emphasized that classes that extend ActiveObject must have a run method. In fact,our first example only had a run method. Like other classes, those that extend ActiveObjectcan have as many methods as needed.

Say that we want to allow a user to create a falling ball with a click of the mouse. In addition,say that we want the ball to have the ability to change its color. We can do this by adding the

Page 8: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

Section 9.4 Interacting with Active Objects 233

public class ColorBallController extends WindowController {

... // Constant declarations omitted

private FallingBall droppedBall; // The falling ball

public void begin() {// Display instructionsnew Text( "Click to make a falling ball...",

INSTR LOCATION, canvas );}

public void onMouseClick( Location point ) {// Make a new ball when the player clicksdroppedBall = new FallingBall( point, canvas );

}// Falling ball turns gray when mouse exits canvas

public void onMouseExit( Location point ) {if ( droppedBall != null ) {

droppedBall.setColor( Color.GRAY );}

}// Falling ball turns black when mouse enters canvas

public void onMouseEnter( Location point ) {if ( droppedBall != null ) {

droppedBall.setColor( Color.BLACK );}

}}

Figure 9.7 Defining a class that allows a user to drop a ball that changes color

following method to the FallingBall class:

public void setColor( Color aColor ) {ballGraphic.setColor( aColor );

}We can now modify our FallingBallController so that the ball turns gray when the usermoves the mouse outside the canvas and then turns black again when the user moves the mouseinside the canvas. The new class, called ColorBallController, is shown in Figure 9.4. It hastwo additional methods to handle mouse events. We have also added a variable droppedBall oftype FallingBall, so that when a ball is dropped, we have a way to refer to it in order to changeits color.

You will note that both onMouseExit and onMouseEnter check whether droppedBall isnull before changing its color. It only makes sense to change the ball’s color if there actually isa ball, i.e., if droppedBall is not null. (Remember that instance variables that refer to objects

Page 9: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

234 Chapter 9 Active Objects

are initialized to null.) For example, say that the user moves the mouse out of the canvas beforeever clicking the mouse. If we simply had the line

droppedBall.setColor( Color.GRAY );

in the onMouseExit method, Java would attempt to send the setColor message to the ball, butthere is no ball at this point! Our program would stop, and a message would tell us that we had anull pointer error. This is why we need to check that the ball exists before doing anything else.

➟ Exercise 9.4.1

What happens if the user clicks twice, creating two falling balls, and then moves the mouse outsidethe canvas? ❖

Changing the color of the falling ball while it is dropping is arguably quite dull. What mightbe more interesting would be to allow the user to stop the ball during its fall. Say that we wantedto modify ColorBallController in such a way that the falling ball would stop and disappearwhen the mouse exited the canvas. The modified onMouseExit might then look like this:

public void onMouseExit( Location point ) {if ( droppedBall != null ) {

droppedBall.stopFalling();}

}It is clear that we need to add a method to the FallingBall class to allow a ball to stop itself.

What might not be clear yet is how that method can affect what happens in the run method. Thatis, how can the invocation of the method stopFalling affect the loop in the run method that ismaking the ball fall?

We solve this mystery by adding an instance variable to the class FallingBall, as shown inFigure 9.8. This is a boolean variable with the name moving. This variable will store informationabout the state of the ball. That is, its value will tell us whether the ball is currently moving or hasbeen stopped. We initialize the variable in the constructor to have the value true. When a usercreates a new falling ball, we assume that it is to move by default.

In the run method, we modify the condition of the while statement. As long as the ball isvisible on the canvas and is moving, i.e., has not been stopped, it falls. The method stopFallingaffects the state of the ball. If this message is sent to a falling ball, the boolean variable movingis set to false. This means that the next time the condition of the while is reached, the conditionwill evaluate to false. The loop body will not be executed and the ball will be removed.

9.5 Making Active Objects Affect Other Objects

So far we have seen how to define a class of ActiveObjects, and we have explored ways toaffect them through mutator methods. To this point, however, our active objects have been fairlyisolated, in that they don’t affect other objects. In this section we explore a number of differentways in which active objects can affect others.

Page 10: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

Section 9.5 Making Active Objects Affect Other Objects 235

public class FallingBall extends ActiveObject {

... // Constant declarations omitted

// The image of the ballprivate FilledOval ballGraphic;// Whether the ball is currently movingprivate boolean moving;// The canvasprivate DrawingCanvas canvas;

public FallingBall( Location initialLocation,DrawingCanvas aCanvas ) {

canvas = aCanvas;ballGraphic = new FilledOval( initialLocation, SIZE, SIZE,

canvas );moving = true;start();

}

public void run() {while ( moving && ballGraphic.getY() < canvas.getHeight() ) {

ballGraphic.move( 0, Y STEP );pause( DELAY TIME );

}ballGraphic.removeFromCanvas();

}

public void stopFalling() {moving = false;

}}

Figure 9.8 Adding stopping functionality to a falling ball

9.5.1 Interacting with a Nonactive ObjectLet’s imagine that our falling balls don’t simply disappear when they reach the bottom of thecanvas. Instead, they are collected in a box which becomes ever more full with each ball that falls.You might imagine that the falling balls are raindrops that collect in a pool of water. Figure 9.9shows how such a pool might look before any drops have fallen and how it might look after manydrops have fallen.

Creating the first image in Figure 9.9 is easy. The controller will simply construct a collector,i.e., a FilledRect, at the bottom of the canvas. We then need to make several changes to theFallingBall class. We’ll call this new class FallingDroplet, and the first modification wewill make is to change the instance variable name ballGraphic to dropletGraphic.

Page 11: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

236 Chapter 9 Active Objects

Figure 9.9 A pool filling with rain

We want every falling droplet to know about the collector. The reason is that each droplet willcheck whether it has reached the collector’s surface, and then send a message to the collectortelling it to raise its level a bit. Therefore, the next change we need to make is to add an instancevariable, collector, as in Figure 9.11. As before, the constructor creates the actual droplet, butbefore it is set in motion, the falling droplet needs to remember the collector for later. Recall fromChapter 8 that parameters to the constructor are not visible outside the constructor. Therefore, weneed to remember the collector by assigning the formal parameter aCollector to our instancevariable collector.

We have made some significant changes to the run method. The droplet should only continueto move down the screen as long as it is visible. Therefore we modify the condition in the whilestatement so that the droplet will stop moving as soon as it falls below the top of the collector.

Once the loop terminates, we need to remove the droplet as we did before, but we need to doadditional work as well. If the collector is not yet full, we need to fill it a bit. In this case we havedecided to increase its height by a quarter of the droplet size. To make the collector more full, wechange the height of the rectangle and move it up on the canvas so that we can see the new size.

Figure 9.10 shows a modified controller that creates a drop every time the user clicks. Aninstance variable, collector, of type FilledRect, represents the pool. It is constructed inthe begin method. (Note that the constant COLLECTOR RATIO is a fraction that specifies theinitial size of the collector relative to the entire canvas.) In order to allow our falling droplets toaffect the pool, we need to pass collector as a parameter to the FallingDroplet constructor.In this way, each falling drop will know about the pool, so that it can effectively change it. Notethat we have removed the onMouseExit method so that we can focus on the interaction betweenthe drops and the pool.

In general, if we want an active object to have the ability to affect other objects, we can do so bymaking sure the active object knows about the others. One way to do so is to pass that informationin as a parameter to the constructor as we did above. As long as the active object remembers thatinformation for later, it can communicate with the other object as much as it needs to.

In the example above, you might have wondered why the droplets were responsible formodifying the collector. Clearly there has to be some communication between the droplets andthe collector. The way we have shown you is just one option. Another possibility is to have the

Page 12: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

Section 9.5 Making Active Objects Affect Other Objects 237

public class DropCollector extends WindowController {

... // Constant declarations omitted

private FilledRect collector; // Collects falling drops

public void begin() {// Display instructionsnew Text( "Click to make a falling raindrop...",

INSTR LOCATION, canvas );

// Construct a collector for the falling dropscollector =

new FilledRect( 0, canvas.getHeight()-canvas.getHeight()*COLLECTOR RATIO,canvas.getWidth(), canvas.getHeight()*COLLECTOR RATIO, canvas );

}

public void onMouseClick( Location point ) {// Make a new droplet when the player clicksnew FallingDroplet( point, canvas, collector );

}}

Figure 9.10 Adding a collector to collect raindrops

controller manage the collector. After completing a fall, a droplet could send a message tothe controller, telling it to increase the collector size. Note that it would not be a good ideafor the collector to know about the droplets, as it would then be responsible for keeping track ofpotentially many at once.

9.5.2 Active Objects that Construct OtherActive Objects

We just saw that active objects can affect nonactive objects. They can also interact with otheractive objects. In particular, they can create active objects.

We have begun to think about our falling balls as raindrops that can fall into a collector.Naturally, raindrops should fall from a cloud. Rather than having each drop fall with the clickof the mouse, let’s construct a cloud that knows how to drop its own raindrops. If we want aRainCloud to be created with the click of the mouse, the body of the method onMouseClickwill be:

new RainCloud( canvas, collector );

assuming that we want to catch the rain in a pool.

Page 13: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

238 Chapter 9 Active Objects

public class FallingDroplet extends ActiveObject {... // Constant declarations omitted

private FilledOval dropletGraphic; // The droplet

// Collector into which droplet fallsprivate FilledRect collector;

public FallingDroplet( Location initialLocation, DrawingCanvascanvas, FilledRect aCollector ) {

// Draw the dropletdropletGraphic = new FilledOval( initialLocation, SIZE, SIZE,

canvas );

// Remember the collector for latercollector = aCollector;

// Start the motion of the dropletstart();

}

public void run() {

while ( dropletGraphic.getY() < collector.getY() ) {dropletGraphic.move( 0, Y STEP );pause( DELAY TIME );

}

dropletGraphic.removeFromCanvas();if ( collector.getY() > 0 ) {

collector.setHeight( collector.getHeight() + SIZE/4 );collector.move( 0, -SIZE/4 );

}}

}Figure 9.11 Making a droplet fill a collector

The RainCloud class is shown in Figure 9.12. The class header specifies that this classextends ActiveObject. So far we have seen that a class that extends ActiveObject hasa constructor that sets up some sort of animation and that the constructor ends with the startstatement, which initiates therunmethod. Therunmethod performs the actual animation. ThoughActiveObjects are very useful for creating animations, they are not limited to this activity.In this case, we want the run method to generate raindrops.

Page 14: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

Section 9.5 Making Active Objects Affect Other Objects 239

public class RainCloud extends ActiveObject {

... // Constant declarations omitted

// The canvas on which drops will be drawnprivate DrawingCanvas canvas;// Pool in which drops will be collectedprivate FilledRect collector;

public RainCloud( DrawingCanvas aCanvas, FilledRect aCollector ) {

// Remember the canvas for dropping rain latercanvas = aCanvas;// Remember the collectorcollector = aCollector;

// Start the rainstart();

}

public void run() {

// Used to generate random drop locations for rainRandomIntGenerator xGenerator =

new RandomIntGenerator( 0, canvas.getWidth() );int dropCount = 0;// Generate specified number of raindropswhile ( dropCount < MAX DROPS ) {

new FallingDroplet( new Location( xGenerator.nextValue(), 0 ),canvas, collector );

pause( DELAY TIME );dropCount++;

}}

}Figure 9.12 A rain cloud class

We will treat objects of the class FallingDroplet, as in Figure 9.11, as our raindrops.To construct one of these, we need to pass three pieces of information to the contructor: aLocation from which to drop it, the canvas on which it is to be drawn, and a collector. Since theFallingDroplet constructor requires these pieces of information, we need to be certain thatrun knows about locations, about the canvas, and about the collector.

We consider each of these separately. First, let’s think about the locations of raindrops. In orderto simulate real rain, we should probably have each drop fall from a different location in the sky

Page 15: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

240 Chapter 9 Active Objects

(in our case, the top of the canvas). What mechanism do we have for generating a variety of xcoordinates for drop locations? The answer is RandomIntGenerator. In the run method of theRainCloud class, we declare a variable xGenerator that will be used to generate random xcoordinates as drop locations.

Now we need to consider the canvas and how we can pass information about the canvas tothe FallingDroplet constructor. We handle this by having the RainCloud constructor takethe canvas as a parameter. The canvas is then remembered as an instance variable, which wehave called canvas. This variable is then known in the run method and can be passed on to theFallingDroplet constructor. The collector is handled similarly.

In order to limit the amount of rain to fall, we define a constant MAX DROPS that gives usthe maximum number of droplets to be dropped. In order to count droplets, we declare a localvariable, dropCount, in the run method that will keep track of the number dropped so far. Thisis initialized to 0. The condition of the while statement specifies that the body of the loop shouldexecute as long as the count of drops is less than the maximum allowed. The statements in the bodyconstruct a new FallingDroplet, passing a location, the canvas, and collector as parameters.After a raindrop is created, the cloud pauses briefly, and the number of drops so far is incremented.Each time through the loop, a cloud object will create an entirely new active object that is a singleraindrop, i.e., a FallingDroplet.

Note that we did not have to limit the number of raindrops. We could have had a RainCloudgenerate raindrops indefinitely by replacing the while loop as follows:

while ( true ) {new FallingDroplet( new Location(xGenerator.nextValue(), 0 ),

canvas, collector );pause( DELAY TIME );

}

Here, as long as the condition evaluates to true, which it does for each iteration, raindrops willbe generated.

➟ Exercise 9.5.1

a. What would happen if we omitted the statement

dropCount++;

from the run method of RainCloud shown in Figure 9.12?b. What if we omitted the pause? ❖

9.6 Active Objects without Loops

In this chapter, every class of ActiveObjects has had a run method in which a while loopcontrolled the main behavior. At this point you might be wondering whether the run method must

Page 16: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

Section 9.6 Active Objects without Loops 241

public class CreditScroller extends ActiveObject {

... // Miscellaneous declarations omitted

public CreditScroller( DrawingCanvas aCanvas ) {canvas = aCanvas;start();

}

public void run() {new Credit( "Producer . . . Martha Washington", canvas );pause(DELAY TIME);new Credit( "Director . . . George Washington", canvas );pause( DELAY TIME );new Credit( "Script . . . Thomas Jefferson", canvas );pause( DELAY TIME );new Credit( "Costumes . . . Betsy Ross", canvas );}

}Figure 9.13 A class to generate movie credits

contain a while loop. The answer is no. There is no requirement that there be a while loopin run.

As an example, consider the way that movie credits scroll on the screen at the end of a film.Typically they rise from the bottom of the screen, float to the top, and then disappear. We can thinkof each line of the credits as an ActiveObject. Its behavior is similar to that of our falling ball,except that it moves up, rather than down. We might imagine individual lines being generatedby a credit generator, similar to our rain cloud. It could generate a line, wait a bit, then generateanother line, and so on. But there is a big difference between raindrops and film credits. Eachline in the credits is different from any other. The textual information it conveys is unique. Withthe tools you have learned so far, the best way to generate a series of movie credits would bewith the CreditScroller class shown in Figure 9.13, where the Credit class is as shown inFigure 9.14. Note that the CreditScroller does not have a while loop controlling its behavior.The run method in CreditScroller simply generates a series of movie credits, pausing brieflybetween lines. It passes two parameters to the Credit constructor: the actual information to bedisplayed and the canvas.

The Credit constructor takes a String and a canvas and constructs a Text object. Of course,we want each line of the credits to be centered on the canvas. To do this, we begin by constructingthe line so that it is just below the visible canvas. We then determine where it should be movedso that it is centered. Once the line has been positioned, we start its motion up the canvas, justas we moved a falling ball down the canvas. We move the line only while some piece of it isvisible. Once it has moved completely off the canvas, we stop moving it and remove it. The linehas moved completely off the canvas when the bottom of the Text is at y coordinate 0. When thisis the case, the y coordinate of the top of the Text is a negative value, specifically at −height,where height is the Text’s height.

Page 17: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

242 Chapter 9 Active Objects

public class Credit extends ActiveObject {

... // Constant declarations omitted

private Text roleAndName; // Information to be displayed

public Credit( String scrollingLine, DrawingCanvas canvas ) {// Create the line to be displayedroleAndName = new Text( scrollingLine, 0, canvas.getHeight(),

canvas );// Center it on the canvasroleAndName.move( (canvas.getWidth() -

roleAndName.getWidth())/2, 0 );// start the movementstart();

}

public void run() {// Move up while any part of the credit line is visiblewhile ( roleAndName.getY() > -roleAndName.getHeight() ) {

roleAndName.move( 0, -Y STEP );pause( DELAY TIME );

}roleAndName.removeFromCanvas();

}}

Figure 9.14 A class of individual movie credit lines

9.7 Making Animations Smooth

If you were to run the movie credit generator, you would see the four lines of movie credits scrollingup the screen, each line slightly below the one before it. It is also likely that the movement wouldoccasionally appear “jumpy”. That is, the movement up the canvas might not be completelysmooth. In this section, we consider the cause of such behavior and introduce a way of copingwith it.

Consider the while loop in the run method of the Credit class. The line of text is moved abit, then we pause briefly, then we move a bit, pause briefly, and so on. In a perfect world the lineof text would move at exactly the same speed all the way up the canvas. Ideally, the line wouldmove exactly Y STEP pixels up every DELAY TIME milliseconds. The problem here is with thestatement

pause( DELAY TIME );

Page 18: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

Section 9.7 Making Animations Smooth 243

public void run() {double lastTime, currentTime, elapsedTime;// Remember the timelastTime = System.currentTimeMillis();while ( roleAndName.getY() > -roleAndName.getHeight() ) {

// Determine how much time has passedcurrentTime = System.currentTimeMillis();elapsedTime = currentTime - lastTime;// Restart timinglastTime = currentTime;roleAndName.move( 0, -SPEED PER MILLI * elapsedTime );pause( DELAY TIME );

}roleAndName.removeFromCanvas();

}Figure 9.15 Making movement smoother

pause allows us to pause for at least DELAY TIME milliseconds. It does not say that we willpause for exactly that many milliseconds. If the value of DELAY TIME is 33, we might find thata pause lasts 33 milliseconds, 34 milliseconds, or more. If the pauses are longer than 33, ourcredit will move more slowly than expected.

Fortunately, we can handle this problem. To do so, we begin by calculating the number ofpixels to move per millisecond, as follows:

SPEED PER MILLI = Y STEP / DELAY TIME;

Then, before we move, we determine how much time has actually passed since the last time wemoved. Say that we have this information in a variable elapsedTime. We can now calculate thenumber of pixels that we really want to move. To do this, we multiply the speed per millisecond,SPEED PER MILLI, by the elapsed time. This is illustrated in Figure 9.15, which shows how wehave modified the run method of the Credit class.

We declare three local variables in therunmethod, all of typedouble. The first two,lastTimeand currentTime, will hold snapshots of the time. The third, elapsedTimewill be used to holdthe result of our calculation of how much time has passed between moves. We begin timing beforewe reach the while statement. If the condition of the while is true and we enter the loop, wethen calculate the amount of time that has passed so that we can move appropriately. We do thisby calculating the difference between the current time and the starting time. Before moving thetext, we start timing again. In this way, if the condition is true and we re-execute the statementsin the body of the while, we can recalculate the time that has elapsed since the last move. Thistechnique can be applied to make the rate at which an animated object moves more uniform.

➟ Exercise 9.7.1

Modify the FallingBall class to make the motion of the falling ball more uniform. ❖

Page 19: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

244 Chapter 9 Active Objects

9.8 More Hints about Timing

Even if you were to run the movie credits with the improved, smoother motion of each Credit,you would likely see some odd behavior. While there would be space between the individualcredits, the space might not be completely even. In fact, some of the credits might overlap eachother slightly. As in the previous section, this is a result of our inability to have complete controlover the timing in our program.

Consider the following scenario. A single movie credit is constructed, and then ourCreditScroller pauses. We hope that this pause is long enough to allow the movie creditto move upward and out of the way before the next is generated. But suppose that it onlymoves a little bit before the CreditScroller constructs another line. How can we modifythe CreditScroller class to assure that each line of the credits will be a reasonable distancefrom the one before it?

There are several parts to the solution. First, before we generate a new line of the moviecredits, we can ask the previous one how far it has traveled upward. If the distance traveledis over a certain threshold, we can construct the new line. If not, we pause and then tryagain.

In order to ask a Credit how far it has traveled, we need to add a method to the definition ofthe Credit class. We will call that method distanceTraveled and write it as follows:

public double distanceTraveled() {return ( canvas.getHeight() - roleAndName.getY() );

}

Since we initially positioned the movie credit so that its top was at the bottom edge of thecanvas, we simply calculate the difference between the bottom of the canvas and the current ycoordinate of the credit. This additional method requires that we add canvas as an instancevariable.

Once we have this method, we can modify the run method of the CreditScroller class aswe described above. We put off constructing a new Credit as long as the one before it is stilltoo close. Our modifications are shown in Figure 9.16. We begin by declaring a local variable,lastCredit. We then construct the first movie credit. After doing so, we invoke a method thatwaits for the credit to get out of the way of the subsequent credit. The method consists of a whileloop that pauses as long as the last movie credit is too close.

In order to help us to keep our code clearer, we have written a private boolean method tooClosethat determines whether the last movie credit is too close. There we simply ask the last credithow far it has traveled and then determine whether that is less than the desired distance. This isslightly more complex than you might have imagined. The reason is that we want to assure thatthere is a certain gap between the bottom of one credit and the top of the next. To do this, we addto the desired gap size the height of the last credit. This requires the addition of another methodto the Credit class:

public double getHeight() {return roleAndName.getHeight();

}

Page 20: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

Section 9.9 Summary 245

public void run() {Credit lastCredit;lastCredit = new Credit( "Director . . . George Washington",

canvas );waitToScroll( lastCredit );lastCredit = new Credit( "Script . . . Thomas Jefferson",

canvas );waitToScroll( lastCredit );lastCredit = new Credit( "Producer . . . Martha Washington",

canvas );waitToScroll( lastCredit );lastCredit = new Credit( "Costumes . . . Betsy Ross", canvas );

}

private boolean tooClose ( Credit lastCredit, int desiredGap ) {return lastCredit.distanceTraveled () <

desiredGap + lastCredit.getHeight();}

private void waitToScroll( Credit aCredit ) {while ( tooClose( aCredit, GAP SIZE ) ) {

pause( DELAY TIME );}

}Figure 9.16 Guaranteeing minimum spacing between moving objects

➟ Exercise 9.8.1

Given the last modifications for CreditScroller, do you still expect the credits to be evenlyspaced? Why or why not? ❖

9.9 Summary

This chapter has focused on ActiveObjects. The main points that you should take from thischapter are:" Animation is a context in which while statements are used often." To define a class of animated objects we extend ActiveObject.

– Every class that extends ActiveObject must have a run method.– The run method is started by invoking start. This will typically happen as the last item

in the constructor.– The code that guides the animation should include pauses.

Page 21: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

246 Chapter 9 Active Objects

9.10 Chapter Review Problems

➟ Exercise 9.10.1

Consider the definition of a class, SlidingBox of ActiveObjects. A SlidingBox is arectangle that moves across the canvas from left to right. It should be removed when it goesacross the right edge of the canvas. Unfortunately, the class definition below has two errors.Please fix them.

public class SlidingBox extends ActiveObject {

... // Constant declarations omitted

private FilledRect box;

private DrawingCanvas canvas;

public SlidingBox( Location boxLocation, DrawingCanvas aCanvas ) {canvas = aCanvas;box = new FilledRect( boxLocation, BOXSIZE, BOXSIZE, canvas );run();

}

public void run() {if (box.getX() < canvas.getWidth() ) {

box.move( X SPEED, 0 );pause( DELAY TIME );

}box.removeFromCanvas();

}}

❖➟ Exercise 9.10.2

What are the four key steps when creating an ActiveObject? ❖

➟ Exercise 9.10.3

Suppose that FallingDroplet for RainCloud had the line

dropletGraphic.hide();

instead of

dropletGraphic.removeFromCanvas();

What this difference does this make? Why is it better to use removeFromCanvas? ❖

Page 22: Active Objects - Java: An Eventful Approacheventfuljava.cs.williams.edu/chapters/Bruce_chapter9.pdfCHAPTER 9 Active Objects I nChapter 7you learned about Java control constructs to

Section 9.11 Programming Problems 247

Figure 9.17 Run of HitTheTarget

9.11 Programming Problems

➟ Exercise 9.11.1

a. Revisit LightenUp from Chapter 2 and modify it so that now the sun rises slowly and thesky brightens as it rises. The sun should begin rising as soon as the program starts. The sunshould stop rising before it begins to exit the window.

b. Add methods so that the sun also stops rising if the mouse exits the window. ❖

➟ Exercise 9.11.2

Write a program that creates a ResizableBall centered around the point where the mouse isclicked. A ResizableBall is an animated ball that grows up to twice its original size and thenshrinks down to half its size before beginning to grow again. It continues to grow and shrink untilthe program is stopped. The center of the ResizeableBall should never move. ❖

➟ Exercise 9.11.3

Write a program called HitTheTarget with the following attributes (refer to Figure 9.17):" When the program begins, there should a be a target moving back and forth horizontallybetween the left and right edges of the canvas. This is depicted as the large oval in Figure 9.17." When the user clicks the mouse, a ball shooter is created at the bottom of the window andaligned with the mouse. The ball shooter will shoot three balls that move up the screen, oneafter another." If the user hits the target with a ball, the console should indicate this. ❖