Shaking Object With Swinging/Tilting Effect in AS3 - actionscript-3

I have an image of a bottle on the stage. I want to have it so that the user can click and drag the bottle around. I want the bottle to sway as it's dragged so tilt and rotate as if it's being pulled back and forth. I've Googled this and found nothing. How can I achieve this effect through code?
So far I've created a timer that tracks a start and end point during drag, calculates distance travelled to get a rough idea of speed and direction and then rotates the bottle within a range of 120 degrees using the Tween class (and then bounce back to 0 after). It's close but not quite as fluid looking as I'd like.. I feel like I need some kind of custom easing function but I'm not great with this type of math.
function distanceTimerBeat(e:TimerEvent):void
{//speed detection beat timer
distCounter++;
if(!distSwitch)
{
startX = product.x;
startY = product.y;
distSwitch = true;
}
else
{
endX = product.x;
endY = product.y;
distSwitch = false;
var newDistance = calculateDistance();
setRotation();
//trace('>> ('+distCounter+') distance travelled = '+newDistance);
//produceNewFirework()
}
}
function calculateDistance():Number
{//returns distance travelled over beat timer intervals
var rawDistance = (( startX - endX ) * (startX - endX )) + ((startY - endY ) * (startY - endY ));
var distance = Math.sqrt( rawDistance);
var xDistance = startX - endX;
if(xDistance < 0)
{
speedDirection = 'right';
directionModifier = -1;
}
else if(xDistance > 0)
{
speedDirection = 'left';
directionModifier = 1;
}
//trace('>> going '+speedDirection+' at '+xDistance);
return distance;
}
function setRotation():void
{ //once start & end values are detected, check speed and set the rotation.
var curSpeed = calculateDistance();
if(curSpeed > 4)
{
tweenLock = false;
var speedPercent = curSpeed/maxSpeed * 100;
var rotationDegree = (speedPercent*120)/300;
var newRotation = directionModifier*rotationDegree;
var reversePercent = 100 - speedPercent;
var tweenSpeedModifier = (speedPercent/100);
//trace(tweenSpeedModifier*1);
//if(!tweenLock)
//rotationTween = null;
if(rotationTween) rotationTween.stop();
rotationTween = new Tween(product, "rotation", Strong.easeInOut, product.rotation, newRotation, 0.25, true);
//rotationTween.resume();
rotationTween.addEventListener(TweenEvent.MOTION_FINISH, resetProductTween);
//trace('speed percet = '+speedPercent);
}
else
{
//tweenLock = true;
}
}
function resetProductTween(e:TweenEvent)
{//after the product rotates, snap it back to it's original position.
tweenLock = false;
if(!tweenLock)
{
rotationTween = new Tween(product, "rotation", Strong.easeOut, product.rotation, 0, 0.75, true);
rotationTween.addEventListener(TweenEvent.MOTION_FINISH, tweenUnlock);
}
}

http://code.tutsplus.com/tutorials/quick-tip-trigonometry-for-flash-game-developers--active-4458
stage.addEventListener(MouseEvent.CLICK, calculateAngle);
var myAtan2:Number;
function calculateAngle(e:MouseEvent):void
{
myAtan2 = Math.atan2(e.stageY - mCircle.y, e.stageX - mCircle.x);
trace(myAtan2);
}

Related

How to stop player from walking on walls

