Creating Ally AI That Locates Nearest Enemy and Attacks
In this tutorial, you will learn:
- Basic for loop usage
- Basic array usage
- How to find the distance between two points
- How to use Math.atan2() to have MovieClips follow each other
- How to fire projectiles
- How to split a velocity vector into two perpendicular lines using sine and cosine
- How to create objects from custom classes
Introduction
When learning Actionscript, or any programming language for that matter, I find it best to create specific engines that employ a variety of techniques and functions. This way, you are not only learning how to use these features, but you are also experiencing a pragmatic use for them. In this tutorial we will be creating a class for an ally AI that will locate the enemy nearest it, chase it, and shoot it. This is used in just about any game where there are characters not controlled by the player. Take, for example, a tower defense game. The towers created do not simply attack any enemy they want. They usually attack the closest. However, there are some games where you can choose to have them attack the quickest enemy, or the one with the most health, or whatever it may be. But for now let’s concentrate on distance.
Now, what if there are three allies (let’s call them followers for now) and two enemies, one of which is really far away? The tutorial you are about to read is designed so that rather than all three followers clustering up on the closest enemy, two will attack it and the third will set off for the far one. Furthermore, what happens when a third enemy is spawned when all this is going on? Well, rather than the follower by itself (which may be closer) going out to attack it, one of the two that are attacking the same enemy will leave and target the newfound enemy. This optimizes the strategy of the followers so no enemy is left alone while there is a surplus of followers elsewhere.
Rather than just talking about what we’re going to do, let’s actually do it. Sometimes I prefer to just see the source code and play around with it rather than just following a tutorial. If you want, you may download a heavily-commented copy of all the code down below. Without further ado, let’s begin the tutorial.
Step 1: Setting Up the MovieClips
There are three MovieClips essential to this tutorial. First, we need the followers and the enemies. We also need some sort of projectile or bullet for the followers to shoot. For the sake of example, I am programming this tutorial in a way that the user can create followers and enemies by clicking buttons at the bottom of the stage. So now we’re up to five MovieClips. Name them Bee, EnemyBee, Ball, BeeButton, and EnemyButton. Below are my five MovieClips in their respective order:

When saving each as a symbol, be sure to check the “Export for Actionscript” box and keep the class name the same.
Step 2: The Arrays
Now that we have our MovieClips, we need a place to store them. Open up the Main Timeline’s Actions Panel (F9) and input the following code:
var beeArray=[]; var eArray=[]; var ballArray=[];
What’s happening here is we are creating three arrays to store our objects: one for all the bees (followers), one for all the enemies, and one for all the balls being shot. These arrays are going to be accessed a ton of times throughout the tutorial, so if you need to brush up on any of the arrays basics be sure to check out this tutorial
Step 3: The Enemies
The enemies are much easier to program, so we’ll start with them first. Create a new custom class called EnemyBee (or whatever necessary to match the name of your enemy MovieClip) and start off with the following code:
package { import flash.display.MovieClip; import flash.events.*; public class EnemyBee extends MovieClip { private var r:Object; private var angle:Number; private var rot:Number; private var health:int=1; private var speedX:Number; private var speedY:Number; private var dist:Number; private var reload:int=50; //reload timer for shooting (don't worry about this for now) public var isTarget:int; public function EnemyBee() { addEventListener(Event.ADDED_TO_STAGE, beginClass); } private function beginClass(event:Event):void { r=MovieClip(root); addEventListener(Event.ENTER_FRAME, eFrame); r.eArray.push(this); this.x=stage.stageWidth+this.width; this.y=Math.random()*400+20; var h:Number=Math.random()*stage.stageHeight; angle=Math.atan2(h-this.y,-this.x); this.speedX=Math.cos(angle)*2; this.speedY=Math.sin(angle)*2; this.rotation=angle/(Math.PI/180); rot=Math.random()*4; } private function eFrame(event:Event):void { } } }
Now this is a lot of code all at once so let’s go through it line by line. Lines 6-13 declare variables which are more or less self-explanatory. The main one to note here is the public variable isTarget. This variable isn’t accessed at all by EnemyBee, but from the followers. It is used to tell how many bees are following the EnemyBee. But let’s move on for now.
In the beginClass function, line 20 places EnemyBee inside eArray (on the Main Timeline) through the use of r (the root of the MovieClip). Below, the initial x and y coordinates are set to be just off the right side of the stage anywhere from the top to the bottom. Variable h is the destination of EnemyBee and, using Math.random(), is different every time. The next line uses a function called Math.atan2(). Most of you trigonometry aficionados know that the atan2 function, being a variant of the arctangent function, returns the angle (in radians) between the positive x axis of a plane and the point constructed by the given coordinates. For now, all you need to know are the parameters. They are as follows:
Math.atan2(destinationY-this.y , destinationX-this.x);
It seems strange, but just remember the y comes first. By using the atan2 function we now know the angle between the point EnemyBee is going for (in this case h) and itself. But how do we make it so that our object moves towards it? That’s where the sine and cosine lines come in. By taking the cosine of the angle we can find the horizontal speed the object must take. Similarly, we can use sine to find the vertical speed. You can replace the 2 at the end of each line with a larger or smaller number to increase or decrease the speed (respectively). Finally, we rotate the object to face its target once again using angle, and then assign rot (the rotational speed) a random number from 0-3.99999 (this is used later on).
Now we can edit the eFrame function. So within the brackets, place this code:
this.x+=this.speedX; if (this.health>0) { this.y+=this.speedY; if (this.x+this.width/2<0) { r.eArray.splice(r.eArray.indexOf(this),1); removeMe(); } } else { if (r.eArray.indexOf(this)>=0) { r.eArray.splice(r.eArray.indexOf(this),1); } this.rotation+=5+rot; this.y+=3+(3-rot/2); if (this.y-this.width>;stage.stageHeight) { removeMe(); } }
First, the object moves horizontally. Next, if its health is greater than zero (meaning it is not dead) it will also move vertically and if it manages to survive the followers and go offstage to the left, remove itself from eArray, and then call the removeMe() function which we will declare in a little bit. In case EnemyBee IS defeated, however, it will first check to see if its index in eArray is at least 0. A useful fact about arrays is that “if” an object isn’t in the array at all, its index is -1. Through this “if statement” we are effectively checking to see if EnemyBee is in eArray so we don’t accidentally remove it twice (that would result in an error). The next few lines are pretty much self-explanatory.
The last piece of code for EnemyBee is the removeMe() function. Here it is:
private function removeMe():void { removeEventListener(Event.ENTER_FRAME, eFrame); r.removeChild(this); }
This last part is easy. First the event listener is removed, and then the object itself is deleted. That’s all we need for EnemyBee. Now we can start the hard stuff! (:
Step 4: The Followers
Now it’s time to get to the heart of the tutorial: creating the followers. Create a new custom class for your followers (in this case it’s called Bee), and input this code:
package { import flash.display.MovieClip; import flash.events.*; public class Bee extends MovieClip { private var r:Object; private var c; private var max:Number=Number.MAX_VALUE; private var speedX:Number; private var speedY:Number; private var speed:Number=2; private var angle:Number; private var reload:int=50; public function Bee() { addEventListener(Event.ADDED_TO_STAGE, beginClass); } private function beginClass(event:Event):void { r=MovieClip(root); addEventListener(Event.ENTER_FRAME, eFrame); r.beeArray.push(this); x=275; y=245; } private function eFrame(event:Event):void { } } } }
You’ve seen all this code already in EnemyBee so I’m not going to explain it all again. The only exceptions are c (which will be explained in a bit) and Number.MAX_VALUE, which can simply be thought of as infinity.
There’s a lot of code in the eFrame event, so let’s do it all in pieces. First, we need this:
if (r.eArray.length!=0) { //paste all the following code here }
This line of code tells us if there are any enemies on stage or not. Without it, we would get errors. Within the brackets, place this first bunch of code:
if (r.eArray.indexOf(c)<0) { //if c is NOT in eArray... for (var i:int=0; i<;r.eArray.length; i++) { //for every object in eArray... var obj=r.eArray[i]; //assign address of object to obj var distX:Number=this.x-obj.x; //horizontal distance between self and obj (the enemy) var distY:Number=this.y-obj.y; //vertical distance between self and obj (the enemy) var dist:Number=(distX*distX+distY*distY); //overall distance using distance formula if (dist<max&&obj.isTarget==0) { //if distance < max and obj has no followers... max=dist; //assign distance to max (the new shorst distance) c=obj; //assign address of object currently being tested in c } } if (r.eArray.indexOf(c)>=0) { //if c is in eArray... c.isTarget++; //tell c (EnemyBee) that one more bee is following it } else { //if c is NOT in eArray... max=Number.MAX_VALUE; //reset max to maximum value for (var j:int=0; j<r.eArray.length; j++) { //for every object in eArray... obj=r.eArray[j]; //assign address of object to obj distX=this.x-obj.x; //horizontal distance between self and obj (the enemy) distY=this.y-obj.y; //vertical distance between self and obj (the enemy) dist=(distX*distX+distY*distY); //overall distance using distance formuala if (dist<max) { //if distance is closer than max... max=dist; //assign distance to max (the new shortest distance) c=obj; //assign address of object currently being tested in c } } c.isTarget++; //tell c (EnemyBee) that one more bee is following it } }
It can get a little confusing at this point, so I included plenty of comments. The variable c is used to hold the address of the closest enemy (hence it’s name). First we check to see if it is not in the array (meaning the closest enemy has not been determined). We use a for loop to iterate through all the enemies in eArray. If you’re new to loops, you can read up on them here.
Next we instantiate four variables: a placeholder for the object being tested, the horizontal distance, the vertical distance, and the total distance. Just in case you are wondering, the distance formula is as follows:

Now that we know the distance of the object being checked, we must see if it is closer than the previous. Recall that max was equal to Number.MAX_VALUE, so it is greater than anything we could compare it to. So the first instance to be checked will definitely be closer, and the if statement will be true. Therefore, max is set to the distance between itself and the enemy being tested (the new shortest distance), and obj is stored in c. All that happened for the first enemy in eArray. Now go through it again for the second enemy. The second enemy is stored in obj, just like the first, and the distance is established. However, instead of max being equal to the highest value, it is equal to the distance of the previous enemy. If the distance between the bee and the enemy being tested is shorter than that of the first enemy, max is lowered again to match the distance and the enemy is stored in c. If not, the loop skips over this and goes on to test the next enemy. This process continues for every enemy in the array. Logically, the value of max by the time all of the enemies are done being tested will be equal to the shortest distance, and c will hold the address for the corresponding enemy.
Note that with the if statement, Bee only looks for enemies that have isTarget==0. In plain English, that means it only takes enemies that are not being followed into consideration. This way we don’t have that surplus of followers in one section and a non-followed enemy elsewhere like we previously discussed. However, there is the case that every enemy is already being followed. The if statement right after the for loop checks to see if a closest enemy (with no followers) has been found. If so, it increases the value of isTarget on the enemy (amount of followers). If not, another for loop extremely similar to the previous is carried out. The only difference is the if statement:
if (dist<max) {
Instead of checking to be sure the enemy being tested has no followers, it just looks for the closest. I found this to be the best design, but you might have another use for it. This code is very easy to customize. Say you wanted locate the enemy with the least amount of followers. You could simply substitute the if statement for this:
if (obj.isTarget<max) { max=obj.isTarget; c=obj; }
All right we’re getting closer to finishing this up. One more loop:
max=Number.MAX_VALUE; //reset max to maximum value if (r.eArray.length>=r.beeArray.length&&c.isTarget>1) { //if c has other followers... c.isTarget--; //tell c it has one fewer follower for (var l:int=0; l<r.eArray.length; l++) { //for every object in eArray... obj=r.eArray[l]; //assign address of object to obj distX=this.x-obj.x; //horizontal distance between self and obj (the enemy) distY=this.y-obj.y; //vertical distance between self and obj (the enemy) dist=(distX*distX+distY*distY); //overall distance using distance formula if (dist<max&&obj.isTarget==0) { //if distance < max and obj has no followers... max=dist; //assign distance to max (the new shortest distance) c=obj; //assign address of object currently being tested in c } } c.isTarget++; //tell c (EnemyBee) that one more bee is following it max=Number.MAX_VALUE; //reset max to maximum value }
Okay so after doing two loops almost exactly like this one, no explanation should be necessary. However, a brief discourse on the logic behind it should help. The only thing different here than the first loop is that it is only executed when there are more enemies than followers and the enemy the bee is following has at least one other follower. You may be asking yourself why I chose to create three loops that are almost identical instead of simply making a function called several times with slightly different arguments. Well, I think it’s easier to retain the information if you see it several times. Plus, a tutorial is never complete unless something is left up to you to figure out yourself.
All right, now that the difficult part is out of the way, we can move on to actually moving the bee. The code below immediately follows the if statement above:
angle=Math.atan2(c.y-this.y,c.x-this.x); //angle between self and closest enemy speedX=Math.cos(angle)*speed; //find horizontal speed according to angle speedY=Math.sin(angle)*speed; //find vertical speed according to angle rotation=angle/(Math.PI/180); //rotate to face enemy distX=this.x-c.x; //horizontal distance between self and closest enemy distY=this.y-c.y; //vertical distance between self and closest enemy dist=(distX*distX+distY*distY); //overall distance using distance formula if (dist<=100) { //if distance is less than or equal to 100... speed=-.1; //slow down if (speed<0) { //if speed is less than 0... speed=0; //keep speed at 0 } } else { //if distance is greater than 100... speed+=.1; //speed up if (speed>2) { //if speed is greater than 2... speed=2; //keep speed at 2 } } this.x+=speedX; //move horizontally this.y+=speedY; //move vertically
This is very similar to the motion employed in the EnemyBee class and shouldn’t be too difficult at all. The if and else statements are purely for aesthetic purposes so that the bee doesn’t sit directly on the enemy.
That concludes the code for the followers. Now all we need is a means to create the followers and enemies. I’m sure you will come up with plenty of ways that pertain to your game, but for the sake of convenience I just created two buttons that allow the user to spawn them. If you’d like to see the Actionscript for this, download the source code files from above.
Step 5: Shooting
Run your .swf. If you did it right, you should get something like this:
The bees follow the enemies just like we programmed them to, but they do not attack. Remember that Ball Movieclip we make at the beginning of the tutorial? Well we’re finally going to use it. Open up the custom class for Ball and type this in:
package { import flash.display.MovieClip; import flash.events.*; public class Ball extends MovieClip { private var r:Object; private var c:int=15; private var id2:int; private var angle:Number; private var speedX:Number; private var speedY:Number; private var speed:Number=8; private var mc:Object; public function Ball(id:uint) { addEventListener(Event.ADDED_TO_STAGE, beginClass); id2=id; } private function beginClass(event:Event):void { r=MovieClip(root); addEventListener(Event.ENTER_FRAME, eFrame); mc=r.beeArray[id2]; //find which object created it angle=mc.rotation*(Math.PI/180); this.x=mc.x; //set x coordinate to that of bee's this.y=mc.y; //set y coordinate to that of bee's speedX=Math.cos(angle)*speed; speedY=Math.sin(angle)*speed; r.setChildIndex(this,r.getChildIndex(mc)); } private function eFrame(event:Event):void { this.x+=speedX; this.y+=speedY; if (c>0) { c--; } else { this.alpha-=.1; } if (this.alpha<=0) { removeBall(); } } public function removeBall():void { removeEventListener(Event.ENTER_FRAME, eFrame); r.ballArray.splice(r.ballArray.indexOf(this),1); r.removeChild(this); } } }
Basically we’re just creating an object that follows a straight line path using the rotation of the bee it’s coming from. The address of the bee is determined by the parameter id2, which we will explain it a bit. Now we have to program Bee to actually shoot the balls. In the eFrame event of Bee, right after the speedX and speedY are used, place the following code:
reload--; //lower reload timer if (reload==0) { //if reload is complete... reload=50; //reset reload timer to 50 var newBall:Ball=new Ball(r.beeArray.indexOf(this)); //create new ball r.addChild(newBall); //add ball to stage r.ballArray.push(newBall); //put ball in ballArray }
This creates a new instance of the Ball class and places it in the corresponding array on the Main Timeline. As you can see, the argument incorporated while instantiating the new ball is the index of the bee in its array. This is then later accessed by the ball in its class to find where it should be placed on the stage and the angle it should follow:
mc=r.beeArray[id2]; //find which object created it angle=mc.rotation*(Math.PI/180); this.x=mc.x; //set x coordinate to that of bee's this.y=mc.y; //set y coordinate to that of bee's speedX=Math.cos(angle)*speed; speedY=Math.sin(angle)*speed; r.setChildIndex(this,r.getChildIndex(mc));
The address of the bee is found using id2 (which is equal to the argument we just mentioned) and stored in mc, which is used to set all that good stuff above. The setChildIndex() statement is used so that the ball appears below the bee, giving the illusion that it’s being shot by the bee, and not appearing right on top of it.
Okay so run your .swf again and you should see this:
Pretty good, huh? Now we just have more step: the collisions.
Step 6: The Collisions
This part is easy. Simply open up the class file for EnemyBee and, in the first line of the eFrame event, put in the following code:
for (var i:int=0; i<r.ballArray.length; i++) { var mc:Object=r.ballArray[i]; dist=Math.abs(mc.x-this.x)+Math.abs(mc.y-this.y); if (dist<;this.width/2+mc.width/2) { //if they touch... health--; mc.removeBall(); } }
It’s just a for loop that iterates over every instance in the ball array and tests for a collision. You can use whatever collision-detection technique you want, but for this example the best choice is a simple circle-to-circle test. Upon each collision, the health of the bee is lowered, and the ball is removed through the function removeBall().
Well, that’s about it. Test your .swf and it should work just as my example up above did. This technique is very useful and I’m sure you’ll find plenty of ways to use it in your games. If you make something cool, I’d love to see it (: Thanks for reading!
