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;

14 Responses to “How to Constrain Dragging to a Circle”

  1. auit Says:

    I’m auit. Thanks for ur answer my ques in Adobe’s forum. I followed ur hint there but I got the same result when I used the old code.
    I still have not solved the problem -_-’
    Any advice for me?

  2. David Stiller Says:

    Hi, auit,

    I don’t recognize your name, but I think you might be the person who had a number of questions about dragging objects into a glass jar, right? Or maybe that wasn’t you.

    If it was, the hint I was talking about is that you don’t have to use MovieClip.startDrag() to accomplish dragging. Instead, you can use a recurring loop, such as MovieClip.onEnterFrame, to continually position the clip where the mouse is. If you use if statements, like above, you have much more control over when the clip should drag and when it should stop.

    If that wasn’t you, then I’m afraid I’ll need more detail from you, because I don’t know what your problem is.

  3. auit Says:

    Me here [link to Adobe ActionScript forum]

  4. David Stiller Says:

    Aha, now I remember! You’re the person who wants to drag clock hands. :) Okay, in that case, the above code sample is still useful, but different enough that I wouldn’t expect you to be able to figure it out on your own. Check out this new article, How to Drag Clock Hands in a Circle.

  5. Philip Johnston Says:

    First off, thank you for all your articles. I’ve bookmarked your blog in my Flash resources and look forward to future posts.

    Right now, I’m trying to do something that relates to this post, but I can’t quite figure out if the script in your post can solve my problem, or if I need to do something else.

    I have a little scroll wheel that the user can click and “drag” in a circle. It works great, except I’d like it if the user couldn’t move his mouse futher from or closer to the center of the circle while he’s pressing the mouse. Imagine if you have one hand on a steering wheel and are turning it - you’re hand stays on the same place on the wheel. That’s what I want.

    I suppose to do this, you’d find the distance between the mouse and the center of the circle, when the user clicks, and then somehow constrain the mouse to this distance, while still allowing it to move around the circle. I just don’t know how to make that happen.

    Here is the SWF: http://www.newthinkmedia.com/scroll_wheel.swf
    And the AS: http://www.newthinkmedia.com/circle_as.txt

    I downloaded this FLA from someone else and modified it slightly - that’s why I can’t quite figure this out. I suppose if I wrote it to begin with, this it would be pretty easy for me and I wouldn’t get a little dizzy every time I try to work it out.

    This is where I got the original FLA

    Thanks for any help!
    Philip

  6. Karl Says:

    Hey David,
    I’m the guy with the glass jar, well, cylinder actually…anyway, how would this work if the object did not start in the center of the bounding box, circle, ellipse, etc.?
    thanks,
    Karl

  7. David Stiller Says:

    Philip,

    If I understand what you’re saying, you’d like to know how to contrain the mouse rather than what the mouse is dragging — actually, in addition to what the mouse is dragging. If that’s it, I’m afraid you’re out of luck. The Flash Player has very little control over the mouse itself. ActionScript can understand where the mouse is, and even hide the mouse altogether, but it cannot move or constrain the mouse. Notice that in my examples, the mouse moves wherever the users puts it: only the movie clip is constrained.

    That said, you can simulate mouse constraint. The trick there is to draw a movie clip that looks like the mouse arrow, hide the actual mouse arrow (see Mouse.hide()), and constantly position the arrow movie clip where the mouse is. Other dragging will have to be done in addition to that. Make sense?

    I suppose to do this, you’d find the distance between the mouse and the center of the circle, when the user clicks, and then somehow constrain the mouse to this distance, while still allowing it to move around the circle. I just don’t know how to make that happen.

    Although you can’t constrain the mouse itself, as discussed, that part about finding the distance between the mouse and the center of the circle is already spelled out in the above code (see the formula that sets the value of distance). :) You may also want to check out How to Drag Clock Hands in a Circle, which may be a little closer to the kind of dragging you’re after — though my article doesn’t include the secondary horizontal slider at the top of yours.

  8. David Stiller Says:

    Karl,

    how would this work if the object did not start in the center of the bounding box, circle, ellipse, etc.?

    The center point is completely arbitrary. In my example, I set the origX and origY properties of mc to mc’s original location …

    mc.origX = mc._x;
    mc.origY = mc._y;

    … but you can set those properties to whatever you like. Whatever they are, those values will be the center point of the circle or ellipse. In fact, they don’t even have to be dynamic properties of that movie clip: they can by variables, if you like; I just happened to find it convenient to refer to them via this in the above code.

  9. Jim Maskus Says:

    Thanks David

    I found this technique useful for applying any type of behavioral logic while dragging. ie scrubbing to a new frame in a movieClip while dragging a slider thumb for example.

  10. David Stiller Says:

    Jim,

    Glad to hear it. :)

  11. amir Says:

    hi there,

    your solution on using startDrag is perfect…but i’m trying to do more startDrag() within an angle but in a straight line let say 36 degree angle

    i’m trying to create a fixed movieclip resizer…i got the idea to actually detect the angle between the resizer_mc and the movieclip that i want to resize

    can u help me?

  12. David Stiller Says:

    amir,

    It sounds like you’re hoping to constrain dragging to a pie shape. Here’s a quick bit a sample code that should at least get you started. This series of if/if..else statements replaces the existing if statement in the original blog entry:

    if (angle < 0) {
      this._x = this.origX + (Math.cos(0) * distance);
      this._y = this.origY + (Math.sin(0) * distance);
    } else if (angle > 0.62) {
      this._x = this.origX + (Math.cos(0.62) * distance);
      this._y = this.origY + (Math.sin(0.62) * distance);
    } else {
      this._x = _root._xmouse;
      this._y = _root._ymouse;
    }

    So … whereas the original ActionScript cared about distance, this revision cares about angle. 36 degrees in radians is approximately 0.62, which is why that number is the one shown. In the above, zero points to the right, and 0.62 radians also points to the right, but roughly 36 degrees below due east to an nearly southeast. If the mouse happens to be higher than that (angle < 0), the movie clip is positioned specifically at that angle (zero), at the distance determined by the distance variable. In like fashion, if the mouse happens to be lower (angle &rt; 0.68), the movie clip is positioned right at 0.68 radians at the same distance variable. Otherwise, the movie clip is put wherever the mouse is.

    The challenge, here, is how to handle angles at other … angles. ;) The if statements may need to be very much more flexible than the current hard coded 0/0.68 range.

  13. amir Says:

    wow never thought of that approach. now its more clear to me how this pie thingy works.

    thanks david you’re a life saver :D

  14. David Stiller Says:

    amir,

    Glad to hear it. :)

Leave a Reply