Constrain MovieClip drag to a circle - actionscript-3

...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

Related

Orbiting objects with equal spacing AS3

I have a monster that produces crystals. I want each crystal to orbit the monster, but when there is more than one crystal, I want them to orbit at an equal distance from each other. I've been trying to get this to work using two blocks of code I already have, but each one does something different and i need one block of code that does it all.
This block simply allows an object to orbit another:
orbitRadius = 110;
angle += orbitSpeed;
rad = (angle * (Math.PI / 180));
orbitX = monster.x + orbitRadius * Math.cos(rad);
orbitY = monster.y + orbitRadius * Math.sin(rad);
Here's a video of what it looks like:
https://www.youtube.com/watch?v=ACclpQBsjPo
This block of code arranges crystals around the monster based on the amount of crystals there are:
radius = 110;
angle = ((Math.PI * 2) / targetArray.length) * targetArray.indexOf(this);
orbitX = monster.x - (radius * Math.cos(angle));
orbitY = monster.y - (radius * Math.sin(angle));
And here's this video: https://www.youtube.com/watch?v=TY0mBHc2A8U
I do not know how to both space the crystals equally and make them circle around the monster at the same time. What needs to be done in order to achieve this?
1) Hierarchical way: put crystals into the same container so they spread equally (like you are doing on the second video) then rotate the container.
2) Math way.
Implementation:
public class Orbiter extends Sprite
{
// Pixels.
public var radius:Number = 100;
// Degrees per second.
public var speed:Number = 360;
public var items:Array;
public var lastTime:int;
public function start()
{
stop();
rotation = 0;
items = new Array;
lastTime = getTimer();
addEventListener(Event.ENTER_FRAME, onFrame);
}
public function stop():void
{
items = null;
removeEventListener(Event.ENTER_FRAME, onFrame);
}
public function onFrame(e:Event = null):void
{
var aTime:int = getTimer();
rotation += speed * (aTime - lastTime) / 1000;
lastTime = aTime;
for (var i:int = 0; i < items.length; i++)
{
// Get the object.
var anItem:DisplayObject = items[i];
// Get the object's designated position.
var aPos:Point = getPosition(i);
// Follow the position smoothly.
anItem.x += (aPos.x - anItem.x) / 10;
anItem.y += (aPos.y - anItem.y) / 10;
}
}
private function getPosition(index:int):Point
{
// Calculate the angle with regard to the present items amount.
var anAngle:Number = (rotation - 360 / items.length) * Math.PI / 180;
var result:Point = new Point;
// Figure the position with regard to (x,y) offset.
result.x = x + radius * Math.cos(anAngle);
result.y = y + radius * Math.sin(anAngle);
return result;
}
}
Usage:
var O:Orbiter = new Orbiter;
// Define the offset.
O.x = monster.x;
O.y = monster.y;
// Set radius and rotation speed.
O.radius = 110;
O.speed = 270;
// Enable the rotation processing.
O.start();
// Append items to orbit.
O.items.push(Crystal1);
O.items.push(Crystal2);
O.items.push(Crystal3);
You can change radius and speed any time, as well as add/remove items, thanks to motion smoothing that all will look equally fine.

rotating sprite from it's current angle as3

