Proximity Effects

These demonstrations introduce a simple but powerful design technique: using triangulation to compute the distance between two objects. If you compute this value on the fly, in an ENTER_FRAME callback, you can create systems that respond not just to on/off, binary events like collision or mouse click, but to proximity, or approach and retreat. You can thus make digital systems imitate analog behavior, with gradations, variations, and scalar changes, rather than binary switches. Our clearest example of this concept will come in the final lab, where we'll connect proximity to sound effects. But first, we need to lay some groundwork.

I. Triangulation

The demonstration above illustrates the basic technique, using the Flash Drawing API, to which we'll come in future weeks, to show what the code is doing.

The value in the text field at right is the pixel distance from the mouse pointer to the red reference object in the lower left corner: in other words, it is the length of the line shown in red.

As the other two (gray) lines indicate, this line is the hypotenuse of a right triangle. This should arouse some dim memories of your school days, and a semi-mythical fellow called Pythagoras, who discovered that for any right triangle, the square of the length of the side opposite the right angle, called the hypotenuse, is equal to the sum of the squares of the other two sides: A2 + B2 = C2. We use this grand, old theorem in the document class for our demonstration, whose package{} contents look like this:

  import flash.display.MovieClip;
  import flash.events.Event;
  import flash.text.TextField;
	
  public class simpleDocClass extends MovieClip
  {
    var XSide:Number;
    var YSide:Number;
    var dist:Number;
		
    function simpleDocClass()
    {
      addEventListener(Event.ENTER_FRAME, textUp);
    }
		
    function textUp(event:Event):void
    {
      XSide = reference.x - MovieClip(root).mouseX;
      YSide = reference.y - MovieClip(root).mouseY;
      dist = Math.sqrt(XSide*XSide + YSide*YSide);
      readout.text = String(dist);
    }	
  }

For the sake of simplicity, we've omitted the code bits involved with the Drawing API, so if you run this code at home, you won't see the triangle, just the numeric readout on the length of the hypotenuse.

As you can see, we start with three variables, XSide, YSide, and dist. These correspond to the three legs of our triangle, with dist being the hypotenuse. Values for these variables are computed in the ENTER_FRAME callback, textUp().

We derive the length of the first two legs by subtracting the x and y coordinates of our reference object from, respectively, the x and y coordinates of the mouse pointer. These values are expressed in the system variables mouseX and mouseY. (The Flash Player always knows where the mouse pointer is.)

Some important notes here. First, you may be wondering why we do the subtraction in the order shown above. Why not do it the other way around, subtracting the mouse x from the reference x, and so on?

You could do it that way. Results would be identical, because we're going to square the value in question. Let's say our reference x is 50, and our mouse x is 25. By the first method (reference minus mouse), we get 25. By the second method (mouse minus reference), we get -25. Multiply either number by itself and you get 625, since two negative numbers multiplied yield a positive number.

Now for a non-mathematical note. In our code here, we invoke the mouse coordinates as MovieClip(root).mouseX and MovieClip(root).mouseY, using our time-tested root-reference convention.

If you're paying close attention, you'll see that this is a bit silly. The script above belongs to a document class; therefore it is at the root position already. So why did we put on a belt with our suspenders?

Answer: Some day, you may want to write proximity-sensing code for an object that is not in root position. In that case, you MUST obtain the mouse coordinates by referencing the root. If you forget, and simply use mouseX and mouseY without prefix, you'll get values for these variables, but they will be calculated using the internal geometry of your MovieClip, not the geometry of the main movie. Therefore your calculations will all be skewed, and you'll shed bitter tears.

So it's safer simply to learn always to invoke mouse coordinates as MovieClip(root).mouseX and MovieClip(root).mouseY.

There's only one more thing to explain here, namely how we derive our crucial value for dist. As you know from basic algebra, we can solve any three-value equation if we have two values. In our case, by taking the square root of the sum of the two sides, squared, we arrive at the length of the hypotenuse. To calculate a square root, we use the sqrt() method of the Math object, which is built into ActionScript, and so does not have to be imported as a special package.