I'm building a test avatar chat... thingy in ActionScript3, but I've come across a problem, whenever I click the chatbar to say something, my avatar (which is currently a penguin) walks to it -- how can I prevent this from happening? In other words, how do I build a wall and keep the penguins out?
This is the code I'm using to make my penguin move.
stage.addEventListener(MouseEvent.CLICK, myClickReaction);
// speeds ALONG NYPOTENUSE
var v:Number = 7;
// vector of movement
var dir:int = 100;
// mouse click point
var clickPoint:Point = new Point();
// angle doesn't change metween clicks - so it can be global
var angle:Number;
function myClickReaction (e:MouseEvent):void {
clickPoint = new Point(mouseX, mouseY);
angle = Math.atan2(clickPoint.y - penguin.y, clickPoint.x - penguin.x);
dir = angle >= 0 ? -1 : 1;
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
function onEnterFrame(e:Event):void {
var projectedX:Number = penguin.x + v * Math.cos(angle);
var projectedY:Number = penguin.y + v * Math.sin(angle);
var diff:Number = clickPoint.y - projectedY;
if (diff / Math.abs(diff) == dir) {
penguin.x = clickPoint.x;
penguin.y = clickPoint.y;
removeEventListener(Event.ENTER_FRAME, onEnterFrame);
}
else {
penguin.x = projectedX;
penguin.y = projectedY;
}
}

Dragging with Actionscript 3.0, like moving camera in RTS games

I am trying to build a RTS game with Flash and doing some basic testing. I come across this site teaching me dragging objects. I am modified the code to simulate moving the game world of the game while clicking on it. The center circle is the focus point / center of camera. The rectangle board represents the game world.
I tried to change the function boardMove to click and move according to mouseX and mouseY. But every time I click, the mouseX and mouseY becomes the center of the board, which is not what I wanted. I want to make it relative to the mouse position, but I could only make the board flickering, or moves with its top left corner.
Any suggestions would be appreciated.
// Part 1 -- Setting up the objects
var board:Sprite = new Sprite();
var myPoint:Sprite = new Sprite();
var stageWidth = 550;
var stageHeight = 400;
var boardWidth = 400;
var boardHeight = 300;
var pointWidth = 10;
this.addChild(board);
this.addChild(myPoint);
board.graphics.lineStyle(1,0);
board.graphics.beginFill(0xCCCCCC);
board.graphics.drawRect(0,0,boardWidth,boardHeight);
board.graphics.endFill();
board.x = (stageWidth - boardWidth) / 2;
board.y = (stageHeight - boardHeight) / 2;
myPoint.graphics.lineStyle(1,0);
myPoint.graphics.beginFill(0x0000FF,0.7);
myPoint.graphics.drawCircle(0,0,pointWidth);
myPoint.graphics.endFill();
myPoint.x = (stageWidth - pointWidth) / 2;
myPoint.y = (stageHeight - pointWidth) / 2;
// Part 2 -- Add drag-and-drop functionality - Better Attempt
stage.addEventListener(MouseEvent.MOUSE_DOWN, startMove);
function startMove(evt:MouseEvent):void {
stage.addEventListener(MouseEvent.MOUSE_MOVE, boardMove);
}
// Revised definition of pointMove in Part II of our script
function boardMove(e:MouseEvent):void {
board.x = checkEdgeX(board.mouseX);
board.y = checkEdgeY(board.mouseY);
e.updateAfterEvent();
}
stage.addEventListener(MouseEvent.MOUSE_UP, stopMove);
function stopMove(e:MouseEvent):void {
stage.removeEventListener(MouseEvent.MOUSE_MOVE, boardMove);
}
// Part III -- Check for boundaries
function checkEdgeX(inX:Number):Number {
var x = stageWidth / 2 - boardWidth;
if (inX < x) {
return x;
}
x = stageWidth / 2;
if (inX > x) {
return x;
}
return inX;
}
function checkEdgeY(inY:Number):Number {
var y = stageHeight / 2 - boardHeight;
if (inY < y) {
return y;
}
y = stageHeight / 2;
if (inY > y) {
return y;
}
return inY;
}
One option is to determine the relative movement of the mouse and move the board accordingly; something like:
private Point lastPosition;
function startMove(...) {
lastPosition = null;
...
}
function boardMove(e:MouseEvent):void {
Point position = new Point(stageX, stageY);
if (lastPosition != null) {
Point delta = position.subtract(lastPosition);
board.x += delta.x; // NOTE: also try -= instead of +=
board.y += delta.y; // NOTE: also try -= instead of +=
e.updateAfterEvent();
}
lastPosition = position;
}

Drag panning with zoom on mouseclick location AS3

So, another post on here REALLY helped me prior from this link!
It got me to zoom in neatly, but what I'm really looking for in my panning is a mouse drag (I will be working with a touch screen, so dragging to move is important) No need to use Touch classes, I have done dragging events with Mouse classes before and find it easier to me.
My problem here is that my neat zoom is linked to this MOUSE_MOVE panning, and I want to know a clean way to change my panning to a drag event, and still not have my panning go past my image constraints.
bg_mc- is my background image to move and zoom.
My code:
import com.greensock.*;
bg_mc.doubleClickEnabled = true;
//Variables
var percX:Number;
var percY:Number;
var destX:Number;
var destY:Number;
//Image panned and masked
this.mask = mask_mc;
stage.addEventListener(MouseEvent.MOUSE_MOVE,mousemove);
function mousemove(e:MouseEvent) {
if (mask_mc.hitTestPoint(stage.mouseX,stage.mouseY,false)) {
if (bg_mc.width>mask_mc.width) {//Avoids Scrolling if image is under mask area width
percX = mask_mc.mouseX/mask_mc.width;
}
if (bg_mc.height>mask_mc.height) {
//Avoids Scrolling if image is under mask area height
percY = mask_mc.mouseY/mask_mc.height;
}
destX = -(bg_mc.width-mask_mc.width)*percX;
destY = -(bg_mc.height-mask_mc.height)*percY;
TweenMax.to(bg_mc,.5,{x:destX,y:destY});
}
}
//Add listeners for the imgLoader movie clip.
bg_mc.doubleClickEnabled = true;
bg_mc.addEventListener(MouseEvent.DOUBLE_CLICK, increaseSize);
bg_mc.addEventListener(MouseEvent.CLICK, decreaseSize);
function scaleAroundMouse(objectToScale:DisplayObject, scaleAmount:Number,
bounds:Rectangle = null, onComplete:Function = null):TweenLite {
// scaling will be done relatively
var relScaleX:Number = scaleAmount / objectToScale.scaleX;
var relScaleY:Number = scaleAmount / objectToScale.scaleY;
// map vector to centre point within parent scope
var scalePoint:Point = objectToScale.localToGlobal( new
Point(objectToScale.mouseX, objectToScale.mouseY));
scalePoint = objectToScale.parent.globalToLocal( scalePoint );
// current registered postion AB
var AB:Point = new Point( objectToScale.x, objectToScale.y );
// CB = AB - scalePoint, objectToScale vector that will scale as it runs from the centre
var CB:Point = AB.subtract( scalePoint );
CB.x *= relScaleX;
CB.y *= relScaleY;
// recaulate AB, objectToScale will be the adjusted position for the clip
AB = scalePoint.add( CB );
// set actual properties
if(bounds){
var limits:Rectangle = new Rectangle(
bounds.x + (bounds.width - (objectToScale.width * relScaleX)),
bounds.y + (bounds.height - (objectToScale.height * relScaleY)),
(objectToScale.width * relScaleX) - bounds.width,
(objectToScale.height * relScaleY) - bounds.height
);
if(AB.x < limits.x) AB.x = limits.x;
if(AB.x > limits.x + limits.width) AB.x = limits.x + limits.width;
if(AB.y < limits.y) AB.y = limits.y;
if(AB.y > limits.y + limits.height) AB.y = limits.y + limits.height;
}
return TweenLite.to(objectToScale,1,{onComplete: onComplete,
scaleX: scaleAmount, scaleY: scaleAmount, x: AB.x, y: AB.y});
}
function increaseSize(event:MouseEvent):void{
stopMouseMove();
scaleAroundMouse(bg_mc, 4, null, resumeMouseMove);
}
function decreaseSize(event:MouseEvent):void{
stopMouseMove();
scaleAroundMouse(bg_mc, 1, null, resumeMouseMove);
}
function stopMouseMove():void {
stage.removeEventListener(MouseEvent.MOUSE_MOVE,mousemove);
}
function resumeMouseMove():void {
stage.addEventListener(MouseEvent.MOUSE_MOVE,mousemove);
}
Any help I can get would be great!
On a mousedown or whatever listener you want to start dragging, use the MovieClip.StartDrag() function to allow dragging, and the StopDrag() to stop it.
While you could use the built in startDrag (or startTouchDrag) and stopDrag methods, it will not play nice with your zoom in/out function.
bg_mc.addEventListener(MouseEvent.MOUSE_DOWN,mouseDown); //add the listener to the bg directly
function mouseDown(e:MouseEvent) {
// if (mask_mc.hitTestPoint(stage.mouseX,stage.mouseY,false)) { //this isn't needed if you add the listener directly to bg_mc
stage.addEventListener(MouseEvent.MOUSE_UP,mouseUp);
bg_mc.startDrag(false, getBoundsRect()); //see the code below for the getBoundsRect() function
}
function mouseUp(e:MouseEvent):void {
bg_mc.stopDrag();
stage.removeEventListener(MouseEvent.MOUSE_UP,mouseUp);
}
You'd be better served to have a custom drag function, like below:
import com.greensock.*;
import flash.events.MouseEvent;
import flash.geom.Rectangle;
import flash.events.Event;
import flash.geom.Point;
bg_mc.doubleClickEnabled = true;
//Variables
var percX:Number;
var percY:Number;
var destX:Number;
var destY:Number;
//Image panned and masked
bg_mc.mask = mask_mc;
//this function generates a bounds rectangle that would keep bg_mc edges from going outside of mask_mc area
function getBoundsRect():Rectangle {
return new Rectangle((mask_mc.x + mask_mc.width) - bg_mc.width, (mask_mc.y + mask_mc.height) - bg_mc.height, bg_mc.width - mask_mc.width, bg_mc.height - mask_mc.height);
}
var isZoomed:Boolean = false; //a var to keep track of whether your zoomed in or out
var isDragging:Boolean = false; //a var to keep track of whether the bg is being dragged
var tmpMousePoint:Point = new Point(); //this stores the mouse coordinates on the mouse down, to compare later on the mouse up to see if the mouse moved
var decay:Number = .27; //make this lower for slower drag, make it 1 to turn off a smooth drag all together
var tmpMouseMoved:Boolean = false; //to keep track on mouse up whether the action was a drag or a click
var mouseMoveSensitivity:Number = 15; //how far does mouse need to move before you cancel the click event
var offset:Point = new Point(); //the offset of the initial mouse click relative to bg_mc's 0,0
bg_mc.addEventListener(MouseEvent.MOUSE_DOWN,mouseDown);
bg_mc.addEventListener(MouseEvent.CLICK,mouseClick, false, 99999); //listen with a higher priority than any other click listeners on bg_mc - this way if it's a drag, you can cancel the click event
function mouseDown(e:Event):void {
//reset these to default
isDragging = true;
tmpMouseMoved = false;
tmpMousePoint.x = mouseX; //capture the current mouse to check later if it moved (so you know the user intended to drag not click)
tmpMousePoint.y = mouseY;
offset.x = bg_mc.mouseX;
offset.y = bg_mc.mouseY;
bg_mc.addEventListener(Event.ENTER_FRAME,bgEnterFrame); //listen every frame until the mouse is released
stage.addEventListener(MouseEvent.MOUSE_UP, mouseUp);
}
function bgEnterFrame(e:Event):void {
bg_mc.x += decay * (mouseX - offset.x - bg_mc.x);
bg_mc.y += decay * (mouseY - offset.y - bg_mc.y);
var bounds:Rectangle = getBoundsRect();
switch(true){
case (bg_mc.x < bounds.x):
bg_mc.x = bounds.x;
break;
case (bg_mc.x > bounds.x + bounds.width):
bg_mc.x = bounds.x + bounds.width;
}
switch(true){
case (bg_mc.y < bounds.y):
bg_mc.y = bounds.y;
break;
case (bg_mc.y > bounds.y + bounds.height):
bg_mc.y = bounds.y + bounds.height;
}
if(Math.abs(tmpMousePoint.x - mouseX) > mouseMoveSensitivity || Math.abs(tmpMousePoint.y - mouseY) > mouseMoveSensitivity){
tmpMouseMoved = true;
}
}
function mouseUp(e:Event):void {
isDragging = false;
//remove listeners
bg_mc.removeEventListener(Event.ENTER_FRAME,bgEnterFrame);
stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUp);
}
function mouseClick(e:MouseEvent):void {
trace("CLICK cap");
if(tmpMouseMoved){
trace("Kill");
e.stopImmediatePropagation(); //cancel the mouse event
}
}
//Add listeners for the imgLoader movie clip.
bg_mc.doubleClickEnabled = true;
bg_mc.addEventListener(MouseEvent.DOUBLE_CLICK, increaseSize);
bg_mc.addEventListener(MouseEvent.CLICK, decreaseSize,false,0,true);
function scaleAroundMouse(objectToScale:DisplayObject, scaleAmount:Number,
bounds:Rectangle = null, onComplete:Function = null):TweenLite {
// scaling will be done relatively
var relScaleX:Number = scaleAmount / objectToScale.scaleX;
var relScaleY:Number = scaleAmount / objectToScale.scaleY;
// map vector to centre point within parent scope
var scalePoint:Point = objectToScale.localToGlobal( new
Point(objectToScale.mouseX, objectToScale.mouseY));
scalePoint = objectToScale.parent.globalToLocal( scalePoint );
// current registered postion AB
var AB:Point = new Point( objectToScale.x, objectToScale.y );
// CB = AB - scalePoint, objectToScale vector that will scale as it runs from the centre
var CB:Point = AB.subtract( scalePoint );
CB.x *= relScaleX;
CB.y *= relScaleY;
// recaulate AB, objectToScale will be the adjusted position for the clip
AB = scalePoint.add( CB );
// set actual properties
if(bounds){
var limits:Rectangle = new Rectangle(
bounds.x + (bounds.width - (objectToScale.width * relScaleX)),
bounds.y + (bounds.height - (objectToScale.height * relScaleY)),
(objectToScale.width * relScaleX) - bounds.width,
(objectToScale.height * relScaleY) - bounds.height
);
if(AB.x < limits.x) AB.x = limits.x;
if(AB.x > limits.x + limits.width) AB.x = limits.x + limits.width;
if(AB.y < limits.y) AB.y = limits.y;
if(AB.y > limits.y + limits.height) AB.y = limits.y + limits.height;
}
TweenLite.killTweensOf(objectToScale); //need to add this so the click/double click don't compete with each other
return TweenLite.to(objectToScale,1,{onComplete: onComplete,
scaleX: scaleAmount, scaleY: scaleAmount, x: AB.x, y: AB.y});
}
function increaseSize(event:MouseEvent):void{
if(isZoomed){
scaleAroundMouse(bg_mc, 4, getBoundsRect());
isZoomed = false;
}
}
function decreaseSize(event:MouseEvent):void{
if(!isZoomed){
scaleAroundMouse(bg_mc, 1, getBoundsRect());
isZoomed = true;
}
}

