Is there a way to have a MovieClip with startDrag, but to force only horizontal and vertical (i.e. not diagonal) movement?
Here is my solution. It tracks the mouseX and mouseY on click and compares it to last position. Finds out which direction the mouse is mainly moving then moves the object there. You may want to add some extra logic to lock the object to the nearest 10th unit or whatever unit size you want to form a snap grid for use in games or organized placement of the object.
Update: I went ahead and added a snapNearest function to help control the movement.
import flash.events.MouseEvent;
import flash.events.Event;
dragObj.addEventListener(MouseEvent.MOUSE_DOWN, dragIt);
var curX:Number = 0;
var curY:Number = 0;
var oldX:Number = 0;
var oldY:Number = 0;
var gridUnit:Number = 25;
function dragIt(e:MouseEvent):void
{
// set x,y on down
oldX = mouseX;
oldY = mouseY;
// add mouse up listener so you know when it is released
stage.addEventListener(MouseEvent.MOUSE_UP, dropIt);
stage.addEventListener(Event.ENTER_FRAME, moveIt);
trace("Start Drag")
}
function moveIt(e:Event):void
{
// figure out what the main drag direction is and move the object.
curX = mouseX;
curY = mouseY;
// figure out which is the larger number and subtract the smaller to get diff
var xDiff:Number = curX > oldX ? curX - oldX : oldX - curX;
var yDiff:Number = curY > oldY ? curY - oldY : oldY - curY;
if(xDiff > yDiff) {
dragObj.x = snapNearest(mouseX, gridUnit);
}else{
dragObj.y = snapNearest(mouseY, gridUnit);
}
oldX = mouseX;
oldY = mouseY;
}
function dropIt(e:MouseEvent):void
{
//remove mouse up event
stage.removeEventListener(MouseEvent.MOUSE_UP, dropIt);
stage.removeEventListener(Event.ENTER_FRAME, moveIt);
trace("Stop Drag")
}
// snap to grid
function snapNearest(n:Number, units:Number):Number
{
var num:Number = n/units ;
num = Math.round(num);
num *= units;
return num;
}
yes. there are a few options.
A. you can choose to use the startDrag() function and supply it's 2nd parameter with a bounds rectangle for your draggable object. something like;
dragSprite.startDrag(false, new Rectangle(0, 0, 20, stage.stageHeight));
B. you can set your draggable object to listen for mouse events, moving it according to the mouse movements. something like:
dragSprite.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownEventHandler);
function mouseDownEventHandler(evt:MouseEvent):void
{
stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveEventHandler);
stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpEventHandler);
}
function mouseMoveEventHandler(evt:MouseEvent):void
{
//only move dragSprite horizontally
//dragSprite.y = evt.stageY;
dragSprite.x = evt.stageX;
}
function mouseUpEventHandler(evt:MouseEvent):void
{
stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveEventHandler);
stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpEventHandler);
}
You could use a modifier key, for instance normal behavior would be horizontal & press down the shift key to move vertically.
function mouseMoveEventHandler(evt:MouseEvent):void
{
if(!evt.shiftKey)
dragSprite.x = evt.stageX;
else
dragSprite.y = evt.stageY;
}
You can only constrain to one axis or the other (using a constraint rectangle) But diagonal movement would be possible in the method you propose, unless you also define some other limits... for example a grid.
Related
I am creating a drag and drop the game for kids and I realized that when the kid drags a movie clip by mistake outside the stage the movie clip hangs where it is dragged onto and doesn't return back to its original position or it overlaps over other movie clips.
Thanks in advance
the code I am basically using is:
* square_mc.addEventListener(MouseEvent.MOUSE_DOWN, pickUp2);
square_mc.addEventListener(MouseEvent.MOUSE_UP, dropIt2);
function pickUp2(event: MouseEvent): void {
square_mc.startDrag(true);
event.target.parent.addChild(event.target);
startX = event.currentTarget.x;
startY = event.currentTarget.y;
}
function dropIt2(event: MouseEvent): void {
square_mc.stopDrag();
var myTargetName:String = "target" + event.target.name;
var myTarget:DisplayObject = getChildByName(myTargetName);
if (event.target.dropTarget != null && event.target.dropTarget.parent ==
myTarget){
event.target.removeEventListener(MouseEvent.MOUSE_DOWN, pickUp2);
event.target.removeEventListener(MouseEvent.MOUSE_UP, dropIt2);
event.target.buttonMode = false;
event.target.x = myTarget.x;
event.target.y = myTarget.y
} else {
event.target.x = startX;
event.target.y = startY;
}
}*
What you need to do is to preserve the initial position of the clip somewhere and then use it.
Basic algorithm I can think of can look as follows: 1 - player starts dragging a clip, init position is saved 2 - player releases the clip.
Two outcomes are possible: (a): the clip is on the right position or (b): it is misplaced. In case (b) you just need to assign initial coordinates back to the clip.
Very simple example:
private const initialCoords: Dictionary = new Dictionary();
private function saveInitialCoords(clip: DisplayObject): void {
initialCoords[clip] = new Point(clip.x, clip.y);
}
private funciton retreiveInitialCoords(clip: DisplayObject): Point {
return initialCoords[clip]; // it would return null if there is no coords
}
UPD: As it was pointed by #kaarto my answer might be irrelevant. If the issue is to keep the dragging movieclip
within some bounds I usually use something like this:
private var currentTarget: DisplayObject; // clip we're dragging currently
private var areaBounds: Rectangle = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight); // you might want to ensure stage is not null.
private function startDrag(e: MouseEvent): void {
// I'll drop all checks here. Normally you want to ensure that currentTarget is null or return it to initial position otherwise
currentTarget = e.currentTarget as DisplayObject;
saveInitialCoords(currentTarget);
addEventListener(MouseEvent.MOUSE_MOVE, checkAreaBounds);
}
private funciton stopDrag(e: MouseEvent): void {
// some checks were dropped.
if (clipIsOnDesiredPlace(currentTarget)) {
// do something
} else {
const initialCoords: Point = retreiveInitialCoords(currentTarget);
currentTarget.x = initialCoords.x;
currentTarget.y = initialCoords.y;
}
currentTarget = null;
removeEventListener(MouseEvent.MOUSE_MOVE, checkAreaBounds);
}
private funciton checkAreaBounds(e: MouseEvent): void {
// you might need to convert your coords localToGlobal depending on your hierarchy
// this function gets called constantly while mouse is moving. So your clip won't leave areaBounds
var newX: Number = e.stageX;
var newY: Number = e.stageY;
if (currentTarget) {
if (newX < areaBounds.x) newX = areaBounds.x;
if (newY < areaBounds.y) newY = areaBounds.y;
if (newX + currentTarget.width > areaBounds.x + areaBounds.width) newX = areaBounds.x + areaBounds.width - currentTarget.width;
if (newY + currentTarget.height > areaBounds.y + areaBounds.heght) newY = areaBounds.y + areaBounds.height - currentTarget.height;
currentTarget.x = newX;
currentTarget.y = newY;
}
}
... somewhere ...
// for each clip you're going to interact with:
for each (var c: DisplayObject in myDraggableClips) {
c.addEventListener(MouseEvent.MOUSE_DOWN, startDrag);
c.addEventListener(MouseEvent.MOUSE_UP, stopDrag);
}
Another option (if you don't want to limit the area with some bounds) is to use MouseEvent.RELEASE_OUTSIDE event and return a clip to it's initial position once it gets released:
stage.addEventListener(MouseEvent.RELEASE_OUTSIDE, onMouseReleaseOutside);
private function onMouseReleaseOutside(e:MouseEvent):void {
// return clip to it's position.
}
More info you can find in documentation: https://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/events/MouseEvent.html#RELEASE_OUTSIDE
I have dragging mc called "box" but the dragging is not smooth at all.
So how can I make it smoother by using "ease" var.
I'm trying to use "/ease" anywhere but not work.
var topY:int = stage.stageHeight - box.height;
var botY:int = 0;
box.addEventListener(MouseEvent.MOUSE_DOWN, onDown);
box.addEventListener(MouseEvent.MOUSE_UP, onUp);
var constrainY:Rectangle = new Rectangle(box.x, topY ,0, box.y+ (box.height-stage.stageHeight) );
var dragxy:String = "";
function onDown(e:MouseEvent):void
{
dragxy = mouseX + "_" + mouseY;
e.currentTarget.startDrag(false, constrainY);
removeEventListener(MouseEvent.MOUSE_DOWN, onDown);
addEventListener(MouseEvent.MOUSE_UP, onUp);
}
function onUp(e:MouseEvent):void
{
e.currentTarget.stopDrag();
addEventListener(MouseEvent.MOUSE_DOWN, onDown);
removeEventListener(MouseEvent.MOUSE_UP, onUp);
}
a simple example of an easing function would be:
private function easeTo(obj:MovieClip,currentGoalx:Number,currentGoaly:Number,easeFac:Number):void{
obj.x += (currentGoalx-obj.x)/easeFac;
obj.y += (currentGoaly-obj.y)/easeFac;
}
first you will need a timer or enter frame loop such as this added to the stage:
addEventListener(Event.ENTER_FRAME,enterFrame);
then in your enterFrame function do this:
private function enterFrame(e:Event):void{
easeTo(box,stage.mouseX,stage.mouseY,3);
}
an easeFac of 3 is a slower and smoother ease-in than an easeFac of 2, and 4 is even smoother and buttery-er.
or something like that. The idea is that the obj goes half the distance to the goal on each function call. So if this function is getting called on every frame, you'll get a smooth "ease in" effect. If you set currentGoal as the mouse position, then the obj will follow the mouse wherever it goes, but not in a 1:1 matching, rather it will trail behind and smoothly catch up.
I'm trying to make an object, a red circle move bounce to a random location within my stage onCLick, and display the message you touched my circle. I don't quite know what i'm doing wrong.
Here is what i came up with
import flash.events.MouseEvent;
myCircle.addEventListener(MouseEvent.MOUSE_DOWN, onClick);
function onClick(e:MouseEvent):void
{
trace("you touched myCircle");
Math.floor(Math.random()*(1+High-Low))+Low;
}
var High = stage.stageWidth == 550, stage.stageHeight == 400;
var Low = stage.stageWidth == 0, stage.stageHeight == 0;
var HighH:int=stage.stageHeight;
var HighW:int=stage.stageWidth;
var LowH:int=0; var LowW:int=0;
....
function onClick(e:MouseEvent):void
{
trace("you touched myCircle");
myCircle.x=Math.floor(Math.random()*(1+HighW-LowW))+LowW;
myCircle.y=Math.floor(Math.random()*(1+HighH-LowH))+LowH;
}
You need to set your circle's new coordinates, use x and y properties for that.
First I really want to thank you for all the help you have given me so far, since I did not know anything about AS3 (basics gotoAnd stuff only) I came to Stackoverflow searching for some code already made but I was encouraged by some members to make the code by myself, now after almost 2 weeks and thanks to a lot of great people my soccer penalty kick game is almost finished, I really love this place.
I know I have to work on some collisions and other stuff since currently the game is not the best (remember I’m just a newbie), but Unfortunately while checking the game functioning by playing it over and over again, I have found the following:
1- When you get 3 fails, then game is over and a play again button appears after some animation, you click on it and everything seems to be fine, but when you continue playing the second time you reach 3 fails, when you click the button a new cursor appears??? Please help
2- I tried millions of times to make the ball move with speed and to animate its trajectory but was unable to make it, any help on this will be highly appreciated. I have speed variables and gravity but I didn’t know how to use them
3- I'm getting a actionscript error related to a removeChild, I tried many times removing some lines but I´m unable to fix it.
4- I'm using too many timers, I don't know if this is recommendable.
Here is the .fla file https://rapidshare.com/files/1702748636/kicks.fla just in case anybody want to try the game (this is really simple since it is my 1st AS project) and want to help me with the code and help me improving the game, and here is the code if somebody does not need to get into the file (I know this place is full of really smart people), once I finish it I know I will be able to do a lot of stuff with AS3.
var score:Number;
var angle:Number;
var speed:Number;
var cursor:MovieClip;
var failed:Number;
var ballRotation:Boolean = false;
function initializeGame( ):void
{
ball.x = 296.35;
ball.y = 353.35;
score=0;
failed=0;
cursor = new Cursor();
addChild(cursor);
cursor.enabled = true;
Mouse.hide();
stage.addEventListener(MouseEvent.MOUSE_MOVE, dragCursor);
stage.addEventListener(MouseEvent.CLICK, kick);
}
function dragCursor(event:MouseEvent):void
{
cursor.x = this.mouseX;
cursor.y = this.mouseY;
}
initializeGame();
var mouse = this.Mouse;
function kick(evt:Event)
{
removeChild(cursor);
pateador_mc.play();
var timer:Timer = new Timer(500,1);
timer.addEventListener(TimerEvent.TIMER, delayedAction);
timer.start();
function delayedAction(e:TimerEvent)
{
moveBall();
}
}
speed=-100000;
var ease:int = 100;
var gravity:Number = 0.5;
function moveBall()
{
var targetX:Number = mouseX;
var targetY:Number = mouseY;
var angle = Math.atan2(targetY,targetX);
ball.x = mouseX + Math.cos(angle);
ball.y = mouseY + Math.sin(angle) ;
ballRotation = true;
stage.removeEventListener(MouseEvent.CLICK, kick);
if (ballRotation==true)
{
keeper.gotoAndStop(1 + Math.floor(Math.random() * keeper.totalFrames));
ball.play();
}
if (ball.hitTestObject ( keeper)){
ball.y=keeper.x-ball.height- ball.width;
trace ("Tomela");
}
if (ball.hitTestObject(goalie) && ball.y>69 /*&& ball.y<178 && ball.X>139 && ball.x<466*/)
{
gol_mc.play();
score ++;
showScore();
var timer3:Timer = new Timer(3000,1);
timer3.addEventListener(TimerEvent.TIMER, delayedAction3);
timer3.start();
function delayedAction3(e:TimerEvent)
{
ball.x = 296.35;
ball.y = 353.35;
stage.addEventListener(MouseEvent.CLICK, kick);
stage.addEventListener(MouseEvent.MOUSE_MOVE, dragCursor);
addChild(cursor);
keeper.gotoAndStop(1);
}
}
else
{
fails_mc.play();
failed++;
var timer2:Timer = new Timer(3000,1);
timer2.addEventListener(TimerEvent.TIMER, delayedAction2);
timer2.start();
function delayedAction2(e:TimerEvent)
{
ball.x = 296.35;
ball.y = 353.35;
stage.addEventListener(MouseEvent.MOUSE_MOVE, dragCursor);
stage.addEventListener(MouseEvent.CLICK, kick);
addChild(cursor);
keeper.gotoAndStop(1);
}
trace(failed);
if (failed==3) {
gameFinished();
trace("YOU LOST");
}
}
function showScore():void{
goles_txt.text ="" +score;
}
trace (score);
function gameFinished(){
gameOver.play ();
stage.removeEventListener(MouseEvent.CLICK, kick);
stage.addEventListener(MouseEvent.MOUSE_MOVE, dragCursor);
timer2.stop();
Mouse.show();
this.mouseX=cursor.x ;
this.mouseY=cursor.y;
again_btn.addEventListener(MouseEvent.MOUSE_DOWN, playAgain);
}
function playAgain():void{
gameOver.gotoAndPlay(31);
fails_mc.gotoAndStop(1);
keeper.play();
var timer4:Timer = new Timer(1000,1);
timer4.addEventListener(TimerEvent.TIMER, delayedAction3);
timer4.start();
function delayedAction3(e:TimerEvent)
{
initializeGame();
}
}
}
I’ll really appreciate it guys , I promise I won’t be bothering again for a long time
1/3.
Problem 1 & 3 are the same problem. Looks like your trying to remove the cursor from the stage (removeChild) every click (so it will error after the first click because it's no longer a child of anything). Your adding it back on your delayedAction2 which doesn't run unless your hit test is true and only after 3 seconds. On initialize game you create a whole new cursor and add that to the stage which is why you get a duplicate after the first game.
Rather than removeChild the cursor, it might better to just set it's visibility to false/true and only create it once.
You'll need to use an EnterFrame handler, or timer, or tween for this. I can post an example later.
I can't figure out why you're using timers at all or need to delay your functions, except maybe to allow time for the kick animation?
You're code is very disorganized, naming functions things like 'delayedAction' is bad as it doesn't really tell you anything about the purposed of the function. You also have way too much functions inside of other functions. Here is a quick refactoring of your code I've done to hopefully teach a few things. I've also added the tween for the ball animation.
import flash.events.Event;
import fl.transitions.Tween;
import fl.transitions.TweenEvent;
var score:Number;
var cursor:MovieClip;
var failed:Number;
var ballRotation:Boolean = false;
var ballTweenX:Tween;
var ballTweenY:Tween;
var targetCursor = new Cursor(); //only want one of these and you want it to exist the whole time so keep out here.
addChild(targetCursor);
initializeGame();
function initializeGame( ):void
{
stage.addEventListener(MouseEvent.MOUSE_MOVE, dragCursor);
stage.addEventListener(MouseEvent.CLICK, kick);
ball.x = 296.35;
ball.y = 353.35;
score=0;
failed=0;
targetCursor.visible = true;
Mouse.hide();
}
function dragCursor(event:MouseEvent):void
{
targetCursor.x = this.mouseX;
targetCursor.y = this.mouseY;
}
function kick(evt:Event)
{
//removeChild(targetCursor);
targetCursor.visible = false;
pateador_mc.play();
stage.removeEventListener(MouseEvent.CLICK, kick); //move this here, because you don't the option kick again while already kicking
stage.removeEventListener(MouseEvent.MOUSE_MOVE, dragCursor); //added this, you probably don't want the target moving after the click...
setTimeout(moveBall, 500);//cleaner and more efficient than using a timer for a one time delayed call.
}
function moveBall()
{
var targetX:Number = mouseX;
var targetY:Number = mouseY;
var angle = Math.atan2(targetY,targetX);
targetX = mouseX + Math.cos(angle);
targetY = mouseY + Math.sin(angle) ;
ballRotation = true;
ballTweenX = new Tween(ball, "x", null, ball.x, targetX, .3, true);
ballTweenY = new Tween(ball, "y", null, ball.y, targetY, .3, true);
ballTweenY.addEventListener(TweenEvent.MOTION_FINISH, ballTweenDone,false,0,true);
if (ballRotation==true)
{
keeper.gotoAndStop(1 + Math.floor(Math.random() * keeper.totalFrames));
ball.play();
}
}
function stopBallTween():void {
ballTweenX.stop();
ballTweenY.stop();
}
function ballTweenDone(e:TweenEvent):void {
if (ball.hitTestObject ( keeper)){
ball.y=keeper.x-ball.height- ball.width;
trace ("Tomela");
}
if (ball.hitTestObject(goalie) && ball.y>69 /*&& ball.y<178 && ball.X>139 && ball.x<466*/)
{
gol_mc.play();
score ++;
showScore();
}else
{
fails_mc.play();
failed++;
trace(failed);
if (failed==3) {
gameFinished();
trace("YOU LOST");
return; //added this because you don't want the rest of this function running if it's a game over
}
}
setTimeout(resetShot, 3000); //you had the code I put in resetShot repeated twice
trace(score);
}
function resetShot():void {
ball.x = 296.35;
ball.y = 353.35;
targetCursor.visible = true;
keeper.gotoAndStop(1);
stage.addEventListener(MouseEvent.MOUSE_MOVE, dragCursor);
stage.addEventListener(MouseEvent.CLICK, kick);
}
function showScore():void{
goles_txt.text ="" +score;
}
function gameFinished(){
gameOver.play();
stage.removeEventListener(MouseEvent.CLICK, kick);
stage.removeEventListener(MouseEvent.MOUSE_MOVE, dragCursor);
Mouse.show();
//this.mouseX=cursor.x ;
//this.mouseY=cursor.y; //These are read only properties, your can't set the mouse position...
again_btn.addEventListener(MouseEvent.MOUSE_DOWN, playAgain);
}
function playAgain(e:Event = null):void{
gameOver.gotoAndPlay(31);
fails_mc.gotoAndStop(1);
keeper.play();
setTimeout(initializeGame, 1000);
}
How can I make my object stop when it reaches the destination i gave it with my mouse click? The code makes the object move towards the point of a mouse click but I can't seem to find out how to make it stop, because it will almost never pass the specific destination point. :/ Somebody who knows how to accomplish this?
public function onMouseDown(evt:MouseEvent)
{
if (this._character != null)
{
_character.isMoving = false;
_character.dx = 0;
_character.dy = 0;
targetX = mouseX - _character.x;
targetY = mouseY - _character.y;
var angle:Number = Math.atan2(targetY,targetX);
var dx:Number = Math.cos(angle) * _character.speed;
var dy:Number = Math.sin(angle) * _character.speed;
_character.dx = dx;
_character.dy = dy;
_character.isMoving = true;
}
}
public function updateCharacter(e:Event):void
{
if (this._character.isMoving)
{
this._character.x += this._character.dx;
this._character.y += this._character.dy;
}
}
Easiest way to do it would be to calculate the angle to the point you want to stop at each time you move. This value should remain the same if you're moving in a straight line until you pass the point you're trying to stop at, at which point it will change drastically.
Once this happens, simply move your object back to the position it should have stopped at before you render it again.
I've created a demo with source code for you. There's a fair amount of code, so rather than posting everything here you can download the source instead:
http://martywallace.com/testing/gotoPoint.zip
Try this
package
{
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.MouseEvent;
public class Guest extends MovieClip
{
var walkSpeed:Number = 5;
var oldPosX;
var oldPosY;
public function Guest()
{
stage.addEventListener(MouseEvent.CLICK, walk);
}
function walk(event:MouseEvent):void
{
oldPosX = parent.mouseX;
oldPosY = parent.mouseY;
rotation = Math.atan2(oldPosY - y,oldPosX - x) / Math.PI * 180;
addEventListener(Event.ENTER_FRAME, loop);
}
function loop(event:Event):void
{
// see if you're near the target
var dx:Number = oldPosX - x;
var dy:Number = oldPosY - y;
var distance:Number = Math.sqrt((dx*dx)+(dy*dy));
if (distance<walkSpeed)
{
// if you are near the target, snap to it
x = oldPosX;
y = oldPosY;
removeEventListener(Event.ENTER_FRAME, loop);
}
else
{
x = x+Math.cos(rotation/180*Math.PI)*walkSpeed;
y = y+Math.sin(rotation/180*Math.PI)*walkSpeed;
}
}
}
}
Similar questions have been asked many times.
However, see the code in my answer here that should explain how to move and stop.
Movement of Objects in a simulation