API Drawing, Part 3: Cycling Graphics, or Screensavers

autoCircles.swf

Consider the lowly screensaver. Most of you will not remember when this sort of software was new, but there was a brief moment when screensavers like Flying Toasters actually led software sales nationwide. Now we consider cycling graphics (rightly) nothing more than a system utility; but that doesn't mean they aren't interesting to program, in their modest way.

This post discusses three examples, laying out a basic strategy for building a non-terminating loop in ActionScript 3.0, and some ways such a structure can be used with the Drawing API. Along the way, we'll introduce drawing with geometrical primitives other than the simple line.

I. Setting up a cycle: autoCircles

The demonstration project above, autoCircles, represents a very simple implementation of the screensaver design pattern. It draws 200 circles on the screen, then erases the drawing space, then returns to drawing circles. This action repeats infinitely.

Since this program runs automatically, without user input, we can return to the simpler setup for the Drawing API: we don't need a child MovieClip to receive click actions, or a Shape within that child to manage drawing. As in our very first example (the simple mouse tracer), we can use the main movie as our drawing space, installing our script in a document class.

In fact, this class (autoCirclesDocclass) is quite compact. Here are the contents of its package{} container:

import flash.display.MovieClip;
import flash.events.*
import flash.utils.Timer;
	
public class autoCirclesDocClass extends MovieClip
{
  public var myShape = new Shape();
  public var shapeTimer = new Timer(20,0);
		
  public function autoCirclesDocClass()
  {
    //draw on enterFrame
    shapeTimer.addEventListener(TimerEvent.TIMER, shapeUp);
    shapeTimer.start();
  }
		
  public function shapeUp(event:TimerEvent)
  {
    graphics.lineStyle(2+Math.random()*10, Math.random()*0xFFFFFF, 1);
    graphics.drawCircle(Math.random()*400,Math.random()*400,10+Math.random()*100);
	if(shapeTimer.currentCount%200 == 0) graphics.clear();
  }
}

The imports are straightforward. We include Timer because we'll be using that class to manage the slight delay between draw operations, keeping the visual action to a non-frightening pace. As you can see in the variable declarations, our instance of Timer, arbitrarily called shapeTimer, is set for 20 milliseconds (1/50 of a second), and for infinite repetition (that's what that zero in the second parameter means).

As we did the last time we worked with the Timer class, we add an event listener on our instance of the object, handing off to a callback method named shapeUp(). This method is triggered 50 times per second.

In the shapeUp() method, we call the lineStyle() method on the graphics property of the main movie, redefining width and color for the line we will be drawing with the API. Next we invoke the drawCircle() method of graphics. As you'll recall, this method draws a circle, given X and Y coordinates for a center point, as well as a radius. All three of these values are randomized.

autoShapes.swf

We add one more finishing touch. To keep the screen from entirely filling, we cause it to blank out briefly after 200 circles have been draw. We do this by applying the modulo operator (%) to the currentCount property of our Timer instance. This operation tells us if that value may be divided evenly by 200. This in turn tellus us if 200 repetitions have taken place since the last time we checked. If the answer is yes, we blank out the drawing space.

II. Variation: filled shapes

The second version of our screensaver project, autoShapes.swf, draws 200 filled shapes before erasing the screen and starting fresh. The code is identical to our first example, with a few changes to the shapeUp() callback method, which now looks like this:

 public function shapeUp(event:TimerEvent)
 {
  graphics.lineStyle(1,Math.random()*0xFFFFFF, 1);
  graphics.beginFill(Math.random()*0xFFFFFF, 0.5);
  var rX:Number = Math.random()*400;
  var rY:Number = Math.random()*400;
  var rDim:Number = 10+Math.random()*100;
			
   switch(Math.floor(Math.random()*4))
   {
      case 0: //circle
      graphics.drawCircle(rX, rY, rDim);
      break;
					
      case 1: //square
      graphics.drawRect(rX, rY, rDim, rDim);
      break;
					
      case 2: // ellipse
      graphics.drawEllipse(rX, rY, rDim, rDim/2);
      break;
					
      case 3: //triangle
      graphics.moveTo(rX, rY);
      graphics.lineTo(rX+rDim, rX+rDim);
      graphics.lineTo(rX-rDim, rX+rDim);
      graphics.lineTo(rX, rY);
  }
  
  if(shapeTimer.currentCount%200 == 0) graphics.clear();
  
}

There are several additions. At the top, we've added a beginFill() method, which does something similar to the lineStyle() method, only for the filled area of circles, ellipses, and polygons. We keep the lineStyle() method, with the same randomized values as before, so that we'll have distinct random selections for outline and filled area.