i created an object that is rotated with the mouse, however each time after rotating, when mouse is clicked again the object "jump" right to it's new angle i would like that the object will continue to rotate from it's current location no matter where the mouse being clicked.
this is my code that rotates the object.
public function _mouseclick(me:MouseEvent):void
{
addEventListener(Event.ENTER_FRAME, UpdateGame);
IsButtonClicked = true;
}
public function UpdateGame(e:Event):void
{
if (IsButtonClicked)
{
var dist_Y:Number = mouseY - PlayerSprite.y ;
var dist_X:Number = mouseX - PlayerSprite.x ;
var angle:Number = Math.atan2(dist_Y, dist_X);
var degrees:Number = angle * 180/ Math.PI;
PlayerSprite.rotation = degrees;
}
}
how can i reset the angle to the current sprite angle and prevent that "jump"?
It looks like maybe you are trying to make some sort of knob or dial. Finding the angle between the mouse and sprite and applying it the way you are, will always face that direction. I think you are looking for something a little more simple.
First I would set up 2 variables, one to be the amount to increment the rotation, the other to store the mouse position.
var increment:int = 5;
var mousePos:Point;
Then in your mouse click function:
mousePos = new Point( mouseX, mouseY );
Lastly in your UpdateGame():
private function UpdateGame( e:Event ):void {
if ( IsButtonClicked ) {
var increment:int = 5;
if ( mousePos.y < mouseY ) {
spr.rotation += increment;
}
else {
spr.rotation -= increment;
}
mousePos = new Point( mouseX, mouseY );
}
}
That seems to be more the effect you are looking for. In this case when you move the mouse up, the sprite rotates counter clockwise. Moving the mouse down, rotates it clockwise.
If you want to change the rotation smoothly, you want to base it on the change from last rotation as the delta.
The delta is based on the initial rotation from mouse click relative to the current rotation.
current_rotation += (atan2 - last_rotation);
last_rotation = atan2;
And the full pseudo example.
private var current_rotation:Number;
private var last_rotation:Number;
private var mouse_down:Boolean;
public function MouseClick(e:MouseEvent):void
{
var dX:Number = Mouse.X - 400;
var dY:Number = Mouse.Y - 300;
var atan2:Number = Math.atan2(dY, dX);
last_rotation = atan2;
mouse_down = true;
}
public function UpdateGame(e:Event):void
{
if (mouse_down)
{
var dX:Number = Mouse.X - 400;
var dY:Number = Mouse.Y - 300;
var atan2:Number = Math.atan2(dY, dX);
current_rotation += (atan2 - last_rotation);
last_rotation = atan2;
}
}

AS3 How can I constrain an objects movement within the stage?

