Friday 24 July 2009

Drag n' Drop (with momentum) in Flash



A few weeks ago I had the opportunity to attend a JISC e-learning event. A fair number of educational technology vendors were present promoting their latest products and services, but for a brief moment one thing caught my eye. The SMART Technologies SMART Table.

It wasn't so much the idea of the SMART Table that intrigued me but the interactivity it offered. The e-learning game running on the table at the time was a diagram of the human body on which the students would have to correctly identify the different body parts by dragging labels onto them from the edge of the screen. As they were dragged the labels would turn as though being pulled by little strings, then when released would continue on to a gentle stop as though they had their own momentum.

I thought that idea of dragging and dropping labels that had weight and momentum was really cool, and I immediately realised that you could do a similar thing using flash.

Well, now that the first evening of my holiday is here I have begun work and have a basic solution for the momentum part of it (I will add the turning and following part another day when that is done).

So here it is, drag and drop, with momentum, in Flash:

Here's it is







If you are not yet an AS3 whizz or using an older version of Flash, don't worry, this one uses AS2.

Let's get going.

1. Create a new AS2 document.

2. Create a shape on the stage, select it and hit F8, then turn it into a MovieClip

3. Give the new MovieClip the instance name of object_mc in the Properties panel.

And that's the easy bit done. Now for the ActionScript.

4. Create a new layer in the timeline and give it the name Actions.

5. Select the first frame of the Actions layer, then hit F9 to open the Actions panel.

5. Input the following code into the Actions panel (hopefully my comments will explain everything, if not, leave a comment):

/* set some variables we will use later */

var posBh = 0;
var posBv = 0;
var posAh = 0;
var posAv = 0;
var trajh = 0;
var trajv = 0;
var drag = 0;


/* sets the level of friction, the higher the number, the more friction.*/

var friction = 1.35;

/*We make the object draggable, and set the drag value to 1*/


object_mc.onPress = function() {
this.startDrag();
drag = 1;
};


/*We stop dragging and set the drag value to 0*/

object_mc.onRelease = function() {
this.stopDrag();
drag = 0;
};


/*This part controls the momentum of the drag on stopDrag()*/

onEnterFrame = function () {

/*If we are dragging the object (and we can tell we are because drag == 1) then we gather information on the trajectory of the drag for ready for when we stop drag. We do this by comparing the X and Y position of the object between frames and so finding out its X and Y velocity. This is stored in the variables trajh and trajv.*/

if (drag == 1) {

/* Calculate Y trajectory/velocity*/

posBv = posAv;
posAv = _ymouse;
trajv = posBv-posAv;


/* Calculate X trajectory/velocity*/

posBh = posAh;
posAh = _xmouse;
trajh = posBh-posAh;
}


/*If the object has stopped being dragged (and we can tell because drag == 0) we then apply some momentum so the object keeps moving after it is let go. We do this by telling the X and Y position of the object to keep moving each frame with the value of the X and Y velocity as stored in the variables trajh and trajv. However we also apply some friction by reducing the value of trajh and trajv a little on each frame. We do this by dividing the variables trajh and trajv by the friction value on every frame and then setting trajh and trajv to have this new value.*/

if (drag == 0) {
trajh = (trajh/friction);
trajv = (trajv/friction);
_root.object_mc._x -= (trajh);
_root.object_mc._y -= (trajv);
}
};


And that's your lot.

CTRL ENTER to test your movie. You will find you can click and drag the object on the stage and if you let go while moving it will carry on with momentum but come to a gradual halt.

You can increase or reduce the amount of friction the object encounters on release by changing the value of the friction variable. Remember, the more friction the quick it slows, the less friction further it travels (like on ice). Have fun experimenting.

Summary

Although this was inspired by my experience of the SMART Table, it won't ever be that versatile because while the SMART table can handle several kids all working at once, Flash will always be limited by the number of "mouse" type inputs your computer supports at once. Usually one.

But we interactive media types don't care for such trifling limitations. Inspiration is one thing, and application is another. I don't have to make a SMART Table clone.

I can still use my flash experiment in a variety of ways to spice up my interactive applications, so the inspiration and the experiment was worth it.