Next, we choose a random number between 0 and 3, and use that number as the index of a switch() statement, which, you might remember, is a control structure that lets us choose one of several branches on each pass through the loop. Each option or case) represents one of four geometrical shapes: circle, square, ellipse, or triangle.

lineCrawl.swf

the first three of these are drawn with predefined methods of the Graphics object. The third is custom-programmed, using the now-familiar lineTo() method. We create three lines from the given start point, such that they define an equilateral triangle. Since we've set up a fill color, these lines automatically fill as soon as the figure is closed, as the third line returns to the starting point.

You can adapt this method to draw other sorts of polygons.

The final line of the shapeUp() method is the same as in the earlier example: we test to see if our timer loop has run 200 iterations; if so, we blank the screen.

III. Third variation: variable lines in sequence

Finally, here's a third variation, lineCrawl.swf. In this example, our infinite loop draws straight lines from each of the four edges of the screen, proceeding in sequence from the left edge, and running around the screen until we come back to the top left, at which point we clear the display and start over.

This action is controlled by a callback named LineUp, which looks like this:

public function lineUp(event:TimerEvent)
{
  theColor = Math.random()*0xFFFFFF;
  graphics.lineStyle(5+Math.random()*5,theColor,0.5);

  graphics.moveTo(rX, rY);
			
  switch(crawlMode)
  {
    case 0:  //top to bottom, left side
      graphics.lineTo(20+Math.random()*300, rY);
      rY += 10;
      graphics.moveTo(0, rY);
      break;
					
    case 1: //right to left along bottom
      graphics.lineTo(rX, 20+Math.random()*300);
      rX += 10;
      graphics.moveTo(rX,0);
      break;
				
    case 2: //bottom to top, right side
      graphics.lineTo(400 - Math.random()*300, rY);
      rY -= 10;
      graphics.moveTo(400,rY);
      break;
					
    case 3: //right to left along top
      graphics.lineTo(rX, 20+Math.random()*300);
      rX -= 10;
      graphics.moveTo(rX,0);
      break;
    }
			
    theCount ++;
	
    if(theCount == 40) //switch to left to right, bottom
    {
      rY = 400;
      crawlMode = 1;
    }
	
    if(theCount == 80) //switch to bottom to top, right
    {
      rX = 400;
      rY = 400;
      crawlMode = 2;
    }
	
    if(theCount == 120) //switch to right to left, top
    {
      rY = 0;
      rX = 400;
      crawlMode = 3;
    }
	
    if(theCount == 160) //switch to top to bottom, left; go round again
    {
      rX = 0;
      crawlMode = 0;
      graphics.clear();
    }
		
  }

This method depends on several control variables. The variable crawlMode is an integer between 0 and 3, indicating on which edge we are currently drawing: 0 for left, 1 for bottom, 2 for right, and 3 for top. The variable theCount tells us where we are in the loop. (Since we need four distinct values for theCount, it's easier to use a separate variable here than to apply modulus tests to the currentCount property of our Timer.) Finally, we have rX and rY, which indicate points where drawing will either begin or end.

The switch() structure lets us tailor drawing to the edge in question. When drawMode is zero (left edge), we draw our line (with randomized width and color) from the left edge (rX = 0) to a point randomly chosen somewhere to the right. The vertical coordinate for both these points (rY) is zero when we draw our first line, and increases by 10 on each pass.

Meanwhile, theCount goes up each time we draw a line. When it reaches 40 (that is, when we've drawn one line for every 10 pixels along the left edge of our 400x400 screen), we change drawMode to 1, meaning that we will now draw along the bottom edge of the screen. Accordingly, we also change rY to 400, where it will stay for a bit, and set rX to zero.

We then proceed, adding 10 to rY on each pass, and drawing to a randomly chosen point somewhere higher on the screen.

Much the same happens for the other two edges, with the changeover from bottom to right happening when theCount reaches 80, and from right to top when theCount reaches 120. For right and top, we change our sequential term (rY, then rX) by -10 instead of 10, since we're moving up and then leftward along the respective edges.

When theCount comes to 160, we've traveled all the way around our 400x400 screen, and so set everything back to its initial state, including theCount. Since our loop continues to operate indefinitely, we just blank the screen, and let things march.

As you can see, this third example is a bit different from the previous two, in that it contains four distinct drawing regimes, but it retains a basic resemblance, and is powered by the same infinite loop.

IV. Source files

The source files for the three demonstrations in this posting are all available in the screensavers subdirectory, within APIDrawing, within MULTIMEDIA in the shared account on student-iat.ubalt.edu. There is a main movie and a document class (logicall named) for each of the three projects, autoCircles, autoShapes, and lineCrawl.



University of Baltimore Logo

Last updated: 04/18/08 15:19:51
Copyright © 2008 School of Information Arts and Technologies