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;

22 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. :)

  15. Sean Shomes Says:

    This works really great! However, I’m trying to convert this to AS3. Having trouble assigning the listeners within the “on pressed” function, now that .onPress and .onMouseMove are gone

  16. David Stiller Says:

    Sean,

    Event handling is certainly more wordy in AS3, that’s for sure. In principle, though, you’re doing the same thing you did before — just with different syntax. You’re still associating a function with an event, but in AS3, you use the EventDispatcher.addEventListener() method, which is inherited by movie clips and plenty of other AS3 entities. Let’s step through the conversion (assuming the movie clip’s instance name is still mc):

    mc.buttonMode = true;
    mc.origX = mc.x;
    mc.origY = mc.y;

    The only real difference here is that you’re setting the Sprite.buttonMode property of this movie clip to true, so that you get the finger cursor when you mouse over the clip (in AS3, movie clips inherit functionality from the Sprite class). You’ll also notice that the formerly _x and _y properties have lost their underscores.

    Here are the first two event handlers, which kick everything off:

    mc.addEventListener(MouseEvent.MOUSE_DOWN, dragHandler);
    mc.addEventListener(MouseEvent.MOUSE_UP, dragStopHandler);
    

    This isn’t so different from the AS2 approach; it’s just that instead of referencing the object (mc) and then associating a function directly to one of the object’s events …

    mc.onPress = function() { ... };

    … you’re using that addEventListener() method as an intermediary. In addition, we’re using named functions now, instead of function literals, as shown in the above tutorial. The main reason we need named functions now is because of how AS3 unhooks functions when they’re no longer needed. In AS2, you could just set the event reference in question to null; here, we’ll be using the removeEventListener() method, which needs a name by which to reference the function in question.

    Bear in mind, AS2 could use named functions too. It is, in fact, arguably a better practice to give your functions names, in case you need to reference them again later.

    Here’s how the AS2 version would look with a named function:

    // original
    mc.onPress = function():Void {
      this.onMouseMove = function():Void {
        var angle:Number = Math.atan2(
        ...
    
    // named
    mc.onPress = dragHandler;
    
    function dragHandler():Void {
      this.onMouseMove = moveHandler;
    };
    
    function moveHandler():Void {
        var angle:Number = Math.atan2(
        ...
    

    Please note, these are arbitrary names. You could call your function strudelKicker() if you wanted to. Note, too, that I’m typing my functions with the :Void suffix, which simply lets Flash know that these functions don’t return any values. This sort of post colon suffix is optional in AS1, AS2, and AS3, but it has certain benefits, including code hinting, compile-time error notification, and — in AS3 — even runtime error notification and improved RAM usage. (Note that :Void becomes :void in AS3.)

    So, to get back on track, you’ll need the named functions in AS3 because removeEventListener() needs function names. Here are the arbitrarily named functions noted in the event listener hook-ups so far:

    function dragHandler(evt:MouseEvent):void {
      stage.addEventListener(MouseEvent.MOUSE_UP, dragStopHandler);
      mc.addEventListener(MouseEvent.MOUSE_MOVE, moveHandler);
    };
    
    function dragStopHandler(evt:MouseEvent):void {
      stage.removeEventListener(MouseEvent.MOUSE_UP, dragStopHandler);
      mc.removeEventListener(MouseEvent.MOUSE_MOVE, moveHandler);
    };

    [Note: See additional comments between Sean and me, following this reply, for a reason to replace mc with stage in two places.]

    When the mouse is pressed (MOUSE_DOWN), the Stage will get a MOUSE_UP listener, because none of AS3’s classes has an onReleaseOutside event. When the Stage hears a mouse release, that’ll accomplish the same thing here as an onReleaseOutside would have done in AS2.

    Other than that, when mc is pressed, it’ll be assigned a MOUSE_MOVE handler, just like in the AS2 version. When the mouse is released — either over mc or the Stage — that MOUSE_MOVE handler will be removed.

    Finally, the brains of the outfit, which is largely the same as it was before:

    function moveHandler(evt:MouseEvent):void {
      var angle:Number = Math.atan2(
        stage.mouseY - mc.origY,
        stage.mouseX - mc.origX
      );
      var distance:Number = Math.sqrt(
        Math.pow(stage.mouseX - mc.origX, 2) +
        Math.pow(stage.mouseY - mc.origY, 2)
      );
      if (Math.ceil(distance) >= 100) {
        mc.x = mc.origX + (Math.cos(angle) * 100);
        mc.y = mc.origY + (Math.sin(angle) * 100);
      } else {
        mc.x = stage.mouseX;
        mc.y = stage.mouseY;
      }
    };

    Look over it carefully, and you’ll see that only minor things have changed, such as a reference to the Stage rather than _root, mouseX instead of _xmouse, and the like.

    Hope this helps! Here it is again, all in one shot:

    mc.buttonMode = true;
    mc.origX = mc.x;
    mc.origY = mc.y;
    
    mc.addEventListener(MouseEvent.MOUSE_DOWN, dragHandler);
    mc.addEventListener(MouseEvent.MOUSE_UP, dragStopHandler);
    
    function dragHandler(evt:MouseEvent):void {
      stage.addEventListener(MouseEvent.MOUSE_UP, dragStopHandler);
      mc.addEventListener(MouseEvent.MOUSE_MOVE, moveHandler);
    };
    
    function dragStopHandler(evt:MouseEvent):void {
      stage.removeEventListener(MouseEvent.MOUSE_UP, dragStopHandler);
      mc.removeEventListener(MouseEvent.MOUSE_MOVE, moveHandler);
    };
    
    function moveHandler(evt:MouseEvent):void {
      var angle:Number = Math.atan2(
        stage.mouseY - mc.origY,
        stage.mouseX - mc.origX
      );
      var distance:Number = Math.sqrt(
        Math.pow(stage.mouseX - mc.origX, 2) +
        Math.pow(stage.mouseY - mc.origY, 2)
      );
      if (Math.ceil(distance) >= 100) {
        mc.x = mc.origX + (Math.cos(angle) * 100);
        mc.y = mc.origY + (Math.sin(angle) * 100);
      } else {
        mc.x = stage.mouseX;
        mc.y = stage.mouseY;
      }
    };

    [Note: See additional comments between Sean and me, following this reply, for a reason to replace mc with stage in two places.]

  17. Sean Shomes Says:

    Wow dude. That was amazingly fast! I’m going to go through it now, but I’m already noticing my problem in conversion. I was thrown off by the AS2: mc.origX. I was trying to set a new variable: var mcOrigX:Number = mc.x; which I now see is incorrect. Thanks again.

  18. David Stiller Says:

    Sean,

    Heh, totally luck of the draw. I’ve been behind on my blog and on the Adobe forums for a few months. I do answer when I can — ASAP when I can — but sometimes I go for insanely long stretches!

    In both AS2 and AS3, the MovieClip class is a dynamic class, which means it can have properties added to it at runtime. These origX and origY variables are properties in the sense that they’re being assigned to an object instance (a MovieClip instance) at runtime, but in practice, they’re no different from timeline variables (the sort you declare with var in keyframe code. But you can only type variables, not properties. So you could do this:

    var mcOrigX:Number = some number

    but when that variable is a property, no matter what version of ActionScript, it can only be this:

    someObjectReference.mcOrigX = some number
  19. Sean Shomes Says:

    Hmm…this doesn’t seem to work. At first I thought it was because I was using “evt.target.x” and “evt.target.y” in my functions rather than just “mc.x” and “mc.y”, but that did not work either. Just to test everything, I even simply copied and pasted your code in a new document with a single mc, and it’s doing the same thing. Not sure if you tested the code or not, but perhaps there’s a syntax error? I’ll keep working on it. Thanks again for all your help!

  20. David Stiller Says:

    Sean,

    I’m lost, bro. Sorry! When I paste this into a new FLA configured for AS3, it works for me. Now … that said, I did see where my suggestion could be improved. AS3 is always turning up sticky points like this. ;)

    In AS2, the mouseMove registers even when the mouse isn’t over the object in question (mc, in this case). In AS3, that doesn’t seem to be the case. For that reason, instead of assigning the MOUSE_MOVE handler to mc, it would be better to temporarily associate it with the Stage instead — then remove it later, as shown above. The only change is a revision to two lines, from mc to stage:

    function dragHandler(evt:MouseEvent):void {
      stage.addEventListener(MouseEvent.MOUSE_UP, dragStopHandler);
      stage.addEventListener(MouseEvent.MOUSE_MOVE, moveHandler);
    };
    
    function dragStopHandler(evt:MouseEvent):void {
      stage.removeEventListener(MouseEvent.MOUSE_UP, dragStopHandler);
      stage.removeEventListener(MouseEvent.MOUSE_MOVE, moveHandler);
    };
  21. Sean Shomes Says:

    That did it! Not sure if it was an issue when the curser got outside of the target mc. Works great. Thanks again Dave, this was a huge help.

  22. Darpan Dalal Says:

    thank you very very much your code is beautiful for constraining

    thanks very much again :)

Leave a Reply