elastic string script makes flash crash

I'm in over my head in this AS3 project.
I'm no expert in actionscript and certainly not in AS3 but I managed to create (with help from this: http://swamy-techtalk.blogspot.com/2011/07/elastic-string-to-mouse-pointer-effect.html) a eleastic string effect from a point to a draggable movieclip.
Problem is the script seems to crash flash or the browser when I test it. (Not right away just when I'm playing around with the movieclip)
Sinse I'm in over my head in the script I compiled I'm not exactly sure whats wrong.
A bit of google research hinted that it might have something to do with removeChildAt() wich I changed from removeChildAt(0) to removeChildAt(1) to prevent it from removing my movieclip.
Hope somebody has the patience to read through my script to see what I did wrong.
Example here: http://www.madsringblom.dk/flash/pullstring.html (beware it might crash your browser)
Code below:
Object(this).leaf_mc.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
var origX:int = Object(this).leaf_mc.x + 1;
var origY:int = Object(this).leaf_mc.y + 2;
var pullbackX:int = Object(this).leaf_mc.x;
var pullbackY:int = Object(this).leaf_mc.y;
var dragging:Boolean = false;
//speed of pulling and rotating back when stop dragging.
var speed:int = 15;
var addX:int = 2
var addY:int = 3
function mouseDownHandler(e:MouseEvent):void
{
var obj = e.target;
obj.startDrag();
dragging = true;
}
function mouseUpHandler(e:MouseEvent):void
{
var obj = e.target;
Object(this).leaf_mc.stopDrag();
dragging = false;
}
import flash.display.*;
import flash.events.MouseEvent;
import flash.events.Event;
var haschild:Boolean = false;
var gotonodes:Array = new Array();
var currentnodes:Array = new Array();
var posX:int = Object(this).leaf_mc.x + addX;
var posY:int = Object(this).leaf_mc.y + addY;
gotonodes = Interpolate(posX,posY,origX,origY,25);
currentnodes = gotonodes;
stage.addEventListener(Event.ENTER_FRAME, onmove1);
function onmove1(e:Event)
{
for (var node = 0; node < gotonodes.length - 1; node++)
{
currentnodes[node].xco=currentnodes[node].xco+(gotonodes[node].xco-currentnodes[node].xco)/(node*node/30+1);
currentnodes[node].yco=currentnodes[node].yco+(gotonodes[node].yco-currentnodes[node].yco)/(node*node/30+1);
}
var posX:int = Object(this).leaf_mc.x + addX;
var posY:int = Object(this).leaf_mc.y + addY;
gotonodes=Interpolate(posX,posY,origX,origY,25);
// pull leaf_mc back to starting point when released. And rotate back.
if (dragging == false)
{
Object(this).leaf_mc.x-=(Object(this).leaf_mc.x-pullbackX)/speed;
Object(this).leaf_mc.y-=(Object(this).leaf_mc.y-pullbackY)/speed;
Object(this).leaf_mc.rotation+=Object(this).leaf_mc.rotation/speed;
}
// rotating the leaf_mc according to the point (origX,origY)
var theX:int = origX - Object(this).leaf_mc.x;
var theY:int = (origY - Object(this).leaf_mc.y) * -1;
var angle = Math.atan(theY/theX)/(Math.PI/180);
Math.atan( -5 / 10) / (Math.PI / 180);
if (theX < 0)
{
angle += 180;
}
if (theX >= 0 && theY < 0)
{
angle += 360;
}
Object(this).leaf_mc.rotation = (angle*-1) + 90;
DrawNodes(currentnodes);
}
function FindAngle(x1, x2, y1, y2):Number
{
return Math.atan2(y2-y1, x2-x1);
};
function Distance(x1, x2, y1, y2):Number
{
return Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2));
}
function Interpolate(x1, y1, x2, y2, n):Array
{
var dist= Distance(x1,x2,y1,y2);
var ang = FindAngle(x1,x2,y1,y2);
var points = [];
for (var l = 0; l <= dist; l += dist / n)
{
var x3 =x1+l*Math.cos(ang);
var y3 = y1+l*Math.sin(ang);
points.push({xco:x3,yco:y3});
}
points.push( { xco:x1, yco:y1 } );
return points;
}
function DrawNodes(array):void
{
if(haschild)
{
this.removeChildAt(1);
haschild=false;
}
var shape:Shape = new Shape();
shape.graphics.lineStyle(1,0x331100,40);
shape.graphics.moveTo(array[0].xco, array[0].yco);
for (var i = 0; i < array.length - 1; i++)
{
shape.graphics.lineTo(array[i].xco,array[i].yco);
}
shape.graphics.beginFill(0xFBFFA4,1);
shape.graphics.drawCircle(array[0].xco,array[0].yco,1);
shape.graphics.endFill();
this.addChild(shape);
haschild = true;
}
Where is this code placed? From what you pasted, I'm thinking on the stage.
A stop(); somewhere in the script might be a start and help quite a bit - otherwise the Flash movie will loop, and every time it hits this frame (every frame if you only have one), you'll add new event handlers etc. Eventually you'll run out of memory, and the onmove1 event handler will eat up your CPU, running 50 times per frame after 50 frames, 200 times after 200 frames etc.

