How to Constrain Dragging to a Circle
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;
August 14th, 2006 at 9:48 pm
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?
August 15th, 2006 at 12:13 am
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 asMovieClip.onEnterFrame, to continually position the clip where the mouse is. If you useifstatements, 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.
August 16th, 2006 at 2:15 am
Me here [link to Adobe ActionScript forum]
August 16th, 2006 at 10:10 am
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.
August 18th, 2006 at 3:02 am
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
August 18th, 2006 at 7:35 am
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
August 18th, 2006 at 12:22 pm
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?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
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.
distance).August 18th, 2006 at 12:26 pm
Karl,
The center point is completely arbitrary. In my example, I set the
origXandorigYproperties ofmctomc’s original location …… 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
thisin the above code.November 8th, 2006 at 9:20 pm
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.
November 9th, 2006 at 2:57 pm
Jim,
Glad to hear it.
August 14th, 2007 at 3:46 am
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?
August 14th, 2007 at 10:42 am
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..elsestatements replaces the existingifstatement in the original blog entry: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 thedistancevariable. 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 samedistancevariable. Otherwise, the movie clip is put wherever the mouse is.The challenge, here, is how to handle angles at other … angles.
The
ifstatements may need to be very much more flexible than the current hard coded 0/0.68 range.August 19th, 2007 at 11:45 pm
wow never thought of that approach. now its more clear to me how this pie thingy works.
thanks david you’re a life saver
August 20th, 2007 at 12:43 pm
amir,
Glad to hear it.
March 13th, 2009 at 10:54 am
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
March 13th, 2009 at 1:12 pm
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 stillmc):The only real difference here is that you’re setting the
Sprite.buttonModeproperty of this movie clip totrue, so that you get the finger cursor when you mouse over the clip (in AS3, movie clips inherit functionality from theSpriteclass). You’ll also notice that the formerly_xand_yproperties have lost their underscores.Here are the first two event handlers, which kick everything off:
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 tonull; here, we’ll be using theremoveEventListener()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:
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:Voidsuffix, 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:Voidbecomes:voidin 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:[Note: See additional comments between Sean and me, following this reply, for a reason to replace
mcwithstagein two places.]When the mouse is pressed (
MOUSE_DOWN), the Stage will get aMOUSE_UPlistener, because none of AS3’s classes has anonReleaseOutsideevent. When the Stage hears a mouse release, that’ll accomplish the same thing here as anonReleaseOutsidewould have done in AS2.Other than that, when
mcis pressed, it’ll be assigned aMOUSE_MOVEhandler, just like in the AS2 version. When the mouse is released — either overmcor the Stage — thatMOUSE_MOVEhandler will be removed.Finally, the brains of the outfit, which is largely the same as it was before:
Look over it carefully, and you’ll see that only minor things have changed, such as a reference to the Stage rather than
_root,mouseXinstead of_xmouse, and the like.Hope this helps! Here it is again, all in one shot:
[Note: See additional comments between Sean and me, following this reply, for a reason to replace
mcwithstagein two places.]March 13th, 2009 at 1:44 pm
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.
March 13th, 2009 at 2:03 pm
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
MovieClipclass is a dynamic class, which means it can have properties added to it at runtime. TheseorigXandorigYvariables are properties in the sense that they’re being assigned to an object instance (aMovieClipinstance) at runtime, but in practice, they’re no different from timeline variables (the sort you declare withvarin keyframe code. But you can onlytypevariables, not properties. So you could do this:but when that variable is a property, no matter what version of ActionScript, it can only be this:
March 13th, 2009 at 2:06 pm
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!
March 13th, 2009 at 2:23 pm
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
mouseMoveregisters 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 theMOUSE_MOVEhandler tomc, 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, frommctostage:March 13th, 2009 at 2:44 pm
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.
June 30th, 2009 at 4:37 am
thank you very very much your code is beautiful for constraining
thanks very much again