Hit the Target With a Deadly Homing Missile
This tutorial will guide you through adding deadly accurate homing missiles to the arsenal of your next game.
Final Result Preview
Let's take a look at the final result we will be working towards:
Step 1: Set up the FLA Document
Create a new Flash document set for ActionScript 3.0. I'll be using the dimensions of 600x400, and a frame rate of 30 FPS. Save the file with a name of your choice.
Step 2: Create a Document Class
Besides the FLA, we also need to create a document class. Create a new Actionscript file, and add this code:
1 |
|
2 |
package
|
3 |
{
|
4 |
import flash.display.Sprite; |
5 |
|
6 |
public class Main extends Sprite |
7 |
{
|
8 |
public function Main() |
9 |
{
|
10 |
|
11 |
}
|
12 |
}
|
13 |
|
14 |
}
|
Save this file in the same directory as our FLA. Name it Main.as.
Step 3: Link the Main Class with the FLA
In order to compile the code from the Main class, we need to link it with the FLA. On the Properties panel of the FLA, next to Class, enter the name of the document class, in this case, Main.
Then, save the changes on the FLA.
Step 4: Draw a Missile
We need a missile graphic to be displayed when shooting. You may import a bitmap or draw a shape right there on Flash. I'll be using a tiny shape on this example.
What's important here is that you have to make the missile point straight to the right, since that's the origin point for the rotation. So 0° means pointing straight to the right, -90° means upwards, 90° means downwards, and 180° points to the left. Later on we'll need to rotate the missile according to its direction.
Step 5: Create a MovieClip for the Missile
Once you have the missile graphic, select it and press F8 to create a Movie Clip. Name it "Missile", make sure the the Registration Point is at the center, and tick the "Export for ActionScript" checkbox.
You'll end up with a Missile MovieClip in the Library.
If you have a Missile instance on the Stage, delete it. We'll be adding the Missile MovieClip by code.
Step 6: Aim
The first thing a homing missile needs to know is where the target is located. We're going to set the rotation of the missile according to the position of the mouse cursor first. Let's work with the enterFrame Event for a constant rotation update.
Add a Missile instance to the stage, I'm placing it at the center (300, 200). Then calculate the distance from the missile to the mouse cursor (I'm storing it in variables targetX and targetY). Finally, the missile's angle will be the arc tangent of both points (targetX, targetY). The result you'll get will be in radians, but the rotation works in degrees, so you'll need to do the conversion by multiplying by 180/Pi. (To see why, check this article.)
1 |
|
2 |
import flash.events.Event; |
3 |
|
4 |
public class Main extends Sprite |
5 |
{
|
6 |
private var missile:Missile = new Missile(); |
7 |
|
8 |
public function Main() |
9 |
{
|
10 |
addChild(missile); |
11 |
missile.x = 300; |
12 |
missile.y = 200; |
13 |
addEventListener(Event.ENTER_FRAME, playGame); |
14 |
}
|
15 |
|
16 |
private function playGame(event:Event):void |
17 |
{
|
18 |
var targetX:int = mouseX - missile.x; |
19 |
var targetY:int = mouseY - missile.y; |
20 |
missile.rotation = Math.atan2(targetY, targetX) * 180 / Math.PI; |
21 |
}
|
22 |
}
|
(Not sure what Math.atan2()
is for? Check out this article on trigonometry.
If you Publish (Ctrl + Enter) the document at this point, you should be getting something like this:
Move your mouse near the missile to see it rotate.
Step 7: Seek
We got the rotation, now we need the movement. The missile has to seek the target, no matter whether it's a steady or a moving target. What we'll do is calculate the movement according to the current rotation of the missile. Let's set a value for the speed, and make the missile chase after the mouse cursor.
We'll include a couple of new variables to calculate the velocity (vx, vy). When the missile is pointing to the right, its angle is lower than 90° and higher than -90°, so it's always lower than the absolute value of 90°. When it's pointing to the left, its angle has an absolute value higher than 90°. This will determine vx in accordance to speed, then vy will be the difference of speed and vx.
1 |
|
2 |
private var speed:int = 10; |
3 |
|
4 |
public function Main() |
5 |
{
|
6 |
addChild(missile); |
7 |
missile.x = 300; |
8 |
missile.y = 200; |
9 |
addEventListener(Event.ENTER_FRAME, playGame); |
10 |
}
|
11 |
|
12 |
private function playGame(event:Event):void |
13 |
{
|
14 |
var targetX:int = mouseX - missile.x; |
15 |
var targetY:int = mouseY - missile.y; |
16 |
missile.rotation = Math.atan2(targetY, targetX) * 180 / Math.PI; |
17 |
//Velocity in x is relative to the angle, when it's 90° or -90°, vx should be 0.
|
18 |
var vx:Number = speed * (90 - Math.abs(missile.rotation)) / 90; |
19 |
var vy:Number;//Velocity in y is the difference of speed and vx. |
20 |
if (missile.rotation < 0) |
21 |
vy = -speed + Math.abs(vx);//Going upwards. |
22 |
else
|
23 |
vy = speed - Math.abs(vx);//Going downwards. |
24 |
|
25 |
missile.x += vx; |
26 |
missile.y += vy; |
27 |
}
|
You'll get a missile chasing your cursor.
You can use a different speed if you want.
Step 8: Create a Missile Launcher
Missiles don't come out of thin air, they are shot out of missile launchers. Let's make a MovieClip representing a cannon (I'll use a simple rectangle), and name it Cannon. I'm going to add a Cannon instance by code, so I'm going to keep the stage empty.
Step 9: Shoot
Now, instead of adding a missile, I'm just going to add a cannon, and a missile will be added at the cannon's position when I click on the stage. We'll add a Boolean to check if the missile has been shot, and also a new function for shooting after the click.
1 |
|
2 |
import flash.events.MouseEvent; |
3 |
|
4 |
public class Main extends Sprite |
5 |
{
|
6 |
private var missile:Missile = new Missile(); |
7 |
private var speed:int = 10; |
8 |
private var cannon:Cannon = new Cannon(); |
9 |
private var missileOut:Boolean = false;//Has the missile been shot? |
10 |
|
11 |
public function Main() |
12 |
{
|
13 |
addChild(cannon); |
14 |
cannon.x = 50; |
15 |
cannon.y = 380; |
16 |
addEventListener(Event.ENTER_FRAME, playGame); |
17 |
stage.addEventListener(MouseEvent.CLICK, shoot); |
18 |
}
|
19 |
|
20 |
private function playGame(event:Event):void |
21 |
{
|
22 |
if (missileOut) |
23 |
{
|
24 |
var targetX:int = mouseX - missile.x; |
25 |
var targetY:int = mouseY - missile.y; |
26 |
missile.rotation = Math.atan2(targetY, targetX) * 180 / Math.PI; |
27 |
|
28 |
var vx:Number = speed * (90 - Math.abs(missile.rotation)) / 90; |
29 |
var vy:Number; |
30 |
if (missile.rotation < 0) |
31 |
vy = -speed + Math.abs(vx); |
32 |
else
|
33 |
vy = speed - Math.abs(vx); |
34 |
|
35 |
missile.x += vx; |
36 |
missile.y += vy; |
37 |
}
|
38 |
}
|
39 |
|
40 |
private function shoot(event:MouseEvent):void |
41 |
{
|
42 |
if (!missileOut) |
43 |
{
|
44 |
addChild(missile); |
45 |
swapChildren(missile, cannon);//missile will come out from behind cannon |
46 |
missileOut = true; |
47 |
missile.x = cannon.x; |
48 |
missile.y = cannon.y; |
49 |
}
|
50 |
}
|
This is what you'll get:
This doesn't look nice. We have to either make the cannon rotate as well, or force the missile to go upwards right after being shot. Since option #1 is the easiest approach, we'll take option #2.
Step 10: Less Precision for Better Looks
If the cannon is vertical, we would expect the missile to launch upwards and then get on track towards its target. The approach I'll use to achieve this is to give the missile a starting angle of -90° (pointing upwards), and smoothly rotate to get on track to the mouse cursor. We'll add an ease variable to determine the smoothness or sharpness of the rotation. Then we'll create another variable to keep track of the actual rotation that points straight to the target, while the missile's rotation will change according to the ease we set (ease = 1 will behave just like before, anything higher will make a smoother turn).
Since half of the rotation values are negative, in some cases we'll need to calculate them against 360 to get the actual difference between the target angle and the missile's rotation.
1 |
|
2 |
private var ease:int = 10; |
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 |
}
|
12 |
|
13 |
private function playGame(event:Event):void |
14 |
{
|
15 |
if (missileOut) |
16 |
{
|
17 |
var targetX:int = mouseX - missile.x; |
18 |
var targetY:int = mouseY - 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 |
|
44 |
private function shoot(event:MouseEvent):void |
45 |
{
|
46 |
if (!missileOut) |
47 |
{
|
48 |
addChild(missile); |
49 |
swapChildren(missile, cannon);//missile will come out from behind cannon |
50 |
missileOut = true; |
51 |
missile.x = cannon.x; |
52 |
missile.y = cannon.y; |
53 |
missile.rotation = -90;//missile will start pointing upwards |
54 |
}
|
55 |
}
|
Check it out:
Notice what happens when you move your mouse out of the SWF, and how this is different from the previous example.
Step 11: Missile Hits, Missile Explodes
Besides the Missile Movie Clip, we need an explosion animation. In my case, I'll make a separate MovieClip with a simple tween of a circle that expands. I'm exporting it as Explosion. Press O to select the Oval Tool, and hold Shift while drawing the oval to get a circle.
For a nicer visual effect, I'll put the circle inside another Movie Clip of its own, and give it a Bevel filter to get a darker color at the bottom and a lighter color at the top. Next, I'll go to frame 10 and press F6 to create a Keyframe, then right-click between frame 1 and 10 and create a Classic Tween. Back on frame 10, press Q to select the Free Transform Tool and enlarge the circle.
Then, create another Classic Tween to frame 20, I'll add a Blur filter effect.
Finally, make it disappear in a last Classic Tween to frame 30 with an Alpha color effect going to 0.
Step 12: Clean Up the Stage
The explosion animation has to be removed after it finishes, or it will loop indefinitely. Add a new layer and press F6 on the last frame, then press F9 to open the Actions panel, and add this code:
1 |
|
2 |
stop();<br />parent.removeChild(this); |
This will make the Explosion instance remove itself after the animation is done.
Step 13: Explode
Now when the missile meets the cursor, we'll replace it with an Explosion instance. We just need to add a new conditional in the playGame()
function.
1 |
|
2 |
private function playGame(event:Event):void |
3 |
{
|
4 |
if (missileOut) |
5 |
{
|
6 |
if (missile.hitTestPoint(mouseX, mouseY)) |
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 |
var targetX:int = mouseX - missile.x; |
18 |
var targetY:int = mouseY - 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 |
}
|
Take a look:
Step 14: Something Else to Blow Up
Chasing after the mouse cursor was entertaining, but it's pointless in a game; we need to make a target. I'm going to draw a bunch of circles to form a Target Movie Clip.
Step 15: Shoot the Target
Now we'll add a Target instance for the missile to have a more tangible objective. So we'll replace any reference of the mouse cursor for the target's position. Also, we won't be testing for a hit point, but an object.
1 |
|
2 |
private var target:Target = new Target(); |
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);addChild(target); |
11 |
target.x = 550; |
12 |
target.y = 50; |
13 |
}
|
14 |
|
15 |
private function playGame(event:Event):void |
16 |
{
|
17 |
if (missileOut) |
18 |
{
|
19 |
if (missile.hitTestObject(target)) |
20 |
{
|
21 |
var explosion:Explosion = new Explosion(); |
22 |
addChild(explosion); |
23 |
explosion.x = missile.x; |
24 |
explosion.y = missile.y; |
25 |
removeChild(missile); |
26 |
missileOut = false; |
27 |
}
|
28 |
else
|
29 |
{
|
30 |
var targetX:int = target.x - missile.x; |
31 |
var targetY:int = target.y - missile.y; |
32 |
var rotation:int = Math.atan2(targetY, targetX) * 180 / Math.PI; |
33 |
if (Math.abs(rotation - missile.rotation) > 180) |
34 |
{
|
35 |
if (rotation > 0 && missile.rotation < 0) |
36 |
missile.rotation -= (360 - rotation + missile.rotation) / ease; |
37 |
else if (missile.rotation > 0 && rotation < 0) |
38 |
missile.rotation += (360 - rotation + missile.rotation) / ease; |
39 |
}
|
40 |
else if (rotation < missile.rotation) |
41 |
missile.rotation -= Math.abs(missile.rotation - rotation) / ease; |
42 |
else
|
43 |
missile.rotation += Math.abs(rotation - missile.rotation) / ease; |
44 |
|
45 |
var vx:Number = speed * (90 - Math.abs(missile.rotation)) / 90; |
46 |
var vy:Number; |
47 |
if (missile.rotation < 0) |
48 |
vy = -speed + Math.abs(vx); |
49 |
else
|
50 |
vy = speed - Math.abs(vx); |
51 |
|
52 |
missile.x += vx; |
53 |
missile.y += vy; |
54 |
}
|
55 |
}
|
56 |
}
|
57 |
|
58 |
private function shoot(event:MouseEvent):void |
59 |
{
|
60 |
if (!missileOut) |
61 |
{
|
62 |
addChild(missile); |
63 |
swapChildren(missile, cannon); //missile will come out from behind cannon |
64 |
missileOut = true; |
65 |
missile.x = cannon.x; |
66 |
missile.y = cannon.y; |
67 |
missile.rotation = -90;//missile will start pointing upwards |
68 |
}
|
69 |
}
|
The hitTestObject()
method actually only checks for an overlap between the bounding boxes of the two objects (i.e., the blue boxes that appear when you click an instance of the object in the stage), so watch out for that; it's not pixel-perfect collision detection. However, it does the job just fine here.
You may try placing the target at different locations, as well as the cannon.
Step 16: Moving Target
We already saw that the missile will chase a moving target, such as the mouse cursor, so now let's make the Target instance move a little.
This isn't realistic physics, I'm just going to make the target bounce vertically. I'll pick a reference point as the ground level, and add a gravity value to affect the target. And to make it more dynamic, I'll increase the missile speed to 15.
1 |
|
2 |
private var floor:int = 385; |
3 |
private var gravity:Number = 0.5; |
4 |
private var targetVY:Number = 0;//Current vertical velocity of the target |
5 |
|
6 |
public function Main() |
7 |
{
|
8 |
addChild(cannon); |
9 |
cannon.x = 50; |
10 |
cannon.y = 380; |
11 |
addEventListener(Event.ENTER_FRAME, playGame); |
12 |
stage.addEventListener(MouseEvent.CLICK, shoot);addChild(target); |
13 |
target.x = 550; |
14 |
target.y = 50; |
15 |
}
|
16 |
|
17 |
private function playGame(event:Event):void |
18 |
{
|
19 |
if (missileOut) |
20 |
{
|
21 |
if (missile.hitTestObject(target)) |
22 |
{
|
23 |
var explosion:Explosion = new Explosion(); |
24 |
addChild(explosion); |
25 |
explosion.x = missile.x; |
26 |
explosion.y = missile.y; |
27 |
removeChild(missile); |
28 |
missileOut = false; |
29 |
}
|
30 |
else
|
31 |
{
|
32 |
var targetX:int = target.x - missile.x; |
33 |
var targetY:int = target.y - missile.y; |
34 |
var rotation:int = Math.atan2(targetY, targetX) * 180 / Math.PI; |
35 |
if (Math.abs(rotation - missile.rotation) > 180) |
36 |
{
|
37 |
if (rotation > 0 && missile.rotation < 0) |
38 |
missile.rotation -= (360 - rotation + missile.rotation) / ease; |
39 |
else if (missile.rotation > 0 && rotation < 0) |
40 |
missile.rotation += (360 - rotation + missile.rotation) / ease; |
41 |
}
|
42 |
else if (rotation < missile.rotation) |
43 |
missile.rotation -= Math.abs(missile.rotation - rotation) / ease; |
44 |
else
|
45 |
missile.rotation += Math.abs(rotation - missile.rotation) / ease; |
46 |
|
47 |
var vx:Number = speed * (90 - Math.abs(missile.rotation)) / 90; |
48 |
var vy:Number; |
49 |
if (missile.rotation < 0) |
50 |
vy = -speed + Math.abs(vx); |
51 |
else
|
52 |
vy = speed - Math.abs(vx); |
53 |
|
54 |
missile.x += vx; |
55 |
missile.y += vy; |
56 |
}
|
57 |
}
|
58 |
targetVY += gravity; |
59 |
target.y += targetVY; |
60 |
if (target.y > floor) |
61 |
{
|
62 |
target.y = floor; |
63 |
targetVY = -18; |
64 |
}
|
65 |
}
|
If you Publish this now, you should be getting a moving target.
Conclusion
Whether you want an accurate homing missile, or you'd prefer a smooth animation, you can get both results based on this example. Now you've got a new weapon to add in your arsenal, maybe you could try making a Worms-like game, or even use the algorithm on something other than a missile, like some weird mosquito that follows your character.
I hope you've found this tutorial useful. Thanks for reading!