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)!