Saturday, August 7, 2010

Drag objects smoothly using Actionscript 3

Today we will learn how to click and drag objects, smoothly!

What do I want to do:

- Make smooth click'n'drag function
- Make it easy to make objects draggable
- Make objects that are dragged overlap all other draggables

Result:



How are we going to accomplish that?

We will need to create a function that handles all the dragging of one object, and then call this function several times with different parameters. Our paremeter will be the instance of our object on stage, so the function to add a draggable object will look like this:

draggableObject(mc_instance_name);
draggableObject(mc_instance_name2);
draggableObject(mc_instance_name3);
// etc

The first thing we have to do is to make the smooth movement. I use this method:

function dragger(Event)
 {
  mc.x+=(mouseX-mc.x)/3;
  mc.y+=(mouseY-mc.y)/3;
 }

The most difficult part now: how do we make it only move when we click and hold, and stop when we release mouse? The most obvious thing would be to make 2 listeners MOUSE_DOWN and MOUSE_UP for our object, but this wont work here. Let me explain:

If we have smooth movement, the mouse cursor will often be ahead of our object, meaning we can click outside the object. If we do that, we already made a MOUSE_UP, however the object won't stop moving because the action wasn't performed ON the object, but outside it, and to drop it we would have to click on it again.

How do we fix this?

We will use both MOUSE_DOWN and MOUSE_UP, both one inside our function (draggableObject) and one outside (global). First the outside one:

var mouseHold = false;
stage.addEventListener(MouseEvent.MOUSE_UP, mUp);

function mUp(MouseEvent)
{
 mouseHold = false;
}

Now let's take a look at our draggableObject function and look where to put the MOUSE_DOWN listener:

// The unfinished function we have right now:
function draggableObject(mc)
{
 mc.addEventListener(Event.ENTER_FRAME, drag);

 function drag(MouseEvent)
 {

  if (mouseHold == true)
  {
   mc.addEventListener(Event.ENTER_FRAME, dragger);
  }

  if (mouseHold == false)
  {
   mc.removeEventListener(Event.ENTER_FRAME, dragger);
  }

 }

 function dragger(Event)
 {
  mc.x+=(mouseX-mc.x)/3;
  mc.y+=(mouseY-mc.y)/3;
 }

}

Add the MOUSE_DOWN listener in the beginning. Also, if we have many draggable objects on screen, we have to make sure only one can be dragged at the same time. So we need another variable - mouseOnThisObject, that tells us whether we are dragging this object or not.

And now the full code:

var mouseHold = false;
stage.addEventListener(MouseEvent.MOUSE_UP, mUp);

function mUp(MouseEvent)
{
 mouseHold = false;
}

function draggableObject(mc)
{
 var mouseOnThisObject = false;

 mc.addEventListener(Event.ENTER_FRAME, drag);
 mc.addEventListener(MouseEvent.MOUSE_DOWN, mDown);
 function mDown(MouseEvent)
 {
  mouseHold = true;
  mouseOnThisObject = true;
 }

 function drag(MouseEvent)
 {

  if (mouseHold == true && mouseOnThisObject == true)
  {
   mc.addEventListener(Event.ENTER_FRAME, dragger);
  }

  if (mouseHold == false)
  {
   mc.removeEventListener(Event.ENTER_FRAME, dragger);
   mouseOnThisObject = false;
  }

 }

 function dragger(Event)
 {
  mc.x+=(mouseX-mc.x)/3;
  mc.y+=(mouseY-mc.y)/3;
 }

}

Good!

Now every time you need to make an object draggable, just call this function and provide the object's instance name.

draggableObject(object1);
draggableObject(object2);

It all works, but we want to put the object that is being dragged on top of others. We can do that by providing an array of all draggable objects and using swapChildren and getChildIndex. Create an array in the beginning of your code:

var allDraggables:Array = new Array();

Now we should add a code in the dragger function that checks if the MC hits anything and if its depth is lower than the object it hits, swap children depths:

for (var i:int=0; i<allDraggables.length; i++){
  if(mc.hitTestObject(allDraggables[i]) && getChildIndex(allDraggables[i]) > getChildIndex(mc)){
   swapChildren(allDraggables[i], mc)
   }
  }

And here goes the full code:

var allDraggables:Array = new Array();
var mouseHold = false;
stage.addEventListener(MouseEvent.MOUSE_UP, mUp);

function mUp(MouseEvent)
{
 mouseHold = false;
}

function draggableObject(mc)
{
 var mouseOnThisObject = false;
 allDraggables.push(mc);
 mc.addEventListener(Event.ENTER_FRAME, drag);
 mc.addEventListener(MouseEvent.MOUSE_DOWN, mDown);
 function mDown(MouseEvent)
 {
  mouseHold = true;
  mouseOnThisObject = true;
 }

 function drag(MouseEvent)
 {

  if (mouseHold == true && mouseOnThisObject == true)
  {
   mc.addEventListener(Event.ENTER_FRAME, dragger);
  }

  if (mouseHold == false)
  {
   mc.removeEventListener(Event.ENTER_FRAME, dragger);
   mouseOnThisObject = false;
  }
  

 }

 function dragger(Event)
 {
  mc.x+=(mouseX-mc.x)/3;
  mc.y+=(mouseY-mc.y)/3;
  
  for (var i:int=0; i<allDraggables.length; i++){
  if(mc.hitTestObject(allDraggables[i]) && getChildIndex(allDraggables[i]) > getChildIndex(mc)){
   swapChildren(allDraggables[i], mc)
   }
  }
 }

}

draggableObject(object1);
draggableObject(object2);

Thanks for reading!

2 comments:

Jack said...

Works great!
Thanks for a very useful tutorial.

Jack

Anonymous said...

Thank you. I made this with movieclips containing videos. It's working perfectly, but I want my videos to loop. What should I do?

Post a Comment