How to Constrain Dragging to a Circle

ActionScript 2.0

Garden variety movie clip dragging is easy to handle via the MovieClip.startDrag() and MovieClip.stopDrag() methods.  In fact, it’s even easy to constrain dragging to an arbitrary rectangular area.  Just invoke these methods when handling the clip’s MovieClip.onPress and MovieClip.onRelease events.

// Standard dragging
mc.onPress = function() {
  this.startDrag();
}
mc.onRelease = function() {
  this.stopDrag();
}

// Constrained dragging
mc.onPress = function() {
  this.startDrag(true, 0, 0, 200, 100);
}
mc.onRelease = function() {
  this.stopDrag();
}

In that second example, the MovieClip.startDrag() method is supplied with optional arguments.  The first, a Boolean (true/false) determines whether the clip is dragged from its center or from the mouse’s position when the mouse first clicked.  The rest refer to coordinates of the clip’s parent.  If this clip is situated in the main timeline, the parent would be the main timeline.  In this case, the movie clip will only be draggable within a 200×100 pixel rectangle.

So, what if you want to constrain dragging to a circle? 

An answer, short and sweet

To accomplish circular constraint, you’ll need to forego the built-in dragging methods.  Assuming an instance name of mc (as above), past the following into a frame of your scripts layer.

mc.origX = mc._x;
mc.origY = mc._y;
mc.onPress = function() {
  this.onMouseMove = function() {
    var angle:Number = Math.atan2(
      _root._ymouse - this.origY,
      _root._xmouse - this.origX
    );
    var distance:Number = Math.sqrt(
      Math.pow(_root._xmouse - this.origX, 2) +
      Math.pow(_root._ymouse - this.origY, 2)
    );
    if (Math.ceil(distance) >= 100) {
      this._x = this.origX + (Math.cos(angle) * 100);
      this._y = this.origY + (Math.sin(angle) * 100);
    } else {
      this._x = _root._xmouse;
      this._y = _root._ymouse;
    }
  };
};
mc.onRelease = function() {
  delete this.onMouseMove;
};
mc.onReleaseOutside = mc.onRelease;

How it works

The first two lines take advantage of the fact that the MovieClip class is dynamic, which means it may have new properties added to it at runtime.  Two arbitrarily named properties, origX and origY, are set to the movie clip’s current position.  This is just a way to keep track of where the clip was originally positioned — this location will be the center point of the circle constraint.

Next, a function literal is assigned to the MovieClip.onPress event of the mc movie clip.  We’ll come back to that in a moment.  Skip to the end, for now, and notice that a function literal is also assigned to the MovieClip.onRelease event.  This essentially cancels the function assigned to the onPress.  So, press to do some dragging (explained in a bit) and release to stop.  While we’re at it, set MovieClip.onReleaseOutside to the same thing as the release handler — after all, this movie clip’s movement is going to be constrained, so the mouse will not always be over it when the mouse lets go (we still want to cancel the dragging when the user lets go, right?).

Now, the meat and potatoes.

The function assigned to the MovieClip.onPress event does the following:  it, in turn, assigns a function literal to the MovieClip.onMouseMove event of the same movie clip (via the global this property).  At this point, whenever the mouse moves, the following ActionScript is performed.

First, an arbitrary variable, angle, is recorded.  By way of the Math.atan2() method, angle is set to the angle, in radians, of the mouse’s location as it pivots around the original location of mc.  (Note:  read Is _root Evil? if you’re worried about using _root.  In my opinion, this is a perfectly valid use for it.)

Second, an arbitrary variable, distance, is recorded.  This makes use of the Pythagorean theorem (a2 + b2 = c2) to note the distance between the mouse’s location and the original location of mc.

Finally, angle and distance (same as radius, in this context) are used in the standard formula for describing a circle …

x position = cosine of angle times radius
y position = sine of angle times radius

If the distance of the mouse from mc’s original location is equal to or greater than 100 (just an arbitrary number), then the mouse is outside the imaginary circle.  (Math.ceil() is used to round this number up, because it is mostly likely going to be a lengthy decimal number.)  If the mouse is outside that circle, it means we need to position mc right on that circle.  This is accomplished with these two lines:

this._x = this.origX + (Math.cos(angle) * 100);
this._y = this.origY + (Math.sin(angle) * 100);

… in which this still refers to mc.  So mc’s MovieClip._x property is set to whatever it’s original location was plus the cosine formula mentioned above, using the current angle and the same arbitrary 100 pixel radius.  Same goes for the y position.

On the other hand, if the distance between the mouse and mc’s original location is less than 100, simply set mc’s position to the mouse’s position.

this._x = _root._xmouse;
this._y = _root._ymouse;

Leave a Reply