Wednesday, January 07th, 2009 | Author: Dave

In this tutorial we’ll develop classes to allow enemy ships, in a potential game, to move with various motions, using ActionScript 3’s event system.

Overview:
Motion class – this class will use an algorithm to move a single point around it’s own internal coordinate system. By that I mean the class is not tied to any particular object, it just does it’s thing and programmatically moves a point – it’s up to other objects that subscribe to motion update events to use the class’s x,y values.
Motion Event class – we’ll dispatch a custom event object from the motion class to allow the x,y values to be sent to any registered listeners.
Controller – just a bit of code on frame 1 of an .fla that will create instances of a library clip (the enemy) and set up the motion class to allow the enemies to move.

Getting Started:
To begin start a new .fla file using ActionScript 3. Make a small enemy movie clip (about 30×30) and delete it from the stage. Right-click it in the library and select Linkage. Check export for ActionScript and give it a class name of Invader. Giving the clip a class name will allow you to create instances of it programatically. For ease of use, you should create a new folder for this project and save your file there now. The classes you create will be saved into this same folder.

The Motion Class:
As stated previously, this class will simply move a point around. As it does it will dispatch events so that any objects that are listening (the enemies) can respond appropriately. Select File > New and choose ActionScript file from the dialog. Enter the class code:

package
{
	import flash.events.EventDispatcher;
	import flash.events.Event;
	import flash.utils.Timer;
        import flash.events.TimerEvent;
	import InvaderMoveEvent;
 
	public class MoveRoutine_circle extends EventDispatcher
	{
		private var curX:Number;
		private var curY:Number;
		private var ang:Number;
		private var myTimer:Timer;		
 
		function MoveRoutine_circle()
		{
			ang = 0;
			curX = 0;
			curY = 0;
			myTimer = new Timer(10);
			myTimer.addEventListener(TimerEvent.TIMER, evtMove);
		}
 
		public function move():void
		{
			myTimer.start();
		}
 
		public function pause():void
		{
			myTimer.reset();
		}
 
		private function evtMove(e:TimerEvent):void
		{
			ang += .05;
			if (ang >= 6.28) { ang = 0; }
 
			curX = Math.sin(ang);
			curY = Math.cos(ang);			
 
			dispatchEvent(new InvaderMoveEvent(curX, curY));
		}
	}
}

Save this class as MoveRoutine_circle.as in the same folder as your .fla file. Let’s have a look at the code. First, the class extends EventDispatcher in order to have event capabilities – other objects may add event listeners to this class, and the class can dispatch events as necessary. Within the constructor method, default values are applied to the class variables and a timer object is instantiated. The timer is told to call the evtMove() method every 10 ms. The move() and pause() methods simply start the timer running, and reset it respectively. Note that these are public methods allowing external control of the enemies. Finally, the evtMove() method – it is private because it is only called from within the class – by the timer. It accepts a single parameter, of type EventType, as all event listeners do.
So, within the evtMove() method is the code that does the actual moving of the point. As you may be able to tell, all this code does is to move the point in a circle. The ang variable starts at 0, as set in the constructor, and counts to 6.28 (2 * PI) by .05, before resetting back to 0. In radians this is one complete revolution. As ang is incremented, the sine and cosine are computed and placed in curX and curY. Next, the custom event is dispatched – and curX and curY are sent along to anyone interested.

Note: by playing with the algorithm you can create all sorts of interesting sub-motions. More on that later.

The InvaderMoveEvent class:
As the motion class runs it dispatches new InvaderMoveEvent objects to any registered listeners. Using a custom event here makes sense because it allows you to send the current x,y values directly to listeners. If you were to not use a custom event, the listeners would, after receiving the event, have to poll the motion object to retrieve the x,y values – making for more communication than necessary, and potentially slowing things down.
As before, select File > New and choose ActionScript file from the dialog. Enter the class code:

package 
{
	import flash.events.Event;
 
	public class InvaderMoveEvent extends Event 
	{
		private var _x:Number;
		private var _y:Number;
 
		public static const MOVE:String = "invader_move";
 
		function InvaderMoveEvent(nx:Number = 0, ny:Number = 0)
		{				
			_x = nx;
			_y = ny;			
			super(MOVE);
		}
 
		public function get x():Number
		{
			return _x;
		}		
 
		public function get y():Number
		{
			return _y;
		}
	}	
}

Save this class as InvaderMoveEvent.as in the same folder as your .fla file. As all custom events must do, it extends Flash’s built-in Event class and makes a call to super(eventType) in the constructor – invoking the Event class’s constructor with the data from the custom one. Because I’m using the event to simply carry the x,y data to listeners, and nothing else, I opted out of sending in any of the normal parameters (type, bubbles, etc) and just use the MOVE constant directly in the call to super(). I’ve also not done the override on clone or toString. Two getter methods provide the x,y values to the objects receiving the event.

