Flash as3 Drag and Drop

Fair Warning: This swf file plays sounds!!!

This tutorial will probably be unlike anything you've seen before on the subject of Flash drag-and-drop. It begins with the things you already know (startDrag and stopDrag), then it progresses into some things you maybe had never considered before (MOUSE_UP problems, the use of a "currentClip" variable, "snapping" to a screen location, etc). It discusses how to use the built-in hit testing methods to detect hits on the target clips. Then, after the timeline code is developed to a certain point, it will show you how to turn the whole thing into a versatile, reusable class that's sure to come in handy whenever you want to make a drag-and-drop style game. In making the class, you will also see how you can employ interfaces, and also dispatch (and listen for) your own custom events! What fun!

This tutorial owes a debt of gratitude to my good friend Jessamin Swearingen, with whom I have been developing some Flash games for school kids in NYC! Hi, kids! Anyway, if you are a teacher like Jessamin, you will probably enjoy this one. Even if you aren't into AS3 programming that much, you might still want to at least grab a copy of the DragGame class, read enough to learn how to use it, and put it to work for you!

Beginning Drag and Drop

Download the above starter file. It's got three objects on the stage: square, circle, and triangle. You may recognize them if you did the previous tutorial Streamlining your AS3 code. It's certainly not a bad idea to go through that one first, because we will use the same techniques:

  1. Grouping objects into an array
  2. Using for loops to process them
  3. Sharing event listeners using event.currentTarget

If you haven't read that previous tutorial, but you are comfortable with the above techniques, read on. If not, go back and read that previous tutorial, and these techniques will be made clear to you. They are not that difficult.

We are just going to use the built-in startDrag() and stopDrag() methods to program the drag and drop for our MovieClips. Turning to our starter file, the objects on the stage already have the instance names square, circle, and triangle. The timeline already has an actions layer which is locked. A locked actions layer only prevents you from putting graphics on the layer, it doesn't stop you from putting actions on its frames. So click on the first frame of the actions layer and press F9 to bring up the actions panel. Either type in or copy and paste the following code block:

var dragArray:Array = [square, circle, triangle];

for(var i:int = 0; i < dragArray.length; i++) {
	dragArray[i].buttonMode = true;
	dragArray[i].addEventListener(MouseEvent.MOUSE_DOWN, item_onMouseDown);
	dragArray[i].addEventListener(MouseEvent.MOUSE_UP, item_onMouseUp);
}

function item_onMouseDown(event:MouseEvent):void {
	var clip:MovieClip = MovieClip(event.currentTarget);
	addChild(clip); //bring to the front
	clip.startDrag();
}
function item_onMouseUp(event:MouseEvent):void {
	var clip:MovieClip = MovieClip(event.currentTarget);
	clip.stopDrag();
}

The first thing we do is to put our three objects into an array. Since we are going to be dragging them, we'll call the array dragArray. Next, we set up a for loop that loops through the array, adding buttonMode to the objects so that they get a hand cursor. We also add event listeners for MOUSE_DOWN and MOUSE_UP to all the objects.

In these event handler functions, notice that the first thing we do is to create a variable named "clip" to serve as a reference to the event.currentTarget, and we cast it as a MovieClip. Creating this variable (and casting it as a MovieClip type) is an optional step, but it can be kind of handy. For one thing, "clip" is a whole lot easier to type than "event.currentTarget." For another thing, casting can sometimes prevent some kinds of compiler errors. And thirdly, by casting to a MovieClip, when we go on programming, we get MovieClip code hints when we type "clip" and then a dot.

The "clip" variable is a local variable whose value is lost when the function completes. You can tell local variables because they use the var keyword inside a function. This is also why we can reuse the word "clip" as a variable name inside the two functions. They are completely separate variables from each other. They each point to their own function's event.currentTarget, which, in turn, is a reference to the square, the circle, or the triangle, whichever one is being acted upon.

In the MOUSE_DOWN handler, saying addChild(clip); causes whichever movie clip instance got the event to be added to the display list. Even though the clip in question is already on the display list, adding it again does no harm, and has the effect of bringing it to the front and layered over everything else. That way, the clip being dragged will always be in front of all the others.

Here is how the swf now behaves when we test the movie:

Notice that the item being dragged is always in front of the other two. And while this state of the program isn't too bad, it has a small problem: Try dragging an item, then let your mouse wander outside the swf window, then let go of the mouse button. You will find when your cursor returns to the window that the dragging item is now "stuck" to the mouse cursor, until you click again. This is because we added MOUSE_UP to the object itself. The MOUSE_UP event didn't register because the cursor was outside the window when the mouse button was released. In fact, the MOUSE_UP event doesn't register unless the mouse cursor is directly over the object.

This can also happen when the user drags too fast, and happens to release the mouse button ahead of the graphics. While that one is a bit harder to demonstrate and reproduce, I have seen it happen.

The solution to the above problem is to add the event listener for MOUSE_UP to the stage. That way the event registers no matter where we release the mouse button, even outside the window. We will explore this solution on the next page.

Here's the same starter file, with the above code added to it: flash_drag_drop_2_simple.fla. However, if you are following along, and updating your own file, all subsequent downloads are just provided for your convenience, or for those who like progressive versions.

Using a currentClip variable

Now we are going to change the event listener for MOUSE_UP, and add it to the stage instead of to the movie clip instances. However, we have to be careful with this event listener, because it will stay in effect until we turn it off, and it will fire even if we are not dragging anything. For example, we might be clicking on something else entirely. In a larger program, this event would even fire in other sections of the program, if we don't remove it when we're done with it. Therefore, the thing to do is to (very cleverly) add this event listener inside the handler function for the MOUSE_DOWN event, and then turn it off again in the handler for the MOUSE_UP event. That way listening for this event is only in effect when we are dragging.

Another problem, though, is that by adding the event listener to the stage, now we lose our reference (inside the handler function) to the clip being dragged! The event.currentTarget variable refers to the object that the listener was added to, which is now the stage, and not the clip being dragged. But we still need a way to tell the clip to stop dragging. Somehow!