5 comments:

  1. i wrote this into an as3 class for you. To apply this to an object, you must attach it to a movieclip. It needs to be rewritten as a decorator really.

    package{

    import flash.display.MovieClip;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.Rectangle;

    public class Momentum extends MovieClip{

    /* set some variables we will use later for drag momentum*/

    private var posAv:int = 0;
    private var posBv:int = 0;

    private var posAh:int = 0;
    private var posBh:int = 0;

    private var trajh:int = 0;
    private var trajv:int = 0;

    private var drag:int = 0;

    //NOTE: the higher the framerate, the lower the friction needs to be;
    private var friction:Number = 1.25;



    public function Momentum():void{
    addEventListener(MouseEvent.MOUSE_DOWN, dragger);
    addEventListener(MouseEvent.MOUSE_UP, dropper);
    addEventListener(Event.MOUSE_LEAVE, dropper);
    }

    private function dragger(e:MouseEvent):void{
    /*We make the object draggable, and set the drag value to 1*/
    /*If we are dragging the object then we gather
    information on the trajectory of the drag for ready
    for when we stop drag.
    We do this by comparing the X and Y position of the
    object between frames and so finding out its X and Y
    velocity. This is stored in the variables trajh and
    trajv.*/
    trace("drag starting");
    startDrag();
    drag = 1;
    addEventListener(Event.ENTER_FRAME, momentum);

    }
    /*We stop dragging and set the drag value to 0*/

    private function dropper(e:MouseEvent):void{
    /*If the object has stopped being dragged we then
    apply some momentum so the object keeps moving after
    it is let go. We do this by telling the X and Y
    position of the object to keep moving each frame
    with the value of the X and Y velocity as stored
    in the variables trajh and trajv. However we also
    apply some friction by reducing the value of trajh
    and trajv a little on each frame. We do this by
    dividing the variables trajh and trajv by the friction
    value on every frame and then setting trajh and trajv
    to have this new value.*/
    stopDrag();
    drag = 0;
    }


    private function momentum(e:Event):void{

    if (drag==1){
    trace("recording trajectory");

    /* Calculate Y trajectory/velocity*/
    posBv = posAv;
    posAv = stage.mouseY;
    trajv = posBv-posAv;
    /* Calculate X trajectory/velocity*/
    posBh = posAh;
    posAh = stage.mouseX;
    trajh = posBh-posAh;
    trace("v:"+trajv);
    trace("h:"+trajh);

    }else if(drag==0){
    trace("moving to final trajectory");

    trajh = trajh/friction;
    trajv = trajv/friction;
    trace("v:"+trajv);
    trace("h:"+trajh);
    x -= Math.round(trajh);
    y -= Math.round(trajv);
    if (trajh==0 && trajv==0){
    trace("got to destination");
    removeEventListener(Event.ENTER_FRAME, momentum);
    }
    }
    }
    }
    }

    ReplyDelete
  2. That's groovy plumpNation. Thanks. Loads of readers will find this valuable.

    ReplyDelete
  3. This will allow you to just pass in a MovieClip, it is very rough and needs a tidy...


    package{

    import flash.display.MovieClip;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.Rectangle;

    public class DragMomentum extends MovieClip{

    /* set some variables we will use later for drag DragMomentum*/

    public var targetObject:Sprite = new Sprite;

    private var posAv:int = 0;
    private var posBv:int = 0;

    private var posAh:int = 0;
    private var posBh:int = 0;

    private var trajh:int = 0;
    private var trajv:int = 0;

    private var drag:int = 0;

    //NOTE: the higher the framerate, the lower the friction needs to be;
    private var friction:Number = 1.25;



    public function DragMomentum(target:Sprite):void{
    targetObject = target;
    trace(targetObject);
    targetObject.addEventListener(MouseEvent.MOUSE_DOWN, dragger);
    targetObject.addEventListener(MouseEvent.MOUSE_UP, dropper);
    targetObject.addEventListener(Event.MOUSE_LEAVE, dropper);
    }

    private function dragger(e:MouseEvent):void{
    /*We make the object draggable, and set the drag value to 1*/
    /*If we are dragging the object then we gather
    information on the trajectory of the drag for ready
    for when we stop drag.
    We do this by comparing the X and Y position of the
    object between frames and so finding out its X and Y
    velocity. This is stored in the variables trajh and
    trajv.*/
    trace("drag starting"+e.target);
    e.target.parent.startDrag();
    drag = 1;
    addEventListener(Event.ENTER_FRAME, momentum);

    }
    /*We stop dragging and set the drag value to 0*/

    private function dropper(e:MouseEvent):void{
    /*If the object has stopped being dragged we then
    apply some momentum so the object keeps moving after
    it is let go. We do this by telling the X and Y
    position of the object to keep moving each frame
    with the value of the X and Y velocity as stored
    in the variables trajh and trajv. However we also
    apply some friction by reducing the value of trajh
    and trajv a little on each frame. We do this by
    dividing the variables trajh and trajv by the friction
    value on every frame and then setting trajh and trajv
    to have this new value.*/
    e.target.stopDrag();
    drag = 0;
    }


    private function momentum(e:Event):void{

    if (drag==1){
    trace("recording trajectory");

    /* Calculate Y trajectory/velocity*/
    posBv = posAv;
    posAv = mouseY;
    trajv = posBv-posAv;
    /* Calculate X trajectory/velocity*/
    posBh = posAh;
    posAh = mouseX;
    trajh = posBh-posAh;
    trace("v:"+trajv);
    trace("h:"+trajh);

    }else if(drag==0){
    trace("moving to final trajectory");

    trajh = trajh/friction;
    trajv = trajv/friction;
    trace("v:"+trajv);
    trace("h:"+trajh);
    targetObject.x -= Math.round(trajh);
    targetObject.y -= Math.round(trajv);
    if (trajh==0 && trajv==0){
    trace("got to destination");
    removeEventListener(Event.ENTER_FRAME, momentum);
    }
    }
    }
    }
    }

    ReplyDelete
  4. That's ace Scott, thanks for sharing your solution. This one seems to have captured the imagination. Thanks.

    ReplyDelete
  5. Very nice, except I googled my way here looking for a Javascript version of this. Any chance you feel like porting it? :)

    ReplyDelete