Let’s quickly see this in action. Add this code to frame 1 of your Flash movie:

var moveCirc = new MoveRoutine_circle();
moveCirc.move();

As you might guess this bit of code creates an instance of the circle motion class and stores it in the variable moveCirc. Next a call to the object’s move() method starts the class’s internal timer which in turns starts making calls to the evtMove() method and begins dispatching the custom event. Underneath the current code add this:

moveCirc.addEventListener(InvaderMoveEvent.MOVE, see);
function see(e:InvaderMoveEvent){
	trace(e.x);
}

What this does is register the timeline function see() to receive the MOVE event type from the class. Test the movie to see the results. You should see a continuous stream of numbers roll by (the points current x) in the output panel. When you’re done you should delete, or comment, those last three lines, as you don’t want the timeline to always listen for motion updates, the enemies will do that themselves.

Note: you could actually create n enemies, sticking references to them in an array. The timeline could receive the motion update and then iterate the array, passing in the values. However, that is essentially bypassing the event system, and inherently slower. Although in testing I got good results with this method, I opted to have individual enemies receive the event directly by registering their own update method.

The Enemy:
Previously, you created a small enemy graphic movieClip and set its class name to Invader. When you test the movie Flash automatically creates the Invader class, because one doesn’t exist in the folder. Click File > New and choose ActionScript file from the dialog. Enter the class code:

package 
{
	import flash.display.MovieClip;	
	import InvaderMoveEvent;
 
	public class Invader extends MovieClip
	{
		private var myX:Number;
		private var myY:Number;
 
		//s is a reference to the current motion class object
		public function Invader(s:*) {			
			s.addEventListener(InvaderMoveEvent.MOVE, doMove);
		}
 
		public function setPos(newX:Number, newY:Number):void
		{			
			myX = newX;
			myY = newY;
		}
 
		private function doMove(e:InvaderMoveEvent):void
		{			
			this.x = myX + e.x;
			this.y = myY + e.y;
		}
	}	
}

Save this class as Invader.as in the same folder as your .fla file. Now when you run the movie, this class will be attached to every enemy invader that is created. Let’s create a single enemy on stage to see how this works. Add these three lines to frame 1 and test the movie:

var p = new Invader(moveCirc);
p.setPos(0,0);
addChild(p);

You should see one enemy ship moving in a small circle. When you invoke the constructor:var p = new Invader(moveCirc); you pass in a reference to the current motion – moveCirc in this case. The Invader class then registers its doMove() method to receive motion updates. The setPos() method allows the code to set the invaders initial position, and updates the internal class variables accordingly. When an event is received, ie when doMove() is invoked the enemy position is updated – note that the enemy is always moved about it’s initial position, if you were to do something like: this.x = this.x + e.x; the objects would end up moving off screen.

Let’s see how more enemies do. Erase those last three lines that add the single enemy and replace them with the following:

for(var i=0; i < 8; i++){
	var v = new Invader(moveCirc);		
	v.setPos(50*i,0);
	addChild(v);	
}

Test the movie and you will see eight ships, all in a row and all moving in a small circle. The code on the frame is, essentially, your controller – it creates an instance of your motion class, and instances of an enemy ship class, and binds them together – by passing a reference to the motion class, to new enemy instances.

Things to try now include modifying the algorithm in the circle class to create differing motions. By creating new classes for your different motions you can create a plugin motion system quite easily. You can also quite easily create a compound motion, where the enemies march across and down the stage, as in the classic space invaders – and still keep their local motion as dictated by the motion class. You can do this by creating a new Sprite, adding the clips to the sprites display list, and then adding the sprite to the main display list. Like so:

var p = new Sprite();
for(var i=0; i < 8; i++){
	var v = new Invader(moveCirc);		
	v.setPos(50*i,0);
	p.addChild(v);	
}
addChild(p);

You can also make bigger circles simply by multiplying the curX and curY values by some number, inside the evtMove() method.

Here’s an interesting variation:

curX = Math.sin(ang) * 10;
curY = Math.tan(ang/2);
Category: Flash
You can follow any responses to this entry through the RSS 2.0 feed. Both comments and pings are currently closed.

4 Responses

  1. Awesome, good tutorial Dave!

    Keep up the good work and I’ll catcha on the flip side :)

  2. my last comment came out strange. Try changing:

    if (ang (6.28)) { ang = 0; }

  3. Damn… can%27t… type… the… right… characters… \n\nif %28ang &gt %286.28%29%29 { ang = 0%3b }

  1. Blogring for tutorial+flash…

    Related Blog Entries…