My object is controlled via mouse movement (for now)... Because of the way it's controlled (object always moves away from cursor), I want to add constraints so that it not only stays within the stage but leaves space between the movement boundaries and the stage edges...
Therefore if the object moves too close to the stage limits, there will be room for the cursor to move it back into the play area.
Currently, I have a rectangle created dynamically within the stage and I was thinking I could constrain movement to within the area of this rectangle, which would leave enough room around the edges... How can I do this?
However, if there's a better/easier way to get the desired results... I'm all ears.
import flash.events.KeyboardEvent;
import flash.display.MovieClip;
import flash.events.Event;
import flash.display.Shape;
var rectangle:Shape = new Shape;
//initialize "rectangle" shape
rectangle.graphics.beginFill(0xCCCCCC);
//choose colour for fill - black
rectangle.graphics.drawRect(50, 50, 450, 300);
//draw shape
rectangle.graphics.endFill();
//end fill
addChild(rectangle);
//add "rectangle" to stage
var hero:MovieClip = new hero_mc();
//initialize "hero" object - "hero_mc"
hero.x = stage.stageWidth / 2;
hero.y = stage.stageHeight / 2;
//set spawn location, centre stage
addChild(hero);
//add "hero" to stage
function cursorHold(evt:Event):void {
trace("moving");
var dX:Number = hero.x - stage.mouseX;
//get adjacent
var dY:Number = hero.y - stage.mouseY;
//get opposite
var r:Number = Math.sqrt(Math.pow(dX, 2) + Math.pow(dY, 2));
//get hypotenuse
var angle:Number = Math.acos(dX/r)*180/Math.PI;
//get angle
var radians:Number = deg2rad(angle);
//call conversion function for angle
var speed:Number = 1.5;
//set speed
var xV:Number = Math.cos(radians) * speed;
//get x velocity
var yV:Number = Math.sin(radians) * speed;
//get y velocity
hero.x += xV;
//move hero along new x velocity
if (stage.mouseY > hero.y) {
hero.y -= yV;
} else {
hero.y += yV;
}
//move hero along new y velocity
}
function deg2rad(deg:Number):Number {
return deg * (Math.PI / 180);
//convert degrees to radians
}
stage.addEventListener(Event.ENTER_FRAME, cursorHold, false, 0, true);
Here's an image: https://lh5.googleusercontent.com/lWG8uR9MLK32oHXX26JBiLtBdvmiICFuxOQakQESnBY=w552-h402-no
So, "hero_mc" shouldn't be able to move into the white area but it should still move (scrape against the edges with the mouse movement.
Apologies if the code is a mess... I'm pretty new to ActionScript 3.0. Any tips on cleaning it up a bit would also be more than welcome.
here are some tips for you..
First, I would add a var for the margin to dynamically draw the active area of the stage and then create the rectangle using that, like this -
// margin of stage
var margin:uint = 20;
rectangle.graphics.drawRect(margin, margin, stage.stageWidth-(2*margin), stage.stageHeight-(2*margin));
Then I would also add a var for getting the center of the hero mc like this -
var heroCenter:Number = hero.width * .5;
And finally a var for setting the yV to += or -= and using an if to set it like this -
var posNeg:Number = 0;
if(stage.mouseY > hero.y){
posNeg = -1;
} else {
posNeg = 1;
}
hero.y += yV*posNeg;
Here is the full code. I set it to a speed to 10 to test faster.
import flash.events.KeyboardEvent;
import flash.display.MovieClip;
import flash.events.Event;
import flash.display.Shape;
// margin of stage
var margin:uint = 20;
var rectangle:Shape = new Shape;
//initialize "rectangle" shape
rectangle.graphics.beginFill(0xCCCCCC);
//choose colour for fill - black
rectangle.graphics.drawRect(margin, margin, stage.stageWidth-(2*margin), stage.stageHeight-(2*margin));
//draw shape
rectangle.graphics.endFill();
//end fill
addChild(rectangle);
//add "rectangle" to stage
var hero:MovieClip = new hero_mc();
//initialize "hero" object - "hero_mc"
// half hero to make sure hero goes completely to edge of stage without going over or under
// best if the hero widht is an even number.
var heroCenter:Number = hero.width * .5;
hero.x = stage.stageWidth / 2;
hero.y = stage.stageHeight / 2;
//set spawn location, centre stage
addChild(hero);
//add "hero" to stage
var posNeg:Number = 0;
function cursorHold(evt:Event):void {
//trace("moving");
var dX:Number = hero.x - stage.mouseX;
//get adjacent
var dY:Number = hero.y - stage.mouseY;
//get opposite
var r:Number = Math.sqrt(Math.pow(dX, 2) + Math.pow(dY, 2));
//get hypotenuse
var angle:Number = Math.acos(dX/r)*180/Math.PI;
//get angle
var radians:Number = deg2rad(angle);
//call conversion function for angle
var speed:Number = 10;
//set speed
var xV:Number = Math.cos(radians) * speed;
//get x velocity
var yV:Number = Math.sin(radians) * speed;
//get y velocity
hero.x += xV;
if(hero.x < margin + heroCenter){
hero.x = margin + heroCenter;
}
if(hero.x > stage.stageWidth - (margin + heroCenter)){
hero.x = stage.stageWidth - (margin + heroCenter);
}
if(stage.mouseY > hero.y){
posNeg = -1;
} else {
posNeg = 1;
}
hero.y += yV*posNeg;
//move hero along new x velocity
if (hero.y > stage.stageHeight - (margin + heroCenter)) {
hero.y = stage.stageHeight - (margin + heroCenter);
} else if (hero.y < margin + heroCenter) {
hero.y = margin + heroCenter;
}
//move hero along new y velocity
}
function deg2rad(deg:Number):Number {
return deg * (Math.PI / 180);
//convert degrees to radians
}
stage.addEventListener(Event.ENTER_FRAME, cursorHold, false, 0, true);

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;
}

Calculate a trajectory for an object, when given an angle and a velocity in flash

