Getting Warmer: Smart Aiming With Heat-Seeking Missiles
In the previous tutorial we had a homing missile chasing after a single target. This tutorial will show you how to convert your homing missiles into heat-seeking missiles for multiple targets.
If you haven't read the first Homing Missile tutorial, you may download this .zip file, which contains the source code we'll be starting with on this tutorial.
Final Result Preview
Let's take a look at the final result we will be working towards:
Step 1: Modify the Cannon Graphic
The only Movie Clip in the Library we'll need to change is the Cannon, since we'll make it aim at the closest target before shooting. Remember that 0° of rotation means pointing at the right, so make the graphic accordingly.

Step 2: Declare Distance Variables for the Cannon
I'm going to reuse the targetX and targetY variables to calculate the distance of the cannon from the target, so I'll declare them at the beginning of the class instead of inside the playGame function, as well as a new variable to store the calculated distance:
1 |
|
2 |
private var missile:Missile = new Missile(); |
3 |
private var speed:int = 15; |
4 |
private var cannon:Cannon = new Cannon(); |
5 |
private var missileOut:Boolean = false; |
6 |
private var ease:int = 10; |
7 |
private var target:Target = new Target(); |
8 |
private var floor:int = 385; |
9 |
private var gravity:Number = 0.5; |
10 |
private var targetVY:Number = 0;//Current vertical velocity of the target |
11 |
private var distance:int; |
12 |
private var targetX:int; |
13 |
private var targetY:int; |
Now the targetX and targetY variables will be already declared for the playGame function:
1 |
|
2 |
private function playGame(event:Event):void |
3 |
{
|
4 |
if (missileOut) |
5 |
{
|
6 |
if (missile.hitTestObject(target)) |
7 |
{
|
8 |
var explosion:Explosion = new Explosion(); |
9 |
addChild(explosion); |
10 |
explosion.x = missile.x; |
11 |
explosion.y = missile.y; |
12 |
removeChild(missile); |
13 |
missileOut = false; |
14 |
}
|
15 |
else
|
16 |
{
|
17 |
targetX = target.x - missile.x; |
18 |
targetY = target.y - missile.y; |
19 |
var rotation:int = Math.atan2(targetY, targetX) * 180 / Math.PI; |
20 |
if (Math.abs(rotation - missile.rotation) > 180) |
21 |
{
|
22 |
if (rotation > 0 && missile.rotation < 0) |
23 |
missile.rotation -= (360 - rotation + missile.rotation) / ease; |
24 |
else if (missile.rotation > 0 && rotation < 0) |
25 |
missile.rotation += (360 - rotation + missile.rotation) / ease; |
26 |
}
|
27 |
else if (rotation < missile.rotation) |
28 |
missile.rotation -= Math.abs(missile.rotation - rotation) / ease; |
29 |
else
|
30 |
missile.rotation += Math.abs(rotation - missile.rotation) / ease; |
31 |
|
32 |
var vx:Number = speed * (90 - Math.abs(missile.rotation)) / 90; |
33 |
var vy:Number; |
34 |
if (missile.rotation < 0) |
35 |
vy = -speed + Math.abs(vx); |
36 |
else
|
37 |
vy = speed - Math.abs(vx); |
38 |
|
39 |
missile.x += vx; |
40 |
missile.y += vy; |
41 |
}
|
42 |
}
|
43 |
targetVY += gravity; |
44 |
target.y += targetVY; |
45 |
if (target.y > floor) |
46 |
{
|
47 |
target.y = floor; |
48 |
targetVY = -18; |
49 |
}
|
50 |
}
|
Step 3: Make the Cannon Point Towards the Target
Previously in the playGame function we were only interested in knowing if the missile was out to take care of its rotation and motion. Now we need first to know if the missile hasn't been shot yet and update the cannon's rotation.
1 |
|
2 |
private function playGame(event:Event):void |
3 |
{
|
4 |
if (!missileOut) |
5 |
{
|
6 |
targetX = target.x - cannon.x; |
7 |
targetY = target.y - cannon.y; |
8 |
cannon.rotation = Math.atan2(targetY, targetX) * 180 / Math.PI; |
9 |
}
|
10 |
else
|
11 |
{
|
12 |
if (missile.hitTestObject(target)) |
13 |
{
|
14 |
var explosion:Explosion = new Explosion(); |
15 |
addChild(explosion); |
16 |
explosion.x = missile.x; |
17 |
explosion.y = missile.y; |
18 |
removeChild(missile); |
19 |
missileOut = false; |
20 |
}
|
21 |
else
|
22 |
{
|
23 |
targetX = target.x - missile.x; |
24 |
targetY = target.y - missile.y; |
25 |
var rotation:int = Math.atan2(targetY, targetX) * 180 / Math.PI; |
26 |
if (Math.abs(rotation - missile.rotation) > 180) |
27 |
{
|
28 |
if (rotation > 0 && missile.rotation < 0) |
29 |
missile.rotation -= (360 - rotation + missile.rotation) / ease; |
30 |
else if (missile.rotation > 0 && rotation < 0) |
31 |
missile.rotation += (360 - rotation + missile.rotation) / ease; |
32 |
}
|
33 |
else if (rotation < missile.rotation) |
34 |
missile.rotation -= Math.abs(missile.rotation - rotation) / ease; |
35 |
else
|
36 |
missile.rotation += Math.abs(rotation - missile.rotation) / ease; |
37 |
|
38 |
var vx:Number = speed * (90 - Math.abs(missile.rotation)) / 90; |
39 |
var vy:Number; |
40 |
if (missile.rotation < 0) |
41 |
vy = -speed + Math.abs(vx); |
42 |
else
|
43 |
vy = speed - Math.abs(vx); |
44 |
|
45 |
missile.x += vx; |
46 |
missile.y += vy; |
47 |
}
|
48 |
}
|
49 |
targetVY += gravity; |
50 |
target.y += targetVY; |
51 |
if (target.y > floor) |
52 |
{
|
53 |
target.y = floor; |
54 |
targetVY = -18; |
55 |
}
|
56 |
}
|
Now the cannon rotates in relation to the target's position.
Step 4: Match the Missile's Rotation with the Cannon's.
The cannon is rotating, but the missile keeps being shot upwards. Replace the hard-coded rotation with the cannon's current location at the moment the missile is being shot.
1 |
|
2 |
private function shoot(event:MouseEvent):void |
3 |
{
|
4 |
if (!missileOut) |
5 |
{
|
6 |
addChild(missile); |
7 |
swapChildren(missile, cannon);//missile will come out from behind cannon |
8 |
missileOut = true; |
9 |
missile.x = cannon.x; |
10 |
missile.y = cannon.y; |
11 |
missile.rotation = cannon.rotation; |
12 |
}
|
13 |
}
|
Now the missile will look like it's actually coming out of the cannon.
Step 5: More than One Target
Right now the homing missile is program to go after one target, but what if we have more targets? How will it decide which one to go after?
First, let's decide how many targets will there be, then we'll put each target in an Array. In this example I'm going to say there are 2 targets, and I'll give each target a random position on screen.
1 |
|
2 |
private var target:Target; |
3 |
private var floor:int = 385; |
4 |
private var gravity:Number = 0.5; |
5 |
private var targetVY:Number = 0;//Current vertical velocity of the target |
6 |
private var distance:int; |
7 |
private var targetX:int; |
8 |
private var targetY:int; |
9 |
private var numTargets:int = 2; |
10 |
private var targets:Array = []; |
11 |
|
12 |
public function Main() |
13 |
{
|
14 |
addChild(cannon); |
15 |
cannon.x = 50; |
16 |
cannon.y = 380; |
17 |
addEventListener(Event.ENTER_FRAME, playGame); |
18 |
stage.addEventListener(MouseEvent.CLICK, shoot); |
19 |
for (var i:int = 0; i < numTargets; i++) |
20 |
{
|
21 |
target = new Target(); |
22 |
addChild(target); |
23 |
target.x = Math.random() * 600; |
24 |
target.y = Math.random() * 400; |
25 |
targets.push(target); |
26 |
}
|
27 |
}
|
Now we have more than one target on screen.