One answer to this dilemma that I have come up with is to create a variable called currentClip. We declare it with the var keyword outside of any functions, so that it can be available inside all of the functions. We will use this variable instead of the former local variable named clip. In contrast to clip, currentClip will have a wider scope. When we refer to currentClip inside of the functions, we never use the var keyword on it, as that would create a completely different local variable with the same name, which would get thrown away at the end of the function, totally defeating our purpose. What a difference the var keyword makes! It establishes the scope of the variable.

Anyway, the idea of using a currentClip variable is simple! Whenever a movie clip gets the MOUSE_DOWN event, it gets assigned to this variable. In effect, it becomes the currentClip. Then, when the stage gets the MOUSE_UP event, we can just tell the currentClip to stop dragging. When we then mouse down on another clip to start dragging it, then it becomes the currentClip and not the first one anymore! So quite obviously, all of our movie clip instances can just take turns "being" the currentClip, and telling currentClip to stop dragging can mean, "whichever one you are." Like, "HEY YOU! Stop dragging!"

The currentClip can be a different movie clip at different times, just like thePresident can be a different man at different times!

This kind of dynamic programming is possible because variable names are really just pointers to the real objects in memory, and so they can be assigned and re-assigned dynamically. Once the variable name is assigned to a real object, the object can then be manipulated at will. Anyway, here is the new code that does all that:

var dragArray:Array = [square, circle, triangle];
var currentClip:MovieClip;

for(var i:int = 0; i < dragArray.length; i++) {
	dragArray[i].buttonMode = true;
	dragArray[i].addEventListener(MouseEvent.MOUSE_DOWN, item_onMouseDown);
}

function item_onMouseDown(event:MouseEvent):void {
	currentClip = MovieClip(event.currentTarget);
	addChild(currentClip); //bring to the front
	currentClip.startDrag();
	stage.addEventListener(MouseEvent.MOUSE_UP, stage_onMouseUp);
}

function stage_onMouseUp(event:MouseEvent):void {
	stage.removeEventListener(MouseEvent.MOUSE_UP, stage_onMouseUp);
	currentClip.stopDrag();
}

Here is how the swf now behaves:

 

Now the objects don't stick to the mouse cursor anymore! But now we have a different problem. The objects drop when we release the mouse, even if the mouse cursor is outside of the window. Consequently, the objects drop outside the window, too, and, if they are too far from the edge, there is no way to get them back. However, we are now on the right track, and we'll solve that problem too, on the next page.

Sending a clip back to its origin

Next, we will make the movie clip instance being dragged "snap back" to its origin when it is dropped. At first, this will happen unconditionally. Later we will program it to snap back only if the user didn't drop it where they were supposed to.

To accomplish this, we introduce two more new variables, both of the Number type (I always use the Number type for screen locations, as everything is not always located on an even pixel). One variable is for recording the x location, and the other, the y location, of the movie clip being dragged. We will call these variables startX and startY. No special significance to these names, I just made them up, but you always want descriptive names. You might be inclined to think that you would need a set of variables like this for each movie clip instance, or that they should even have those as properties. But since only one clip can be dragged at a time, they can just all share these two variables.

The strategy is to just simply record the starting position of the movie clip that gets the MOUSE_DOWN event. Therefore, inside the MOUSE_DOWN event handler is where we are going to record values for these variables. We don't even need to worry in our programming about what the actual numbers are, or where any movie clips are positioned. Whatever that position is, it gets recorded when the user mouses down on it. This also allows us to move them around at will when we are authoring the fla file, and the code will still work.

Here is the new code with startX and startY added in:

var dragArray:Array = [square, circle, triangle];
var currentClip:MovieClip;
var startX:Number;
var startY:Number;

for(var i:int = 0; i < dragArray.length; i++) {
	dragArray[i].buttonMode = true;
	dragArray[i].addEventListener(MouseEvent.MOUSE_DOWN, item_onMouseDown);
}

function item_onMouseDown(event:MouseEvent):void {
	currentClip = MovieClip(event.currentTarget);
	startX = currentClip.x;
	startY = currentClip.y;
	addChild(currentClip); //bring to the front
	currentClip.startDrag();
	stage.addEventListener(MouseEvent.MOUSE_UP, stage_onMouseUp);
}

function stage_onMouseUp(event:MouseEvent):void {
	stage.removeEventListener(MouseEvent.MOUSE_UP, stage_onMouseUp);
	currentClip.stopDrag();
	currentClip.x = startX;
	currentClip.y = startY;
}

And here is how the swf now behaves:

When you release the mouse button, the movie clip instances "snap back" to where they started from. On the next page, we will introduce conditional logic. The clip will only snap back to its origin if the user doesn't drop it where we think they should.

Dragging clips to a target - hitTestObject

Now we are going to introduce some target movie clips. We are getting closer to being able to make a drag-and-drop game with some intended purpose.

The first kind of drag-and-drop game we will make is one in which you have to drag a clip to a hidden matching clip of the same size and shape. This kind of game is one in which the user has to put together some kind of picture or map. In other words, by dragging clips they are assembling parts into some kind of overall thing. We'll call it a build game.

We'll create three matching clips. In fact, we'll use the same symbols in the library and just make a new instance of each one. So drag another square, circle , and triangle out of the library, and give them instance names of squareMatch, circleMatch, and triangleMatch. These clips will be mostly hidden from view using their alpha property. We'll just set their alpha to 0.2 (20%) for our purposes, so that you can still see where to drag the clips to (line 11 in the code below). But when making a real game, you might actually set their alpha to 0. But you would need some kind of gimmick, like an overall outline, to let the user know the general area where they need to drag the parts to.

The three clips we just introduced are placed into an array called matchArray. We need to order the parts in the array so that each clip in the dragArray has its counterpart, or match, in the matchArray. But on the screen, of course, they don't have to be in any certain order. The code works no matter where they are. Here's our new code:

var dragArray:Array = [square, circle, triangle];
var matchArray:Array = [squareMatch, circleMatch, triangleMatch];

var currentClip:MovieClip;
var startX:Number;
var startY:Number;

for(var i:int = 0; i < dragArray.length; i++) {
	dragArray[i].buttonMode = true;
	dragArray[i].addEventListener(MouseEvent.MOUSE_DOWN, item_onMouseDown);
	matchArray[i].alpha = 0.2;
}

function item_onMouseDown(event:MouseEvent):void {
	currentClip = MovieClip(event.currentTarget);
	startX = currentClip.x;
	startY = currentClip.y;
	addChild(currentClip); //bring to the front
	currentClip.startDrag();
	stage.addEventListener(MouseEvent.MOUSE_UP, stage_onMouseUp);
}

function stage_onMouseUp(event:MouseEvent):void {
	stage.removeEventListener(MouseEvent.MOUSE_UP, stage_onMouseUp);
	currentClip.stopDrag();
	var index:int = dragArray.indexOf(currentClip);
	var matchClip:MovieClip = MovieClip(matchArray[index]);
	if(currentClip.hitTestObject(matchClip)) {
		//a match was made! position the clip on the matching clip:
		currentClip.x = matchClip.x;
		currentClip.y = matchClip.y;
		//make it not draggable anymore:
		currentClip.removeEventListener(MouseEvent.MOUSE_DOWN, item_onMouseDown);
		currentClip.buttonMode = false;
	} else {
		//match was not made, so send the clip back where it started:
		currentClip.x = startX;
		currentClip.y = startY;
	}
}

This should be fairly straightforward. In the initial for loop, we add a line that affects the items in the matchArray. We don't need to create a new loop, since dragArray and matchArray must always have the same number of items, we can just use the existing loop to affect the matchArray, too.

The other interesting stuff is going on in the stage_onMouseUp function. At this point, the user has let go of the mouse after having dragged a movie clip. Our first task is to identify the location in the dragArray of the currentClip. This is accomplished by using the indexOf() method of the array class. The indexOf() method finds out which array element matches the argument given to it in the parentheses. So if we say dragArray.indexOf(currentClip) it will compare every item in the dragArray and find out which array element matches currentClip, and return its index number into the array. This is very handy, it saves us from having to loop through the array ourselves. With our small number of array items, it will return either 0, 1, or 2, depending on whether the square, circle, or triangle was being dragged.

The reason for finding out this index number is so that we can find out which clip in the matchArray that currentClip ought to be a match for. As long as we always keep the two arrays parallel, with the same number of items, and arranged so that each clip in the dragArray is at the same position in the array that its counterpart occupies in the matchArray, this system works great (and it works just as well with a few items as it would with tens or hundreds of them)! So, anyway, when we find out which clip in the matchArray is currentClip's counterpart (matchArray[index]), we save that reference as the local variable matchClip, and we can then use that reference to do some hit testing.

The easiest hit testing method is hitTestObject. This method is already built into the MovieClip class. It takes one argument, which is the display object that we want to run the hit test against. The two objects we are hit testing, then, are currentClip and matchClip:

if(currentClip.hitTestObject(matchClip)) {

The hitTestObject method tests the bounding boxes of the two movie clips, and returns true if the bounding boxes intersect. This means that it is not very accurate. For example, the triangle and its matching clip can register a hit even if the actual graphics of the objects aren't touching (See illustration below). The circle has the same problem with its matching clip. Only the hit test between the square and its matching clip is really accurate. So, the more rectangular the objects being tested, the more accurate this hit test is. But for irregular objects, it's not all that great. It's too loose, it makes getting a match too easy. However, there may be projects in which this kind of hit test might be considered good enough, or even desirable.

Anyway, if the two objects are colliding, a match was made, so we set the currentClip's x and y to the same x and y of the matchClip. At that point, we also remove the event listener for MOUSE_DOWN from the currentClip, and also set its buttonMode property to false. This effectively takes it out of the game, preventing it from being dragged again, and the hand cursor going away helps let the user know that this is so.

If no match was made, the else part executes (line 35), and the currentClip snaps back to its origin, just like we made it do previously.

Here's how the swf behaves now. Notice as you drag the items how easy it is to make a match:

On the next page, we will explore using the other built-in hit testing method, hitTestPoint, and make it a bit stricter regarding what it takes to make a match.

Stricter hit testing with hitTestPoint

The other built-in hit testing method is called hitTestPoint. It takes three arguments to its constructor's parameter list. The first two are just the x and y of the point that we'd like to test for a hit. The third parameter is known as shapeFlag. This is a Boolean variable, so we send it a value of either true or false. Here are the implications of that choice:

The first choice above makes the hit testing a little more strict than the second one, so we will be using that and setting shapeFlag to true. We also need to turn our statement around so that matchClip is the subject rather than currentClip. What I mean is this: our if statement previously looked like this:

if(currentClip.hitTestObject(matchClip)) {

Actually, using hitTestObject, it didn't matter which order we hit tested those movie clips. But now, when we run hitTestPoint, we need to run it on the matchClip instead:

if(matchClip.hitTestPoint(currentClip.x, currentClip.y, true)) {

Notice the x, y screen location we are using is the x, y location of the currentClip. But there is one more thing we need to do to make this work right. Right now, the registration point of all our movie clips is in their upper left hand corner. But we want to hit test using a point that's in the middle of the clip being dragged, so it would be way more convenient to have the registration point of all the clips already in the center. To move the registration point, do the following:

Double click on the rectangle to go into its edit mode. The graphics will be highlighted automatically:

Bring up the align panel by pressing CTRL-K. Make sure the "Align to stage" checkbox is checked. Then click the "align center horizontally" and "align center vertically" buttons. 

This will center the graphics:

Notice that when you do this, the other rectangle on the stage (the match clip) centers also. This is because both squares are instances of the same movie clip symbol in the library. This is fine, and poses no problem. Continue to do this for the other two symbols as well. When you are finished, go back to Scene 1. Select all of the movie clip instances on the stage, and move them over to the right and down, to correct the shifting that occured when their registration points were centered. Finished!

I would reproduce the code here again, but the only line that is different is line 28, where all we did was change the hitTestObject line to a hitTestPoint one instead. Here's how the swf behaves with these new changes:

Go ahead and try it out! You will find that now you have to be quite a bit more precise when you drop the clip. If the center point of the clip being dragged does not hit the actual graphics of the matching clip, it doesn't count as a hit. If you wanted to, you could make it a bit less strict by setting shapeFlag to false, and let the test be the point against the matching clip's bounding box. It's really all up to you, but now we have seen the basic options that these two built-in hit testing methods provide.

Creating a "label" type drag game

Another kind of drag and drop game that we might want to make is one in which we require the user to label something. In this variation, the items being dragged are still movie clips, but they typically contain words. The other big difference is that instead of the words snapping to the same x, y location as the matching clip, we might instead actually want the word to snap to a certain screen location that we determined ahead of time, because the x and y of the matching clip may not always be the best place to position the label, and usually won't be. We really want to be able to make it snap to any screen location we want in this type of game.

To author this, we might make word movie clips that say "Square", "Circle", and "Triangle" using static text boxes. These clips will also have their registration points in their center. What I usually do is make one symbol, and then right click it in the library and choose "duplicate," then just edit the resulting duplicate MovieClip symbols. Having made these symbols in the library, let's drag out an instance of each one, and give them instance names of square_word, circle_word, and triangle_word. These are the clips that we'll be dragging. Go ahead and delete the former set of drag clips from the stage (square, circle, and triangle), as these word clips will replace them. But keep the match clips (the ones at the bottom of the screen: squareMatch, circleMatch, and tirangleMatch).

Next, we need to do some pre-planning. In the Flash program, we drag the word clips to where we feel like they ought to wind up if they are dropped on their targets correctly. We make note of these positions, writing down the x and y locations of each one. These numbers can be found in the properties panel, of course. I came up with the following:

Here's what I jotted down on paper for my desired x, y (finishing) positions of the three word clips:

square_word: 
x: 276
y: 207

circle_word:
x: 443
y: 207

triangle_word
x: 107
y: 207

Having made note of these locations, we can now move the label clips to arbitrary starting locations from which they will be dragged (and mix them up a bit). When the user drags one of these words, and correctly drops it on the correct matching clip, we will use these numbers as the x, y location to "snap" the word clip to.

Turning now to the code, we need to make just a few changes to make this work. The first change is to make the dragArray contain these new clips instead of the old ones we got rid of:

var dragArray = [square_word, circle_word, triangle_word];

The matchArray will stay the same, but now we need to add a new array which we will call posArray:

var posArray:Array = [ {x:276, y:207}, {x:443, y:207}, {x:107, y:207} ];

This array is an array of objects. In case you've never encountered this notation before, an object can be indicated using curly braces. Each object in the above line has an x property and a y property. Properties are noted just by naming them and putting a colon between the name and the value, and then commas to separate the name/value pairs. We have faithfully recorded the screen locations we wrote down. We have also made sure that they are in the correct order: square, circle, triangle. This comprises yet a third parallel array, and it's important to keep the items in it in the same order, just like we did for the dragArray and matchArray.

The next change is that the match clips need no longer have their alpha reduced. So we find the line that does that (line 12) and comment it out. This change isn't crucial, but it reflects the idea that this is a different kind of drag-drop game than the build one.

Finally, the last change is on lines 31 and 32. Rather than snapping the dragging clip to the same x and y as the matching clip, we instead snap it to the x and y of the corresponding element in our new posArray!

currentClip.x = posArray[index].x;
currentClip.y = posArray[index].y;

Notice that by creating an array of objects, we are able to use the name and index of the array, followed by a dot, then the name of the property we want that's stored there. Using arrays of objects can be an extremely useful technique, as this simple example demonstrates. We're only storing two properties in each object, but you could potentially store whatever information you want.

Here's the finished code:

var dragArray:Array = [square_word, circle_word, triangle_word];
var matchArray:Array = [squareMatch, circleMatch, triangleMatch];
var posArray:Array = [ {x:276, y:207}, {x:443, y:207}, {x:107, y:207} ];

var currentClip:MovieClip;
var startX:Number;
var startY:Number;

for(var i:int = 0; i < dragArray.length; i++) {
	dragArray[i].buttonMode = true;
	dragArray[i].addEventListener(MouseEvent.MOUSE_DOWN, item_onMouseDown);
	//matchArray[i].alpha = 0.2;
}

function item_onMouseDown(event:MouseEvent):void {
	currentClip = MovieClip(event.currentTarget);
	startX = currentClip.x;
	startY = currentClip.y;
	addChild(currentClip); //bring to the front
	currentClip.startDrag();
	stage.addEventListener(MouseEvent.MOUSE_UP, stage_onMouseUp);
}

function stage_onMouseUp(event:MouseEvent):void {
	stage.removeEventListener(MouseEvent.MOUSE_UP, stage_onMouseUp);
	currentClip.stopDrag();
	var index:int = dragArray.indexOf(currentClip);
	var matchClip:MovieClip = MovieClip(matchArray[index]);
	if(matchClip.hitTestPoint(currentClip.x, currentClip.y, true)) {
		//a match was made! position the clip using the posArray values:
		currentClip.x = posArray[index].x;
		currentClip.y = posArray[index].y;
		//make it not draggable anymore:
		currentClip.removeEventListener(MouseEvent.MOUSE_DOWN, item_onMouseDown);
		currentClip.buttonMode = false;
	} else {
		//match was not made, so send the clip back where it started:
		currentClip.x = startX;
		currentClip.y = startY;
	}
}

And here's how the swf behaves:

On the next page, we'll make our drag-drop game routine into a cool reusable class!

Creating the DragGame class

Now that we have a routine that works really well, it's time to see how we can turn it into a reusable class so that we don't have to write the same code over and over for different projects. The thing to do is to ask yourself what things are going to vary, and what things are always going to stay the same. Then write the class based on the things that stay the same, but making provision for changing the things that vary. Here are the things I see that vary:

  1. The dragArray and the matchArray are going to change from project to project. Whether many or few, these two groups of objects will be different groups of objects each time.
  2. Sometimes we'll include a posArray (array of positions), meaning that we want to make a Label style game. We'll make the class change its behavior when a dragging clip is dropped, depending on whether we supply a posArray or not.
  3. The method of hit testing might vary. Sometimes we might want to use hitTestObject, and other times we might want to use either of the two variations of hitTestPoint. Or even some other collision detection routine that we haven't written yet.
  4. Our client code might want to make some things happen in response to a match being made, a match NOT being made, and/or all matches having been made (for example, we might want certain sounds to play in response to those things happening). So our class will dispatch custom events letting us know when those things happen, and our client code will listen for these events and respond. This will be a great lesson in dispatching custom events!

After we write the class and begin using it, all we will have to do (from the client code's perspective) is create an instance of the class, at the same time sending it the dragArray and matchArray as arguments to the constructor. We'll also need to send it a reference to the stage, since our MOUSE_UP routine needs to get added to the stage. We'll make an optional parameter with a default value of null for the posArray. Also, we'll make the class use some kind of default hit testing method, but we'll include a way to change it if we want to. This will be done using an interface and polymorphism. Don't worry if you're not familiar with those terms, as I'll show you how that works, and this will be a good, and simple, introduction to those things.

Let's begin writing the class with just the basic class structure:

package com.jessamin.games {
	//import statements go here:
	
	public class DragGame {
		//variable list goes here:
		
		public function DragGame() {
			//constructor
			
		}
	}
}

The class is written to be in a package. I've used the name of my friend Jessamin Swearingen's website. Jessamin is a teacher in NYC, and I work with her on games like these. Anyway, create a new AS3 class file in flash. Either type in or paste the above outline. Now save the file. In the process of saving the file, create a com folder in the same folder that your fla is residing in. Inside the com folder create a folder called jessamin. Inside the jessamin folder create one called games. Finally, save the file into the games folder as DragGame.as. See my recent tutorial Flash: The big picture for more information on topics like packages and the classpath.

Next, let's expand upon this basic outline. Let's create some private variables to hold the references to the dragArray, the matchArray, the posArray, the stage, and the hit test method. Let's expand the constructor's parameter list to allow the passing in of these values. And let's write into the constructor the code that saves these values into the class:

package com.jessamin.games {
	import flash.display.Stage;
	//import statements go here:
	
	public class DragGame {
		//variable list goes here:
		private var _stage:Stage;
		private var _dragArray:Array;
		private var _matchArray:Array;
		private var _posArray:Array;
		private var _hitTestMethod:IHitTestMethod;
		
		public function DragGame(stage, 
							     dragArray, 
								 matchArray, 
								 posArray = null, 
								 hitTestMethod = null) {
			//save the five supplied arguments into the private variables:
			_stage = stage;
			_dragArray = dragArray;
			_matchArray = matchArray;
			_posArray = posArray;
			_hitTestMethod = hitTestMethod;
		}
	}
}

In the parameters list, the first three are required, but the final two are optional, since they have default values of null. The final one, hitTestMethod, is typed to an interface type, and we haven't created the interface for it yet, so let's go ahead and do that. Create a new Actionscript file, and enter the following into the code window:

package com.jessamin.games {
	import flash.display.DisplayObject;
	
	public interface IHitTestMethod {
		function hitTest(obj1:DisplayObject, obj2:DisplayObject):Boolean;
	}
}

Save it to the same folder (com.jessamin.games), naming it IHitTestMethod.as. Now, admittedly, if you aren't familiar with interfaces, this is going to look pretty weird. But for now, just realize that this is going to allow us to create a set of classes that implement this interface. By implementing this interface, each class agrees to provide a public method for each function listed in the interface. In this case, there is only one in the list. So the requirement is fairly simple: any class that implements this interface must provide a public method called hitTest, and that public method must follow the same form (signature, if you will) as the one in the interface. In other words, the hitTest method must have the same number of parameters, they must be of the same type that the interface calls for, and the return type of the method must be the same (in this case, Boolean).

Notice that interfaces, unlike classes, are only a list of functions. There are no curly braces or function bodies. The word public is omitted because it would be redundant. It's understood that the list is a list of public functions that any implementing classes must supply.

Next, let's make a simple class that implements this interface. Create yet another new Actionscript file and either type or paste the following into the code window:

package com.jessamin.games {
	import flash.display.DisplayObject;
	
	public class HitTestObject implements IHitTestMethod {
		public function hitTest(obj1:DisplayObject, obj2:DisplayObject):Boolean {
			return obj1.hitTestObject(obj2);
		}
	}
}

Save this file to the same folder and name it HitTestObject.as. All this might seem like over-engineering right now. And it is admittedly a tiny bit of a pain to go to the trouble to do this. But what this does is allows for us to swap out the way that we do hit testing. Our DragGame class has a private variable called _hitTestMethod. This variable is typed to the interface type, so it can hold an instance of any class that implements the interface. This allows for some cool polymorphism magic, when the DragGame class simply tells its _hitTestMethod variable to run its hitTest function. Instances of classes that implement the IHitTestMethod interface can all be treated the same as each other, and are thus interchangeable (they can stand in for each other). This is pretty much the whole idea behind interfaces and polymorphism. Now you know!

Let's go ahead and write just one more class that implements the IHitTestMethod interface:

package com.jessamin.games {
	import flash.display.DisplayObject;
	
	public class HitTestPoint implements IHitTestMethod {
		public function hitTest(obj1:DisplayObject, obj2:DisplayObject):Boolean {
			return obj1.hitTestPoint(obj2.x, obj2.y, true);
		}
	}
}

Save it as HitTestPoint.as. Now we have two classes that implement the interface we created. Instances of these classes can now be plugged into the variable in our DragGame class, and this will allow us to optionally swap out the method that we use to hit test our objects. And more flexibility comes in when you realize that later, if you happen to come across some great new collision routine that you'd like to use, all you have to do is write it into a class that implements this interface, and the DragGame class will accept it, too! The hitTest methods that we've written are short and simple, and all they do is encapsulate the ones that are already built into the language. But the DragGame class doesn't know or care how long or short, simple or complicated these hitTest methods are. The only rule is that an implementing class supply a method called hitTest, and that it conform to the structure defined in the interface.

Now let's continue writing the DragGame class. So far all our class does is just accept the arguments we send and save them to private variables. When someone uses our class, all they have to do is make an instance of it, at the same time sending it references to the dragArray and the matchArray. This is kind of like introducing a couple of mutual friends. When the class gains these references, now it knows about these two arrays, too, just like the fla file does. It's important to understand that, once again, variable names are just pointers to the objects. So we are just making the class aware of these arrays, but they are the exact same arrays that the fla file knows about. In the class they might even have a different name, as they do here, with an underscore in front of the name. But even under a different name, they are still the same arrays.

And now we can basically just start bringing in the timeline code that we developed earlier. But first, in the constructor, we need to test to see if a hit testing method was supplied. If it wasn't, then we will supply a default one. This code is on line 33 (see code below). I've decided that the hit test point one with shapeFlag true is probably the best all-around hit testing method, so we'll use that one as the default.

Next, we reproduce the exact same for loop from the timeline code that sets buttonMode to true and adds event listeners for MouseEvent.MOUSE_DOWN to the _dragArray clips. This code is exactly the same except for the underscore in front of _dragArray.

Our event handler functions are exactly the same, except that they have the word private tacked on in front of them. We also add variables to the variables list for startX, startY, and currentClip.  Here is the code for our class with the timeline code worked into it:

package com.jessamin.games {
	//import statements go here:
	import flash.display.MovieClip;
	import flash.display.Stage;
	import flash.events.MouseEvent;
	
	public class DragGame {
		//variable list goes here:
		private var _stage:Stage;
		private var _dragArray:Array;
		private var _matchArray:Array;
		private var _posArray:Array;
		private var _hitTestMethod:IHitTestMethod;
		
		private var currentClip:MovieClip;
		private var startX:Number;
		private var startY:Number;
		
		public function DragGame(stage, 
							     dragArray, 
								 matchArray, 
								 posArray = null, 
								 hitTestMethod = null) {
			//save the five supplied arguments into the private variables:
			_stage = stage;
			_dragArray = dragArray;
			_matchArray = matchArray;
			_posArray = posArray;
			_hitTestMethod = hitTestMethod;
			
			//make hit test point the default
			//if nothing is supplied:
			if (_hitTestMethod == null) {
				_hitTestMethod = new HitTestPoint();
			}
			
			//add event listeners for MOUSE_DOWN to all the drag clips:
			for (var i:int = 0; i < _dragArray.length; i++) {
				_dragArray[i].buttonMode = true;
				_dragArray[i].addEventListener(MouseEvent.MOUSE_DOWN, item_onMouseDown);
			}
		}
		private function item_onMouseDown(event:MouseEvent):void {
			currentClip = MovieClip(event.currentTarget);
			startX = currentClip.x;
			startY = currentClip.y;
			currentClip.parent.addChild(currentClip); //bring to the front
			currentClip.startDrag();
			_stage.addEventListener(MouseEvent.MOUSE_UP, stage_onMouseUp);
		}
		private function stage_onMouseUp(event:MouseEvent):void {
			_stage.removeEventListener(MouseEvent.MOUSE_UP, stage_onMouseUp);
			currentClip.stopDrag();
			var index:int = _dragArray.indexOf(currentClip);
			var matchClip:MovieClip = MovieClip(_matchArray[index]);
			
			//here we modify the hit testing to use our interface:
			if (_hitTestMethod.hitTest(matchClip, currentClip)) {
				//match was made, so:
				if (_posArray == null) {
					currentClip.x = matchClip.x;
					currentClip.y = matchClip.y;
				} else {
					currentClip.x = _posArray[index].x;
					currentClip.y = _posArray[index].y;
				}
				//make it not draggable anymore:
				currentClip.removeEventListener(MouseEvent.MOUSE_DOWN, item_onMouseDown);
				currentClip.buttonMode = false;
			} else {
				//match was not made, so send the clip back where it started:
				currentClip.x = startX;
				currentClip.y = startY;
			}
		}
		public function set hitTestMethod(value:IHitTestMethod):void {
			this._hitTestMethod = value;
		}
	}
}

Line 47 had to be modified. Our class can't use addChild itself, because it's not a display object container, and that's not what we want anyway. Instead, we want currentClip's parent to perform the addChild. Remember this is just the line that brings the clip being dragged to the front of the display list, layered over the top of all the other clips.

One other difference of our class code from the timeline code begins on line 58. We delegate the hit testing to our instance variable _hitTestMethod. The matchClip needs to be named first, so that the hitTestPoint can work correctly, like we discussed before. And if it's hitTestObject that's being used, the order doesn't matter anyway.

Yet another difference begins on line 60. We test to see if _posArray is null. If it is, then the user of our class didn't supply a posArray, so they must want to snap the currentClip to the matching clip's x and y. However, if the _posArray is not null, then the user of our class did supply one, and we will use those x and y values instead! This way our class can be smart enough to accommodate either a build or label type game!

Finally, one last difference begins on line 76, where we provide a public "setter" method for changing the hitTestMethod from outside the class. Using the set keyword makes outside code treat this method like a property. From outside the class, we can set the hit testing method that our class uses just by using a dot, but we have to supply an instance of a class that implements the IHitTestMethod interface. So to use the other hit testing method instead (remember, right now there's only two), the client code would just say:

dragGame.hitTestMethod = new HitTestObject();

This way, an instance of our class can be made to dynamically change the way it does its hit testing business! The class is almost finished now. We will refine it some more on the next page so that it dispatches events (you're going to absolutely love the simplicity of dispatching events from a class)! Then we'll see how easy it is to use this class from the client code in our fla file (and future fla files)!

Dispatching custom events from our class

Source File: 

This next part is fun stuff: making our class dispatch custom events and having our client code listen for them. It so happens that there are three events taking place in our class that our client code might be interested in knowing about, so that it can react to them.

The first is when a clip is dragged and a match is made. The second is when a clip is dragged and a match is not made. The third is when all of the items have been matched. The first two are easy, and the third only requires us to create a variable to keep track of how many items have been matched.

Extending EventDispatcher, creating constants, and dispatching events

In order for our class to dispatch events, it must extend the EventDispatcher class. This little detail just means we need to add the words extends EventDispatcher at the end of our class declaration, and we also have to import that class in our import statements:

import flash.events.EventDispatcher;

Our next step is to create some public static constants to store the strings of text that we will dispatch. This step, while not technically required, is an established convention and a good practice which I have written about elsewhere. We will name these public static constants MATCH_MADE, NO_MATCH, and ALL_DONE. It is also conventional to make these names all uppercase with separate words indicated with underscores. One glance, and you can tell those are constants! Also, unlike ordinary variables that might wait till the constructor runs to get their values, these constants are static, meaning that they belong to the class and not to individual instances, and we assign them their values right away. Typically, they get the same text assigned to them as the variable name, but written in camel case instead (example: MATCH_MADE = "matchMade"). But the truth of the matter is that this string of text could be absolutely anything. However you would probably not want to duplicate any of the already established ones in the language, just in case. The word "static" you can think of as meaning "only one copy," and "belonging to the class." To access a public static constant, from outside code you just use the name of the class, then a dot, then the name of the variable. It's exactly the same convention used for the built-in constants in the language (like MouseEvent.CLICK, which is just a constant that stores the string "click").

The three lines of code declaring these constants have been added to the top of the variables list, starting at line 11 (see the code listing below).

The other strategic aspect is where to dispatch the events from. This is easy. Find the place in the code where the clip is being dropped. Then find the place where the clip either matches or doesn't. In the place where the clip matches, dispatch the event for MATCH_MADE. In the place where the code determines that the clip doesn't match, dispatch the event for NO_MATCH. For the third one, though, to prepare for the event dispatch, we need to create a variable. We'll call it matchesMade, add it to the variables list, initialize it to 0 in the constructor, and then increment it by 1 each time a match is made. Then we need to put in code that compares this variable's value to the length of the dragArray. If they are equal, then all the matches have been made, and so we dispatch the ALL_DONE event. You'll see all that in the finished code below:

package com.jessamin.games {
	//import statements go here:
	import flash.display.MovieClip;
	import flash.display.Stage;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.MouseEvent;
	
	public class DragGame extends EventDispatcher {
		//variable list goes here:
		public static const NO_MATCH:String = "noMatch";
		public static const MATCH_MADE:String = "matchMade";
		public static const ALL_DONE:String = "allDone";
		
		private var matchesMade:int;
		private var _stage:Stage;
		private var _dragArray:Array;
		private var _matchArray:Array;
		private var _posArray:Array;
		private var _hitTestMethod:IHitTestMethod;
		
		private var currentClip:MovieClip;
		private var startX:Number;
		private var startY:Number;
		
		public function DragGame(stage, 
							     dragArray, 
								 matchArray, 
								 posArray = null, 
								 hitTestMethod = null) {
			//save the five supplied arguments into the private variables:
			_stage = stage;
			_dragArray = dragArray;
			_matchArray = matchArray;
			_posArray = posArray;
			_hitTestMethod = hitTestMethod;
			
			//make hit test point the default
			//if nothing is supplied:
			if (_hitTestMethod == null) {
				_hitTestMethod = new HitTestPoint();
			}
			
			//add event listeners for MOUSE_DOWN to all the drag clips:
			for (var i:int = 0; i < _dragArray.length; i++) {
				_dragArray[i].buttonMode = true;
				_dragArray[i].addEventListener(MouseEvent.MOUSE_DOWN, item_onMouseDown);
			}
			matchesMade = 0;
		}
		private function item_onMouseDown(event:MouseEvent):void {
			currentClip = MovieClip(event.currentTarget);
			startX = currentClip.x;
			startY = currentClip.y;
			currentClip.parent.addChild(currentClip); //bring to the front
			currentClip.startDrag();
			_stage.addEventListener(MouseEvent.MOUSE_UP, stage_onMouseUp);
		}
		private function stage_onMouseUp(event:MouseEvent):void {
			_stage.removeEventListener(MouseEvent.MOUSE_UP, stage_onMouseUp);
			currentClip.stopDrag();
			var index:int = _dragArray.indexOf(currentClip);
			var matchClip:MovieClip = MovieClip(_matchArray[index]);
			
			//here we modify the hit testing to use our interface:
			if (_hitTestMethod.hitTest(matchClip, currentClip)) {
				//match was made, so:
				if (_posArray == null) {
					currentClip.x = matchClip.x;
					currentClip.y = matchClip.y;
				} else {
					currentClip.x = _posArray[index].x;
					currentClip.y = _posArray[index].y;
				}
				//make it not draggable anymore:
				currentClip.removeEventListener(MouseEvent.MOUSE_DOWN, item_onMouseDown);
				currentClip.buttonMode = false;
				//increment matches made. dispatch events 
				matchesMade++;
				dispatchEvent(new Event(DragGame.MATCH_MADE));
				if (matchesMade == _dragArray.length) {
					//only dispatch this one if all the matches have been made
					dispatchEvent(new Event(DragGame.ALL_DONE));
				}
			} else {
				//match was not made, so send the clip back where it started:
				currentClip.x = startX;
				currentClip.y = startY;
				//dispatch the bad news: no match made
				dispatchEvent(new Event(DragGame.NO_MATCH));
			}
		}
		public function set hitTestMethod(value:IHitTestMethod):void {
			this._hitTestMethod = value;
		}
	}
}

This class has grown fairly large, but it's really not all that complicated either. There is really only a constructor, two event handler functions, and another public function for setting the hit testing method. Since we took it from timeline code, you've gotten to see it transformed into a custom class. Now let's see how easy it is to put to work from our client code.

Meanwhile, back in the fla file...

Going back to the fla file we left a couple of pages ago, let's return to the actions layer. Delete all the code there except for the lines that declare the arrays. Next, let's add a line to import our new game classes:

import com.jessamin.games.*;

We'll just use the * to import them all. Next, we'll make an instance of the DragGame class, and pass it the array references (and the stage reference) at the same time:

var dragGame:DragGame = new DragGame(stage, dragArray, matchArray, posArray);

You might wonder about the array names being the exact same names as the names in the class's parameters list. There really is no clash, it's perfectly fine to use the same names.

By supplying a posArray, we know that the clips will snap to the values given in the posArray when we make a match. If we hadn't supplied this argument, they would snap to the x and y of the matching clips. Finally, by not supplying a fifth argument for a hit test method, we know that our class will supply the default one, which is an instance of HitTestPoint.

Here is the complete code (fla file, first frame) so far:

import com.jessamin.games.*;

var dragArray:Array = [square_word, circle_word, triangle_word];
var matchArray:Array = [squareMatch, circleMatch, triangleMatch];
var posArray:Array = [ {x:276, y:207}, {x:443, y:207}, {x:107, y:207} ];

var dragGame:DragGame = new DragGame(stage, dragArray, matchArray, posArray);

And we find that when we run the swf, it behaves identically to the way it did before when all the code was on the timeline. As an experiment, try removing the last argument, posArray. Now run the file. The dragging clips will snap to the middle of the matching clips, as though this were a build type game. Okay, now put it back again.

Another experiment: If we put in a line like this right after the line that instantiates dragGame:

dragGame.hitTestMethod = new HitTestObject();

... we will find that now our code uses the other method of hit testing, and that the clips make a match in the more loose way we saw earlier (bounding boxes collide). This is polymorphism at work. With one line of code, we can completely change how the collisions are determined! (Note that we could have also done this in the DragGame's constructor, by making use of that fifth parameter).

By the way, we left out the part where the code reduces the alpha of the clips in the matchArray. I figured that this aspect should be controlled by the client code in the fla file, and since it has a reference to the same array, it's not a problem. But you have to always make these kind of decisions as to what should be left in the class and what should be controlled from outside of it. It's best to just let the class handle the things that we know are probably not going to vary from project to project, and we won't always want match clips to have 0 alpha.

Now let's add some event listeners to our instance of the DragGame class. Just for the occasion, I imported some sound files to the library of the fla file. I did this by going to the file menu and choosing Import, then Import to library. I imported three short WAV fles that I had on hand (actually, I got them from Jessamin). The next step was to right click each one in the library, and check off "Export for actionscript" and then make up a class name for each one. When the user makes a correct match, a nice sound ("plink!") will play as the clip locks into place. When the user gets it wrong, a sound like a rubber band being strummed will sound ("boiiing"), and when the user matches all the objects, an applause sound will play.

Naturally, all this will happen in response to our class dispatching those custom events, and us listening for them in the fla file.

So do you see the methodology here? We add an event listener for an event to the same instance that is going to be dispatching it, in this case our instance named dragGame. There is no such thing as just setting up a generic event listener that listens for events from everywhere. The event listener has to be added to the same object that is going to be dispatching it. That's how it works.

Here's the final version of the code in the fla:

import com.jessamin.games.*;

var dragArray:Array = [square_word, circle_word, triangle_word];
var matchArray:Array = [squareMatch, circleMatch, triangleMatch];
var posArray:Array = [ {x:276, y:207}, {x:443, y:207}, {x:107, y:207} ];

var dragGame:DragGame = new DragGame(stage, dragArray, matchArray, posArray);

dragGame.addEventListener(DragGame.MATCH_MADE, onMatch);
dragGame.addEventListener(DragGame.NO_MATCH, onFlub);
dragGame.addEventListener(DragGame.ALL_DONE, onDone);

function onMatch(event:Event):void {
	var matchSound:Sound = new MatchSound();
	matchSound.play();
}
function onFlub(event:Event):void {
	var flubSound:Sound = new FlubSound();
	flubSound.play();
}
function onDone(event:Event):void {
	var applause:Sound = new Applause();
	applause.play();
}

And here's how the swf behaves (WARNING: this swf plays sounds!!!):

Conclusion and some more ideas

This has been a fun tutorial. I feel like I'm done, though, and I hope you got a lot out of it! It's been my pleasure to bring it to you. As always, there are lots of ways that you can customize this and try more and different things. Here are just a few suggestions:

  1. The client code might need a reference to the currentClip. For example, when a match is made, and the clip snaps into place, the client code might want to tell it to gotoAndStop(2).  Write a getter method for the class whereby the client code can get a reference to the currentClip variable.
  2. Rather than just snapping into place, we might want the drag clip to tween into place instead. In fact, you could write another interface to provide a snapping method if you found yourself wanting to vary this up a lot.
  3. Lots more things can be done in response to the custom events firing. One idea that comes to mind is a reset button that appears when all the matches have been made. Which leads to...
  4. You might also write a reset method into the class that puts everything back where it started from and starts the game over. Can you think of how you might do that? Hint: you'd have to save all the positions of all the drag clips into another array. You would also have to move some of the actions from the constructor function into an init() type function so that those actions could be called again.

I always hope that my tutorials will be food for thought, and that you will see them not as "set in stone" but rather as a launching point for ideas of your own. Anyway, till next time.... (I may yet add another page and answer #4 myself. I don't like having to refresh the page to reset the game)....