II. Proximity and scaling

This second example uses our triangulation technique for an interesting visual effect. Mouse around in the movie window. The colored discs expand and contract as your mouse passes over them, growing and shrinking as the mouse approaches and retreats from the center of each disc. Note that multiple objects respond simultaneously, each one doing its onw proximity calculation, and adjusting its dimensions accordingly.

In this case, we're working with a class script called discClass, linked to the Library Symbol that contains the disc shape. (We've added the colors via Tint effects in the Flash editor.)

Here are the package contents of the class:

  import flash.display.MovieClip;
  import flash.events.Event;
	
  public class discClass extends MovieClip
  {
    var XSide:Number;
    var YSide:Number;
    var dist:Number;
		
    function discClass()
    {
      addEventListener(Event.ENTER_FRAME, reScale);
    }
		
    function reScale(event:Event):void
    {
      XSide = x - MovieClip(root).mouseX;
      YSide = y - MovieClip(root).mouseY;
      dist = Math.sqrt(XSide*XSide + YSide*YSide);
			
      if(dist < 101)
      {
        scaleX = (110 - dist)/10;
      }
      else
      {
        scaleX = 1;
      }
      scaleY = scaleX;
    }
  }

The Pythagorean bits of this code are the same as in the first example. Again, note that we must obtain the mouse coordinates by reference to the root: this time, the trick won't work as advertised if we forget this.

We link the object's scale to its distance from the mouse in the reScale() callback. In order to make the math a bit more convenient, we arbitrarily decide that nothing will happen until the mouse pointer comes within 100 pixels of our disc. That gives us a neat range of 0-100 for dist. (You could make different decisions about the range of your effect.)

If the mouse has come within 100 pixels, we set the horizontal scale of the disc (scaleX) by subtracting dist from 110, then dividing the result by 10. To see what this does, consider the extremes of the range. When we're at the outside edge of the range, at 100 pixels, we have 110-100, or 10, divided by 10, which is 1. Since ActionScript 3 expresses 100% scale as a value of 1, this is full, normal size. At the inside of the range, where we're zero pixels from the disc's registration point, our caculation gives us 110 minus zero, divided by ten, or 11 times the original size -- as you can see, those discs get quite big as you roll over.

Now, you may be wondering about a few things here. First, why do we substract distance from some arbitrary number? Answer: because we want the scale change to work in inverse proportion to distance, growing larger as distance decreases. No subtraction is necessary if you want scale to work the other way, decreasing as distance increases.

Why do we set our arbitrary value at 110, instead of 100? Answer: starting at 110 gives us a value of 1 (identity) at the outside of the range. If we don't slip in the extra 10, our disc will shrink awkwardly when we first come within range. (Modify the code yourself and see.)

Finally, why do we need to divide by 10? Answer: because scaling in ActionScript 3 works by tenths, not integer values, as in earlier versions of the language. Actually, you can dispense with the division, but you'll end up filling the screen with your rescaled disc.

Note that we do our scale change as part of an if/else structure. If the mouse is not within the 100-pixel radius, we set scale back to 100% (or 1). If we don't do this, our objects will never return to their original proportions.

Finally, notice that we set scaleY to scaleX outside the if/else condition that applies our 100-pixel range. We can do this because we are working with symmetrical objects whose X and Y scales are identical. This instruction is outside the if/else because we need it to apply whether we're in scaling range or not.

If you look at the example in operation, you'll see that it changes the alpha of the disc as well as the scale. We've omitted the lines that handle alpha changes from our code, for the sake of simplicity. See if you can figure them out on your own.

III. Source files

Source files for both demos in this posting can be found in the directory proximity, within MULTIMEDIA in the shared account on student-iat. The first project comprises simpleTri.fla and simpleTriDocClass.as; the second comprises scaling.fla and discClass.as.



University of Baltimore Logo

Last updated: 04/06/08 16:00:16
Copyright © 2008 School of Information Arts and Technologies