The missile is still only acknowledging the existence of one target. We'll fix that next.
Step 6: Seek the Closest Target
We have the missile seeking the target variable, so let's check the targets
Array and see which one is closer. The target variable will be referencing the closest at the beginning of the playGame function.
1 |
|
2 |
private function playGame(event:Event):void |
3 |
{
|
4 |
for (var i:int = 0; i < targets.length; i++) |
5 |
{
|
6 |
targetX = targets[i].x - missile.x; |
7 |
targetY = targets[i].y - missile.y; |
8 |
var dist:int = Math.sqrt(targetX * targetX + targetY * targetY);//the distance from one point to another in a 2D space. |
9 |
if (i == 0 || dist < distance) |
10 |
{
|
11 |
distance = dist; |
12 |
target = targets[i]; |
13 |
}
|
14 |
}
|
At this point the closest target is the only one moving, but the missile is acknowledging the existence of both:
Step 7: Fix the Cannon's Aim
You may have noticed that while the missile does seek the expected target, the cannon's stuck pointing at the same target, regardless if it's closer or farther than the other one. The distance is set in relation to the missile's position, so if there's no missile on stage we need to update its position to match the cannon's so that it always knows which one is closer.
1 |
|
2 |
private function playGame(event:Event):void |
3 |
{
|
4 |
for (var i:int = 0; i < targets.length; i++) |
5 |
{
|
6 |
targetX = targets[i].x - missile.x; |
7 |
targetY = targets[i].y - missile.y; |
8 |
var dist:int = Math.sqrt(targetX * targetX + targetY * targetY); |
9 |
if (i == 0 || dist < distance) |
10 |
{
|
11 |
distance = dist; |
12 |
target = targets[i]; |
13 |
}
|
14 |
}
|
15 |
if (!missileOut) |
16 |
{
|
17 |
missile.x = cannon.x; |
18 |
missile.y = cannon.y; |
19 |
targetX = target.x - cannon.x; |
20 |
targetY = target.y - cannon.y; |
21 |
cannon.rotation = Math.atan2(targetY, targetX) * 180 / Math.PI; |
22 |
}
|
Now the cannon will always aim at the closest target.
Step 8: Move the Cannon
Before the missile is shot, the cannon is already pointing to the closest target, and will change direction if moved closer to the other target. Let's add a couple of lines to position the cannon with the mouse cursor.
1 |
|
2 |
private function playGame(event:Event):void |
3 |
{
|
4 |
cannon.x = mouseX; |
5 |
cannon.y = mouseY; |
Now you can move the cannon freely.
Step 9: Target Hit, Target Destroyed
To make things more dynamic here, I'm going to relocate a target after being hit by a missile, or replace it by a new one, and leave an Explosion instance in its place.
1 |
|
2 |
if (missile.hitTestObject(target)) |
3 |
{
|
4 |
var explosion:Explosion = new Explosion(); |
5 |
addChild(explosion); |
6 |
explosion.x = missile.x; |
7 |
explosion.y = missile.y; |
8 |
removeChild(missile); |
9 |
missileOut = false; |
10 |
explosion= new Explosion(); |
11 |
addChild(explosion); |
12 |
explosion.x = target.x; |
13 |
explosion.y = target.y; |
14 |
explosion.scaleX = explosion.scaleY = 1.5; |
15 |
target.x = Math.random() * 600; |
16 |
}
|
This is what you'll get:
Step 10: Multiple Missiles
We made multiple targets, so now we can make multiple missiles the same way. The difference here is that all missiles must keep moving at all times until they hit, and we're actually going to remove those which have already exploded, so we need to modify a few lines of our code for this to work. First, we'll need an Array for the missiles.
1 |
|
2 |
private var missiles:Array = []; |
Then, we need to make sure all the missiles behave properly:
1 |
|
2 |
private function playGame(event:Event):void |
3 |
{
|
4 |
cannon.x = mouseX; |
5 |
cannon.y = mouseY; |
6 |
for (var i:int = 0; i < targets.length; i++) |
7 |
{
|
8 |
targetX = targets[i].x - missile.x; |
9 |
targetY = targets[i].y - missile.y; |
10 |
var dist:int = Math.sqrt(targetX * targetX + targetY * targetY); |
11 |
if (i == 0 || dist < distance) |
12 |
{
|
13 |
distance = dist; |
14 |
target = targets[i]; |
15 |
}
|
16 |
}
|
17 |
if (!missileOut) |
18 |
{
|
19 |
missile.x = cannon.x; |
20 |
missile.y = cannon.y; |
21 |
targetX = target.x - cannon.x; |
22 |
targetY = target.y - cannon.y; |
23 |
cannon.rotation = Math.atan2(targetY, targetX) * 180 / Math.PI; |
24 |
}
|
25 |
else
|
26 |
{
|
27 |
for (i = 0; i < missiles.length; i++)//each missile must keep moving |
28 |
{
|
29 |
missile = missiles[i]; |
30 |
if (missile.hitTestObject(target)) |
31 |
{
|
32 |
var explosion:Explosion = new Explosion(); |
33 |
addChild(explosion); |
34 |
explosion.x = missile.x; |
35 |
explosion.y = missile.y; |
36 |
removeChild(missile); |
37 |
missiles.splice(i, 1);//out of the Array |
38 |
if (missiles.length < 1)//only if no missiles are out at all |
39 |
missileOut = false; |
40 |
explosion= new Explosion(); |
41 |
addChild(explosion); |
42 |
explosion.x = target.x; |
43 |
explosion.y = target.y; |
44 |
explosion.scaleX = explosion.scaleY = 1.5; |
45 |
target.x = Math.random() * 600; |
46 |
}
|
47 |
else
|
48 |
{
|
49 |
targetX = target.x - missile.x; |
50 |
targetY = target.y - missile.y; |
51 |
var rotation:int = Math.atan2(targetY, targetX) * 180 / Math.PI; |
52 |
if (Math.abs(rotation - missile.rotation) > 180) |
53 |
{
|
54 |
if (rotation > 0 && missile.rotation < 0) |
55 |
missile.rotation -= (360 - rotation + missile.rotation) / ease; |
56 |
else if (missile.rotation > 0 && rotation < 0) |
57 |
missile.rotation += (360 - rotation + missile.rotation) / ease; |
58 |
}
|
59 |
else if (rotation < missile.rotation) |
60 |
missile.rotation -= Math.abs(missile.rotation - rotation) / ease; |
61 |
else
|
62 |
missile.rotation += Math.abs(rotation - missile.rotation) / ease; |
63 |
|
64 |
var vx:Number = speed * (90 - Math.abs(missile.rotation)) / 90; |
65 |
var vy:Number; |
66 |
if (missile.rotation < 0) |
67 |
vy = -speed + Math.abs(vx); |
68 |
else
|
69 |
vy = speed - Math.abs(vx); |
70 |
|
71 |
missile.x += vx; |
72 |
missile.y += vy; |
73 |
}
|
74 |
}
|
75 |
}
|
76 |
targetVY += gravity; |
77 |
target.y += targetVY; |
78 |
if (target.y > floor) |
79 |
{
|
80 |
target.y = floor; |
81 |
targetVY = -18; |
82 |
}
|
83 |
}
|
84 |
|
85 |
private function shoot(event:MouseEvent):void |
86 |
{
|
87 |
missile = new Missile(); |
88 |
missiles.push(missile);//into the Array |
89 |
addChild(missile); |
90 |
swapChildren(missile, cannon);//missile will come out from behind cannon |
91 |
missileOut = true; |
92 |
missile.x = cannon.x; |
93 |
missile.y = cannon.y; |
94 |
missile.rotation = cannon.rotation; |
95 |
}
|
Now when a target is destroyed, the missiles will seek the next target.
At this point all the missiles are chasing after the same target. In order to make each missile seek its own target, it would be better to make a separate class for the missiles in which you determine the closest target individually.
Step 11: Make a Crosshair
At this point you've already understood the main idea of this tutorial, but let's face it, an enemy will not move depending only on its distance to you or your missiles. You can use another indicator, such as a crosshair. Make it a Movie Clip and export it to Actionscript.

Step 12: Use the Crosshair
Now it will be obvious to anyone which target is being aimed at. Just add an instance of the Crosshair Movie Clip.
1 |
|
2 |
private var crosshair:Crosshair = new Crosshair(); |
3 |
|
4 |
public function Main() |
5 |
{
|
6 |
addChild(cannon); |
7 |
cannon.x = 50; |
8 |
cannon.y = 380; |
9 |
addEventListener(Event.ENTER_FRAME, playGame); |
10 |
stage.addEventListener(MouseEvent.CLICK, shoot); |
11 |
for (var i:int = 0; i < numTargets; i++) |
12 |
{
|
13 |
target = new Target(); |
14 |
addChild(target); |
15 |
target.x = Math.random() * 600; |
16 |
target.y = Math.random() * 400; |
17 |
targets.push(target); |
18 |
}
|
19 |
addChild(crosshair); |
20 |
}
|
Then place it on the target's position as the last instruction in the playGame function.
1 |
|
2 |
targetVY += gravity; |
3 |
target.y += targetVY; |
4 |
if (target.y > floor) |
5 |
{
|
6 |
target.y = floor; |
7 |
targetVY = -18; |
8 |
}
|
9 |
crosshair.x = target.x; |
10 |
crosshair.y = target.y; |
11 |
}
|
You'll get a crosshair marking the closest target's position.



Step 13: Move All Targets
Remember what I said about the missiles? The same applies to the targets: They'll look better in their own class with a set of instructions of its own. This is just a quick example, but in your game I don't recommend coding all the objects in the Main class. The more complex your game is, the less stuff you'll code inside the Main class.
The targets are already in an Array, which is already being checked in a for loop, so I'll move the bouncing instructions inside the for loop, so that all the targets, no matter how many, will move the same at all times.
1 |
|
2 |
private function playGame(event:Event):void |
3 |
{
|
4 |
cannon.x = mouseX; |
5 |
cannon.y = mouseY; |
6 |
targetVY += gravity; |
7 |
for (var i:int = 0; i < targets.length; i++) |
8 |
{
|
9 |
targetX = targets[i].x - missile.x; |
10 |
targetY = targets[i].y - missile.y; |
11 |
var dist:int = Math.sqrt(targetX * targetX + targetY * targetY); |
12 |
if (i == 0 || dist < distance) |
13 |
{
|
14 |
distance = dist; |
15 |
target = targets[i]; |
16 |
}
|
17 |
targets[i].y += targetVY; |
18 |
if (targets[i].y > floor) |
19 |
targets[i].y = floor; |
20 |
}
|
21 |
if (target.y >= floor) |
22 |
targetVY = -18; |
23 |
if (!missileOut) |
24 |
{
|
25 |
missile.x = cannon.x; |
26 |
missile.y = cannon.y; |
27 |
targetX = target.x - cannon.x; |
28 |
targetY = target.y - cannon.y; |
29 |
cannon.rotation = Math.atan2(targetY, targetX) * 180 / Math.PI; |
30 |
}
|
31 |
else
|
32 |
{
|
33 |
for (i = 0; i < missiles.length; i++) |
34 |
{
|
35 |
missile = missiles[i]; |
36 |
if (missile.hitTestObject(target)) |
37 |
{
|
38 |
var explosion:Explosion = new Explosion(); |
39 |
addChild(explosion); |
40 |
explosion.x = missile.x; |
41 |
explosion.y = missile.y; |
42 |
removeChild(missile); |
43 |
missiles.splice(i, 1); |
44 |
if (missiles.length < 1) |
45 |
missileOut = false; |
46 |
explosion= new Explosion(); |
47 |
addChild(explosion); |
48 |
explosion.x = target.x; |
49 |
explosion.y = target.y; |
50 |
explosion.scaleX = explosion.scaleY = 1.5; |
51 |
target.x = Math.random() * 600; |
52 |
}
|
53 |
else
|
54 |
{
|
55 |
targetX = target.x - missile.x; |
56 |
targetY = target.y - missile.y; |
57 |
var rotation:int = Math.atan2(targetY, targetX) * 180 / Math.PI; |
58 |
if (Math.abs(rotation - missile.rotation) > 180) |
59 |
{
|
60 |
if (rotation > 0 && missile.rotation < 0) |
61 |
missile.rotation -= (360 - rotation + missile.rotation) / ease; |
62 |
else if (missile.rotation > 0 && rotation < 0) |
63 |
missile.rotation += (360 - rotation + missile.rotation) / ease; |
64 |
}
|
65 |
else if (rotation < missile.rotation) |
66 |
missile.rotation -= Math.abs(missile.rotation - rotation) / ease; |
67 |
else
|
68 |
missile.rotation += Math.abs(rotation - missile.rotation) / ease; |
69 |
|
70 |
var vx:Number = speed * (90 - Math.abs(missile.rotation)) / 90; |
71 |
var vy:Number; |
72 |
if (missile.rotation < 0) |
73 |
vy = -speed + Math.abs(vx); |
74 |
else
|
75 |
vy = speed - Math.abs(vx); |
76 |
|
77 |
missile.x += vx; |
78 |
missile.y += vy; |
79 |
}
|
80 |
}
|
81 |
}
|
82 |
crosshair.x = target.x; |
83 |
crosshair.y = target.y; |
84 |
}
|
Take a look:
Conclusion
Homing missiles, heat-seeking missiles, both are a fun and useful weapon to have around in a shooting game or maybe some other type of app. This tutorial shows an example of its use and the algorithm to make it, but for best practices it is recommended that you have separate classes for the missiles and the targets, unless your app is as simple and short as this one.
I hope you've found this tutorial useful. Thanks for reading!