I am trying to launch a cannonball from a cannon and have it follow a realistic path. The angle of fire changes depending on the orientation of the cannon (automatically orientates to mouse pointer). So what I'm trying to figure out, is how to move a cannonball along a parabolic path, when given an angle, and a set velocity.
I've read that this can be done without complicated trigonometry (never listened to it in highschool), and can be calculated simply by adding gravity to the yVelocity every tick. However, at this moment, I don't know how to calculate the initial yVelocity (again, depending on cannon orientation).
You can see the current animation here: http://kate.ict.op.ac.nz/~welfajw1/portfolio/videos/task3-assignment2.swf
This is all done in AS3, and the code I have is as follows:
Main timeline code:
import flash.display.*;
import flash.events.*;
import flash.geom.*;
var cannonball:ball_mc;
var angleDegree;
myCannon.addEventListener(Event.ENTER_FRAME, cannonEnterFrame);
function cannonEnterFrame(pEvt)
{
var mc = myCannon;
var mg = myCannon.myGun;
//find angle for orientation
var angleRadian = Math.atan2(mouseY - mc.y, mouseX - mc.x);
//convert to degrees
angleDegree = angleRadian * 180 / Math.PI;
//limit rotation
if(angleDegree > -63 && angleDegree < 20)
mg.rotation = angleDegree;
}
stage.addEventListener(Event.ENTER_FRAME, stageRefresh);
function stageRefresh(pEvt)
{
if (cannonball)
{
//move every "tick"
cannonball.move();
}
}
stage.addEventListener(MouseEvent.CLICK, mouseClicked);
function mouseClicked(pEvt)
{
//starting position of the ball
cannonball = new ball_mc(100, 475);
//SEND IN INITIAL x, y VELOCITIES
cannonball.fire(20, angleDegree);
//add to stage
stage.addChild(cannonball);
}
ball_mc code:
package
{
import flash.display.MovieClip;
import flash.sensors.Accelerometer;
import flashx.textLayout.formats.Float;
public class ball_mc extends MovieClip
{
//constant gravity
public static const g:Number = 2;
//starting velocities
private var ux:Number;
private var uy:Number;
public function ball_mc(startX:int, startY:int)
{
x = startX;
y = startY;
}
public function fire(vx:Number, vy:Number):void
{
ux = vx;
uy = vy;
}
public function move():void
{
//distance moved in x dir
var sx:Number = ux;
//new velocity in y dir
var vy:Number = uy + g;
//distance moved in y dir
var sy:Number = uy + g/2;
//apply movement
x += sx;
y += sy;
//save new y velocity
uy = vy;
}
}
}
You need a little bit of physics.
Initial speed must be calculated by using some criteria that you add on your own. One example is to calculate initial speed by using the distance between the mouse and the cannon, at the time the mouse is pressed. If the distance is greater the projectile will have a bigger speed, and if the distance is smaller the projectile will have smaller speed.
The you add an Event Listener with type ENTER_FRAME.
I guess it's 2 dimensional animation so you have to find the current x and y at any point in time.
Here's a little bit of code:
var TimeperFrame:Number = 1/fps //fps is not a constant, here you should add a number, a value that you previously added in fla. document properties. I usualy use 60 fps
var Time:Number = 0;
addEventListener(ENTER_FRAME, movingCannonBall);
function movingCannonBall(e:Event):void
{
Time += TimeperFrame;
}
Now here's the equitation for trajectory of projectile.
x = xo + vxo·t
y = yo + vyo·t - 0.5·g·t^2
yo = initial height of your cannon ball
vyo = initial y velocity; vyo = vo·sin θ
t = time passed, we conrol that by upper code
g = acceleration (9,81 m/s^2) at Earth's surface
xo = initial distance for the start
vxo = initial x velocity; vxo = vo·cos θ
Now in the upper code we add these equitations and it should look like this:
var TimeperFrame:Number = 1/fps
var Time:Number = 0;
var initx: Number = cannonball.x;
var inity: Number = cannonball.y;
var initVelocity: Number = (you define initial Velocity by your criteria)
var G: Number = 9.81;
addEventListener(ENTER_FRAME, movingCannonBall);
function movingCannonBall(e:Event):void
{
Time += TimeperFrame;
cannonball.x = initx + Math.cos(angle) * initVelocity * Time;
cannonball.y = inity + Math.sin(angle) * initVelocity * Time - G * Time * Time * 0.5
}
This should work. I have use this code many times and it's effiecient and also it's simple.