Constrain MovieClip drag to a circle

...well, to an incomplete circle.
I have a draggable slider that looks like this:
The blue bar has the instance name track and the pink dot has the instance name puck.
I need the puck to be constrained within the blue area at all times, and this is where my maths failings work against me! So far I have the puck moving along the x axis only like this:
private function init():void
{
zeroPoint = track.x + (track.width/2);
puck.x = zeroPoint-(puck.width/2);
puck.buttonMode = true;
puck.addEventListener(MouseEvent.MOUSE_DOWN,onMouseDown);
}
private function onMouseDown(evt:MouseEvent):void
{
this.stage.addEventListener(MouseEvent.MOUSE_MOVE,onMouseMove);
this.stage.addEventListener(MouseEvent.MOUSE_UP,onMouseUp);
}
private function onMouseUp(evt:MouseEvent):void
{
this.stage.removeEventListener(MouseEvent.MOUSE_MOVE,onMouseMove);
}
private function onMouseMove(evt:MouseEvent):void
{
puck.x = mouseX-(puck.width/2);
//need to plot puck.y using trig magic...
}
My thinking is currently that I can use the radius of the incomplete circle (50) and the mouseX relative to the top of the arc to calculate a triangle, and from there I can calculate the required y position. Problem is, I'm reading various trigonometry sites and still have no idea where to begin. Could someone explain what I need to do as if speaking to a child please?
Edit: The fact that the circle is broken shouldn't be an issue, I can cap the movement to a certain number of degrees in each direction easily, it's getting the degrees in the first place that I can't get my head around!
Edit2: I'm trying to follow Bosworth99's answer, and this is the function I've come up with for calculating a radian to put into his function:
private function getRadian():Number
{
var a:Number = mouseX - zeroPoint;
var b:Number = 50;
var c:Number = Math.sqrt((a^2)+(b^2));
return c;
}
As I see it, the problem you solve is finding the closest point on a circle. Google have a lot of suggestions on this subject.
You can optimise it by first detecting an angle between mouse position and circle center. Use Math.atan2() for that. If the angle is in a gap range, just choose the closest endpoint: left or right.
EDIT1 Here is a complete example of this strategy.
Hope that helps.
import flash.geom.Point;
import flash.events.Event;
import flash.display.Sprite;
var center:Point = new Point(200, 200);
var radius:uint = 100;
var degreesToRad:Number = Math.PI/180;
// gap angles. degrees are used here just for the sake of simplicity.
// what we use here are stage angles, not the trigonometric ones.
var gapFrom:Number = 45; // degrees
var gapTo:Number = 135; // degrees
// calculate endpoints only once
var endPointFrom:Point = new Point();
endPointFrom.x = center.x+Math.cos(gapFrom*degreesToRad)*radius;
endPointFrom.y = center.y+Math.sin(gapFrom*degreesToRad)*radius;
var endPointTo:Point = new Point();
endPointTo.x = center.x+Math.cos(gapTo*degreesToRad)*radius;
endPointTo.y = center.y+Math.sin(gapTo*degreesToRad)*radius;
// just some drawing
graphics.beginFill(0);
graphics.drawCircle(center.x, center.y, radius);
graphics.moveTo(center.x, center.y);
graphics.lineTo(endPointFrom.x, endPointFrom.y);
graphics.lineTo(endPointTo.x, endPointTo.y);
graphics.lineTo(center.x, center.y);
graphics.endFill();
// something to mark the closest point
var marker:Sprite = new Sprite();
marker.graphics.lineStyle(20, 0xFF0000);
marker.graphics.lineTo(0, 1);
addChild(marker);
var onEnterFrame:Function = function (event:Event) : void
{
// circle intersection goes here
var mx:int = stage.mouseX;
var my:int = stage.mouseY;
var angle:Number = Math.atan2(center.y-my, center.x-mx);
// NOTE: in flash rotation is increasing clockwise,
// while in trigonometry angles increase counter clockwise
// so we handle this difference
angle += Math.PI;
// calculate the stage angle in degrees
var clientAngle:Number = angle/Math.PI*180
// check if we are in a gap
if (clientAngle >= gapFrom && clientAngle <= gapTo) {
// we are in a gap, no sines or cosines needed
if (clientAngle-gapFrom < (gapTo-gapFrom)/2) {
marker.x = endPointFrom.x;
marker.y = endPointFrom.y;
} else {
marker.x = endPointTo.x;
marker.y = endPointTo.y;
}
// we are done here
return;
}
// we are not in a gp, calculate closest position on a circle
marker.x = center.x + Math.cos(angle)*radius;
marker.y = center.y + Math.sin(angle)*radius;
}
addEventListener(Event.ENTER_FRAME, onEnterFrame);
EDIT2 Some links
Here are some common problems explained and solved in a brilliantly clear and concise manner: http://paulbourke.net/geometry/ This resource helped me a lot days ago.
Intersection of a line and a circle is a bit of an overkill here, but here it is: http://paulbourke.net/geometry/sphereline/
Rather than trying to move the point along the partial path of the circle, why not fake it and use a knob/dial? Skin it to look like the dot is moving along the path.
Then just set the rotation of the knob to:
var deg:Number = Math.atan2(stage.mouseY - knob.y,stage.mouseX - knob.x) / (Math.PI/180);
// code to put upper/lower bounds on degrees
knob.rotation = deg;
You can test this by throwing it in an enter frame event, but you'll obviously want to put some logic in to control how the knob starts moving and when it should stop.
100% working code.
enter code here
const length:int = 100;
var dragging:Boolean = false;
var tx:int;
var ty:int;
var p1:Sprite = new Sprite();
var p2:Sprite = new Sprite();
p1.graphics.beginFill(0);
p1.graphics.drawCircle(0, 0, 10);
p1.graphics.endFill();
p2.graphics.copyFrom(p1.graphics);
p1.x = stage.stageWidth / 2;
p1.y = stage.stageHeight / 2;
p2.x = p1.x + length;
p2.y = p1.y;
addChild(p1);
addChild(p2);
p2.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown);
stage.addEventListener(MouseEvent.MOUSE_UP, mouseUp);
stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMove);
function mouseDown(event:MouseEvent):void
{
dragging = true;
}
function mouseUp(event:MouseEvent):void
{
dragging = false;
}
function mouseMove(event:MouseEvent):void
{
if (dragging)
{
tx = event.stageX - p1.x;
ty = event.stageY - p1.y;
if (tx * tx + ty * ty > length * length)
{
p2.x = p1.x + tx / Math.sqrt(tx * tx + ty * ty) * length;
p2.y = p1.y + ty / Math.sqrt(tx * tx + ty * ty) * length;
}
else
{
p2.x = event.stageX;
p2.y = event.stageY;
}
}
}
Something like this ought to work out:
private function projectLocation(center:point, radius:uint, radian:Number):Point
{
var result:Point = new Point();
//obtain X
result.x = center.x + radius * Math.cos(radian));
//obtain Y
result.y = center.y + radius * Math.sin(radian));
return result;
}
Obviously, modify as needed, but you just need to send in a center point, radius and then a radian (you can obtain with angle * (Math.PI / 180)). You could easily hard code in the first two params if they don't change. What does change is the radian, and that is what you will need to change over time, as your mouse is dragging (defined by the mouseX distance to the center point - positive or negative).
Hopefully that helps get you started -
update
This was how I was working this out - tho its a tad buggy, in that the puck resets to 0 degrees when the sequence starts. That being said, I just saw that - #Nox got this right. I'll post what I was arriving at using my projectLocation function anyways ;)
package com.b99.testBed.knob
{
import com.b99.testBed.Main;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
/**
* ...
* #author bosworth99
*/
public class Knob extends Sprite
{
private var _puck :Sprite;
private var _track :Sprite;
private const DIAMETER :uint = 100;
private const RADIUS :uint = DIAMETER / 2;
public function Knob()
{
super();
init();
}
private function init():void
{
assembleDisplayObjects();
addEventHandlers();
}
private function assembleDisplayObjects():void
{
_track = new Sprite();
with (_track)
{
graphics.beginFill(0xffffff, 1);
graphics.lineStyle(1, 0x000000);
graphics.drawEllipse(-RADIUS, -RADIUS, DIAMETER, DIAMETER);
graphics.endFill();
}
this.addChild(_track);
_track.x = Main.stage.stageWidth / 2;
_track.y = Main.stage.stageHeight / 2;
_puck = new Sprite();
with (_puck)
{
graphics.beginFill(0x2DFE07, 1);
graphics.drawEllipse(-8, -8, 16, 16);
graphics.endFill();
x = _track.x;
y = _track.y - _track.width / 2;
buttonMode = true;
}
this.addChild(_puck);
}
private function addEventHandlers():void
{
Main.stage.addEventListener(MouseEvent.MOUSE_DOWN, activate);
Main.stage.addEventListener(MouseEvent.MOUSE_UP, deactivate);
}
private function deactivate(e:MouseEvent):void
{
Main.stage.removeEventListener(MouseEvent.MOUSE_MOVE, update);
}
private var _origin:uint;
private function activate(e:MouseEvent):void
{
Main.stage.addEventListener(MouseEvent.MOUSE_MOVE, update);
_origin = mouseX;
}
private function update(e:MouseEvent):void
{
var distance:Number;
(mouseX < _origin)? distance = -(_origin - mouseX) : distance = mouseX - _origin;
if(distance > 40){distance = 40};
if(distance < -220){distance = -220};
var angle:Number = distance; //modify?
var radian:Number = angle * (Math.PI / 180);
var center:Point = new Point(_track.x, _track.y);
var loc:Point = projectLocation(center, RADIUS, radian);
_puck.x = loc.x;
_puck.y = loc.y;
}
private function projectLocation(center:Point, radius:uint, radian:Number):Point
{
var result:Point = new Point();
//obtain X
result.x = center.x + radius * Math.cos(radian);
//obtain Y
result.y = center.y + radius * Math.sin(radian);
return result;
}
}
}
Main difference is that I'm obtaining the angle via horizontal (x) movement, and not checking against the cursors angle. Thos, trapping the values manually feels kinda hacky compared to #Nox very good soution. Would of cleaned up had I kept going;)
Nice question - Cheers