API Drawing, Part 2: Interactive Drawing
Click and drag to draw lines.
Here's the second of our API Drawing demonstrations. This one lets you draw line segments by holding down the mouse button (clicking and dragging). When you release the button, drawing stops.
You'd think we could simply modify the basic code in the previous example by adding mouse event handlers. Unfortunately, there's a bit more involved, as you'll see.
I. Architecture
For starters, we have to abandon our document-class approach. For some reason, it is not possible to script the main movie for mouse events, even though it is a MovieClip. We have to create a MovieClip within the main movie. We could do this through a document class, but for simplicity's sake, we've built the new object into the Library and Timeline.
Our main movie, clickLine.fla, contains one frame in one layer. That frame contains an instance of drawObject_mc, defined in the Library and linked to drawClass.as.
Remember, as we go, that this setup is more complex than what we used for the initial demonstration.
II. drawClass
Our class file begins with three imports: flash.display.MovieClip, flash.events.*, and flash.display.Shape.
The first two should be entirely familiar. The third is new. A Shape is a type of DisplayObject, and thus has a graphics property, just as a MovieClip does.
You may be wondering why we need to bring in another DisplayObject; and why we use a Shape, isntead of a subsidiary MovieClip.
We wonder, too, but here are some answers, for what they're worth. First, we need another DisplayObject because scripting the graphics property of the MovieClip doesn't work. We're not sure why it doesn't work, when the same procedure does work for the main movie; but it doesn't. We use a Shape because that's how API drawing is explained in the Flash documentation, and in all reference books we've checked. This is probably because a Shape is less complicated than a MovieClip, and thus a lighter-weight object.
Now on to variables. We declare two:
public var myShape = new Shape(); public var drawFlag:Boolean = false;
The first gives a name (and an instance) to our Shape. The second sets up a Boolean flag, which as you can probably guess, we'll use to control the off/on state of drawing.
So we come to the Constructor:
public function drawClass()
{
addEventListener(Event.ENTER_FRAME, lineOut);
addEventListener(MouseEvent.MOUSE_DOWN, yesDraw);
addEventListener(MouseEvent.MOUSE_UP, noDraw);
}
Again, this code should look familiar from other projects (interactive sound, for instance), where we've handled binary states for some function. We have a listener/callback for MOUSE_DOWN, to start things happening, a listener/callback for MOUSE_UP, to turn them off, and an ENTER_FRAME mechanism to do the drawing business when it's called for.
The on-off callbacks are relatively simple:
public function noDraw(Event:MouseEvent):void
{
drawFlag = false;
}
public function yesDraw(Event:MouseEvent):void
{
drawFlag = true;
myShape.graphics.moveTo(MovieClip(root).mouseX, MovieClip(root).mouseY);
myShape.graphics.lineStyle(6, Math.random()*0xFFFFFF, 1);
}
The noDraw() method simply sets our flag variable to false.
Its opposite number, yesDraw(), sets the flag the other way, then executes moveTo() and lineStyle() on the graphics property of ourShape (myShape). Except for the complexit of having to work through a subsidiary Shape, these lines are exactly as you saw them in our initial example. They move the drawing point to the mouse position and set the properties of the line we intend to draw. As before, we use a random color.
The heart of the matter is contained in lineOut(), the callback for the ENTER_FRAME event:
public function lineOut(event:Event):void
{
if(drawFlag)
{
myShape.graphics.lineTo(MovieClip(root).mouseX, MovieClip(root).mouseY);
addChild(myShape);
}
The lineTo() method should look very familiar. You've also seen the addChild() method, though not in connection to API drawing. As you may remember, this method adds the indicated DisplayObject (in this case, our Shape) to the Display List of the class (the MovieClip drawObject_mc) to which the code belongs (also know as this). We need to perform this instruction in order to make what we've just drawn visible.
Now, you may be wondering why we repeatedly need to add our child Shape to the Display List, and what consequences follow from that action. We need to add repeatedly because we change the information contained by myShape every time we draw a new line.
So why don't our previous line segments disappear when we repeatedly add myShape? Answer: the Drawing API maintains all the pixels added to a Graphics object, until a specific method called clear() is executed.
Click and drag to draw circles.
So we keep thing around, which is good for our purposes here; but by constantly adding to the Display List, won't we end up with a dangerously large number of copies?
Happily, the answer to this one is a clear NO. Unless you create a new instance of the object you're adding, addChild() simply replaces the previous instance of the object previously added to the list. That is, we simply push an updated version of myShape into the same slot its earlier version previously inhabited.
You do want to be careful about addChild(). As we said, adding an object that has been re-instantiated, using the keyword new, does increase the size of the DisplayList, and can thus make your Flash movie a memory glutton. Generally speaking, you should instantiate objects only once, at the start of your script.
III. Variation: circles
While it may look very different in function, the version above right contains only a slight modification of the first example. Its lineOut() method has been rewritten as follows:
public function lineOut(event:Event):void
{
if(drawFlag)
{
myShape.graphics.lineStyle(2+Math.random()*10,Math.random()*0xFFFFFF, 1);
myShape.graphics.drawCircle(MovieClip(root).mouseX, MovieClip(root).mouseY, 10+Math.random()*100);
addChild(myShape);
}
}
As in our very first demo (the automatic tracer), we've moved the lineStyle() method into the enter-frame callback, so the color and width of the line changes constantly. That contributes to the psychedelic effect.
Instead of lineTo(), we're using the drawCircle() method of the Graphics class. This method requires three parameters, two to define a center point, and one to define a radius (half the diameter of the circle). The first two we get from the mouse position, the last from a computation that includes a random factor. The result is a circle of unpredictable size, width, and color. If you hold the mouse down without moving, you'll see a sort of groovy mandala thing. Move the mouse to do... art?
This revised, circle-drawing version has one feature not included in the first example: when the mouse is pressed, all previous pixels are erased from the screen. That's because our mouse-down callback includes the statement:
myShape.graphics.clear();
Use this method whenever you want a clean screen for your drawing operations.
IV. Source files
Source files for both demonstrations in this posting can be found in the directory APIDrawing/interactive, within MULTIMEDIA in the shared account on student-iat. Main movie for the first project is called clickLine.fla, and its associated class file is drawClass.as. For the second project, the files are drawCircles.fla and drawCirclesClass.as.
|
|