Advertisement
  1. Code
  2. JavaScript

Laser Generator, Obstacles and Accurate Hit Detection

Scroll to top
19 min read

Hi, friends. The main highlight of this tutorial is accurate hit detection of a generated laser. This kind of AI is useful in making action games, especially in the case of security intelligence with cameras, laser guns etc. So put your rocket on your back, countdown starts..


Final Result Preview

Let's take a look at the final result we will be working towards:


Step 1: Preparing Flash File

Open Flash and create a new Flash document (ActionScript 3.0).

Set stage size to any standard resolution. Mine is 500 x 350 px.

Set frame rate to 24 fps.

Save the file in a folder of your choice.


Step 2: Creating Laser Generator Device

Let's now create a laser generator.

Draw a circle of radius 20, i.e. 40 x 40. Also, fill it with some nice radial gradient color.

LaserGenerator_CircleLaserGenerator_CircleLaserGenerator_Circle

Now we need to convert this circle into a Flash symbol so that we can control it with ActionScript.

Select the circle and press F8 or go to Modify > Convert to Symbol. Select Movie Clip for the symbol type. Also set the registration point to centre so that the laser generator will rotate from its centre. Type laserGenerator_MC into instance name field and then last but not least check "Export for ActionScript" in "Advanced" group so that we can access laser generator from our document class which we are going to meet soon.

ConvertToSymbol_SettingsConvertToSymbol_SettingsConvertToSymbol_Settings

After setting all the above options press OK. Press OK again for the dialog box showing the warning for class definition. This will create a class definition for laser generator at runtime.

Now we do not need laserGenerator_MC symbol on to the stage since it is available in the Library panel with identifier name laserGenerator_MC. Therefore delete this symbol from the stage. Your stage should be empty now.

Library_PanelLibrary_PanelLibrary_Panel

Step 3: Improving the Look of Laser Generator

Now that we shall add a mouth to this generator to create some feel of a device. In the library panel double click on laserGenerator_MC symbol icon (left side of the symbol name) to go into edit mode. Add mouth to it as shown below.

Feel free to add your own design.

Note: Do not change the position of the circle since we need to rotate it around its centre.

LaserGenerator_With_MouthLaserGenerator_With_MouthLaserGenerator_With_Mouth

After adding a mouth to the generator, exit from the symbol edit mode and come back to main timeline.

Your rocket has left the ground. The laser generator device is ready and waiting to perform.


Step 4: Preparing Document Class

To add a drama to our scene we need ActionScript. To act smartly we need document class.

In this step we shall create a basic structure of our document class.

For detailed explanation about Document Class check out this Quick Tip.

So create new ActionScript 3.0 file. Type the following code:

1
2
package  {
3
	public class Laser_HitDetection{
4
		public function Laser_HitDetection () {
5
			// constructor code

6
		}
7
	}
8
}

Save this document class as Laser_HitDetection.as in the same folder where you saved your FLA for this tutorial. Nice take off. Your basic document class is ready.


Step 5: Attach Document Class to FLA

In your FLA file access the Properties panel for the Document and type the name of the class in the Document Class field available under "Publish" group.

ClassName_Field

Now we are ready to communicate with FLA through this Laser_HitDetection.as document class.


Step 6: Place and Align the Laser Generator at Stage Centre

We shall create an instance of laser generator from the library and place it to the centre of the stage.

Modify the Laser_HitDetection.as document class as shown below (highlighted lines):

1
2
package 
3
{
4
	import flash.display.Sprite;
5
	
6
	public class Laser_HitDetection extends Sprite
7
	{
8
		var laserGun:Sprite; //Instance of laserGenerator_MC		

9
10
		//Constructor

11
		public function Laser_HitDetection()
12
		{			
13
			CreateLaserGun();
14
					
15
		}
16
17
		//Get and place laser generator from library

18
		public function CreateLaserGun():void
19
		{
20
			laserGun = new laserGenerator_MC(); //Get it from the library

21
			addChild(laserGun);
22
23
			//Place laser generator at the centre of the stage

24
			laserGun.x = stage.stageWidth / 2;
25
			laserGun.y = stage.stageHeight / 2;
26
		}	
27
	}
28
}

If you test the movie now, you will see our laser gun nicely placed at the centre of the stage.


Step 7: Understanding CreateLaserGun() method

In the above code we used Sprite class to create an instance of Sprite object which will hold laser. For that we declare variable as:

1
2
var laserGun:Sprite;

Then we added a new method "CreateLaserGun()" in which we assigned an instance of laserGenerator_MC from the library to the above laserGun var as:

1
2
laserGun = new laserGenerator_MC();

After adding it to the stage we placed it at the centre of the stage as:

1
2
laserGun.x = stage.stageWidth / 2;
3
laserGun.y = stage.stageHeight / 2;

Finally we called this method from the constructor method of the document class as:

1
2
//Constructor

3
public function Laser_HitDetection()
4
{
5
	CreateLaserGun();
6
}

Rocket is accelerating with full thrust. We shall activate this laser gun very soon which will project the laser. Before that we shall add some obstacles. Let's go.


Step 8: Adding Obstacles of Different Shapes

To experiment with hit detection we need some obstacles placed on the stage. So, on the stage draw different shapes resembling convex-concave surfaces, slope-climbing, and straight edges, as shown below:

ObstaclesObstaclesObstacles

Step 9: Converting all Obstacles Into Single MovieClip

Now we shall put all these shapes in one symbol. Select all shapes at once and press F8 or go to Modify > Convert to Symbol. Select Movie Clip as the symbol type. Name this symbol obstacles_MC in name text field. Set registration point at centre. Also check "Export for ActionScript" so that we can access it from our document class.

After converting into MovieClip we have obstacles_MC in the library with the same identifier name. Simlilary as laserGenerator_MC we do not need this symbol on the stage, so delete it from the stage. Now your stage is empty.

Obstacles_MovieClipObstacles_MovieClipObstacles_MovieClip

Step 10: Place and Align the Obstacles Symbol at Stage Centre

The way we placed our laserGenerator_MC on to the stage, at the center, similarly we shall place obstacles_MC symbol on to the stage. Modify the document class as shown below:

1
2
package 
3
{
4
	import flash.display.Sprite;
5
	
6
	public class Laser_HitDetection extends Sprite
7
	{
8
		var laserGun:Sprite; //Instance of laserGenerator_MC

9
		var obstacles:Sprite; //Instance of obstacles_MC

10
		
11
		//Constructor

12
		public function Laser_HitDetection()
13
		{			
14
			CreateLaserGun();			
15
			CreateObstacles();		
16
		}
17
18
		//Get and place laser generator from library

19
		public function CreateLaserGun():void
20
		{
21
			laserGun = new laserGenerator_MC(); //Get it from the library

22
			addChild(laserGun);
23
24
			//Place laser generator at the centre of the stage

25
			laserGun.x = stage.stageWidth / 2;
26
			laserGun.y = stage.stageHeight / 2;
27
		}
28
		
29
		//Get and place obstacles from library

30
		public function CreateObstacles():void
31
		{
32
			obstacles = new obstacles_MC(); //Get it from the library

33
			addChild(obstacles);
34
			
35
			//Place obstacles at the centre of the stage

36
			obstacles.x = stage.stageWidth / 2;
37
			obstacles.y = stage.stageHeight / 2;
38
		}
39
	}
40
}

Test the movie to see obstacles placed around the laser gun.

LaserGun_ObstaclesLaserGun_ObstaclesLaserGun_Obstacles

Your rocket is reaching terminal velocity. Now it's time to activate the laser gun. It must generate the laser from it. Let's do that now.


Step 11: Projecting Laser

How will we mimic the laser? Any guess? ............... How about using some members of the Graphics class like lineStyle(), moveTo(), lineTo(). If you are familiar with these methods then your job is easy. For those who don't know these methods, we are always with you. Let us see them in detail.

We shall add a new method ProjectLaser(). Let us modify our Laser_HitDetection document class as shown below:

1
2
//Constructor

3
package 
4
{
5
	import flash.display.Sprite;
6
	
7
	public class Laser_HitDetection extends Sprite
8
	{
9
		var laserGun:Sprite; //Instance of laserGenerator_MC

10
		var obstacles:Sprite; //Instance of obstacles_MC

11
		
12
		var laser:Sprite;		
13
14
		var startX:Number; //x starting point of laser

15
		var startY:Number; //y starting point of laser

16
		var endX:Number; //x end point of laser

17
		var endY:Number; //y end point of laser

18
        
19
		//Constructor

20
		public function Laser_HitDetection()
21
		{			
22
			CreateLaserGun();			
23
			CreateObstacles();
24
			ProjectLaser();	
25
		}
26
27
		//Get and place laser generator from library

28
		public function CreateLaserGun():void
29
		{
30
			laserGun = new laserGenerator_MC(); //Get it from the library

31
			addChild(laserGun);
32
33
			//Place laser generator at the centre of the stage

34
			laserGun.x = stage.stageWidth / 2;
35
			laserGun.y = stage.stageHeight / 2;
36
		}
37
		
38
		//Get and place obstacles from library

39
		public function CreateObstacles():void
40
		{
41
			obstacles = new obstacles_MC(); //Get it from the library

42
			addChild(obstacles);
43
			
44
			//Place obstacles at the centre of the stage

45
			obstacles.x = stage.stageWidth / 2;
46
			obstacles.y = stage.stageHeight / 2;
47
		}
48
49
		//Project a laser from laser generator device

50
		public function ProjectLaser():void
51
		{
52
			laser = new Sprite();
53
			addChild(laser);
54
			
55
			//Set origin of the laser as the centre of the laser gun

56
			startX = laserGun.x;
57
			startY = laserGun.y;
58
			
59
			//Set end point of the laser			

60
			endX = startX + 230;
61
			endY = startY;
62
			
63
			//Draw laser

64
			laser.graphics.lineStyle(1, 0xFF0000);			
65
			laser.graphics.moveTo(startX, startY);
66
			laser.graphics.lineTo ( endX, endY );
67
		}
68
	}
69
}

Test the movie.

LaserGun_Projecting_LaserLaserGun_Projecting_LaserLaserGun_Projecting_Laser

The rocket is right there up in the sky. The laser gun has started projecting the laser. How did that happen? Let's try and understand the above code in the next step.


Step 12: How is the Laser Projected?

In the above step we successfully projected a laser. For that we performed the following tasks:

First, we declared five new variables:

1
2
var laser:Sprite;
3
4
var startX:Number; //x starting point of laser

5
var startY:Number; //y starting point of laser

6
var endX:Number; //x end point of laser

7
var endY:Number; //y end point of laser

Second, we added a new method ProjectLaser():

1
2
public function ProjectLaser():void
3
{
4
	laser = new Sprite();
5
	addChild( laser );
6
	
7
	//Set origin of the laser as the centre of the laser gun

8
	startX = laserGun.x;
9
	startY = laserGun.y;
10
	
11
	//Set end point of the laser

12
	endX = startX + 230;
13
	endY = startY;
14
15
	//Draw laser

16
	laser.graphics.lineStyle ( 1, 0xFF0000 );
17
	laser.graphics.moveTo ( startX, startY );
18
	laser.graphics.lineTo ( endX, endY );
19
}

Step 13: Understanding ProjectLaser() Method

In the above method first we created an empty Sprite object to hold the laser and also added it to the stage as shown below:

1
2
laser = new Sprite();
3
addChild( laser );

As we wanted the laser to start projecting from the laser gun, we assign laser gun's X value to startX and Y value to startY as shown below:

1
2
startX = laserGun.x;
3
startY = laserGun.y;

(Later we provided these values to the moveTo(startX, startY) method which we are going to meet soon.)

Then we defined endX and endY:

1
2
endX = startX + 230;
3
endY = startY;

Values assigned to above variables are temporary values. We used them just to show the basic projection of a laser. In coming steps we shall modify these values by applying some simple math. These values are key to making perfect hit detection of the laser. We shall study them later in this session.

And now the important part of this method. Drawing a straight line within laser sprite object to mimic the projected laser.

First we handled the style of the line to be drawn as shown below:

1
2
laser.graphics.lineStyle ( 1, 0xFF0000 );

This lineStyle() method is used to control the styling of the stroke of the drawing object such as line, rectangle, oval etc. You can provide maximum of eight arguments to this method. If not specified default values are assigned instead. For our example we need only two arguments. The first argument is thickness of the line (i.e. 1) and the second argument is the color of the line (i.e. 0xFF0000, which is red).

For detailed explanation of this method check out the Adobe help doc on "lineStyle(args..)" method.

Then we placed the starting point of the line as shown below:

1
2
laser.graphics.moveTo ( startX, startY );

startX and startY ensures that the starting point must be the centre of the laser gun.

After that we finsihed up the line:

1
2
laser.graphics.lineTo ( endX, endY );

Remember that these endX and endY are temporary values just to show the projection. We need them to get adjusted if any obstacle comes in the way of laser. We shall do that math in coming steps.

So, we drew a line from (startX, startY) to (endX, endY).

The rocket is going and going. See those landscapes, waterscapes..

Now, it's time for the real action. Hit detection with obstacles.


Step 14: Hit Detection with Obstacles

Now, we are equipped with a laser gun. We also have several obstacles. We are at the level where we can add hit detection which will certainly add the meaning to the scene.

You might be thinking that this highly accurate hit detection will require complex math. If so then you are wrong. It simply uses the hitTestPoint() built in method of ActionScript along with a for loop. The complex math behind perfect hit detection is handled by this method. You only need to utilize this method and a for loop in a smart way.

We are going to make some important changes to our document class mainly to ProjectLaser() method and some new vars, so observe and apply it carefully. Let us modify it as shown:

First add these new vars:

1
2
var rad:Number = Math.PI/180; //Used to calculate angle in radians

3
var maxDist:Number = 250; //maximum distance to be travelled by the laser

4
var adjustedDist:Number; //new maximum distance if any obstacle comes in a way

Then modify ProjectLaser() method by adding for loop as shown below (also note that now endX and endY are inside for loop):

1
2
//Project a laser from laser generator device

3
public function ProjectLaser():void
4
{
5
	laser = new Sprite();
6
	addChild(laser);
7
			
8
	//Set origin of the laser as the centre of the laser gun

9
	startX = laserGun.x;
10
	startY = laserGun.y;
11
12
	for (adjustedDist = 0; adjustedDist < maxDist; adjustedDist ++)
13
	{
14
		//Trigonometry to aim the laser w.r.t laser gun's rotation

15
		endX = laserGun.x + Math.cos ( laserGun.rotation * rad ) * adjustedDist;
16
		endY = laserGun.y + Math.sin ( laserGun.rotation * rad ) * adjustedDist;
17
18
		//calculate hit test

19
		if ( obstacles.hitTestPoint ( endX, endY, true ) )
20
		{
21
			break;
22
		}
23
	}
24
25
	//Draw laser

26
	laser.graphics.lineStyle( 1, 0 x FF0000 );
27
	laser.graphics.moveTo( startX, startY );
28
	laser.graphics.lineTo ( endX, endY );
29
}

Test the movie.

Laser_HitDetectionLaser_HitDetectionLaser_HitDetection

Boom.... I am sure. Looking at this effect you are inspired to create a Flash Game having security intelligence. Perfect AI, to develop interesting game in Flash.


Step 15: How the Hit Detection Happened

First of all we added new vars to our document class as shown:

Initially, an angle, in radians

1
2
var rad:Number = Math.PI/180; //Used to calculate angle in radians

The above var is used in formula to calculate an angle in radians.

The formula is, radians = degrees * Math.PI/180. More info here. We used this formula in our code, in these lines:

1
2
endX = laserGun.x + Math.cos ( laserGun.rotation * rad ) * adjustedDist;
3
endY = laserGun.y + Math.sin ( laserGun.rotation * rad ) * adjustedDist;

In laserGun.rotation * rad, degrees = laserGun.rotation, and rad = Math.PI/180.

Second, we created a var for the maximum distance to be travelled by the laser:

1
2
var maxDist:Number = 250; //maximum distance to be travelled by the laser

This var decides the maximum distance to be travelled by the laser. Since it is not necessary to draw laser beyond visible area, we define a nice end point. This var will do what's needed.

Laser_MaxDistance

Third, we have a var for the current distance of the laser at the time of intersection with any obstacle.

1
2
var adjustedDist:Number; //new maximum distance if any obstacle comes in a way

When any obstacle comes in the way of laser, the laser is blocked rather than traveling to maximum distance.

The distance after blocking a laser is nothing but the adjusted distance depending on the situation.

The for loop and hitTestPoint() each play an important role in calculating this distance.

Laser_AdjustedDistance

Fourth, we modified ProjectLaser() method by adding ActionScript's hitTestPoint() along with a for loop. We reassigned endX and endY variables.

1
2
for (adjustedDist = 0; adjustedDist < maxDist; adjustedDist ++)
3
{
4
	//Trigonometry to aim the laser w.r.t laser gun's rotation

5
	endX = laserGun.x + Math.cos ( laserGun.rotation * rad ) * adjustedDist;
6
	endY = laserGun.y + Math.sin ( laserGun.rotation * rad ) * adjustedDist;
7
8
	//calculate hit test

9
	if ( obstacles.hitTestPoint ( endX, endY, true ) )
10
	{
11
		break;
12
	}
13
}

The for loop ensures that the adjusted distance will be increased up to maximum distance. You might be thinking "What is the fun in making adjusted distance equal to maximum distance?"

Actually, this adjusted distance is allowed to match the maximum distance naturally but as soon as it hits any obstacles (which is detected by hitTestPoint() method) this distance is marked as maximum distance for that current situation. Thanks to hitTestPoint() for making the task so simple.

We also reassigned endX and endY values as:

1
2
endX = laserGun.x + Math.cos ( laserGun.rotation * rad ) * adjustedDist;
3
endY = laserGun.y + Math.sin ( laserGun.rotation * rad ) * adjustedDist;

The trigonometry functions Math.cos() and Math.sin() are used to calculate the angle of laser w.r.t laser gun rotation.

In simple form, it takes care of aiming the laser where the laser gun is looking. It is important that laser gun and laser are both in sync. So whenever the laser gun is rotated the laser will follow the rotation.

For more information on trigonometry, read this Quick Tip.

Finally the climax of the scene:

1
2
if ( obstacles.hitTestPoint ( endX, endY, true ) )
3
{
4
	break;
5
}

hitTestPoint() method takes three parameters, out of which the first and second parameters are necessary and the third one (shapeFlag) will be left as the default (i.e. false) if not specified.

In our example, shapeFlag is set to true since we want hit detection with respect to the exact shape of the target object (i.e. obstacles). If this value is set to false the hit detection occurs with respect to the bounding box of that object and not the exact shape.

The first and second parameters of hitTestPoint() define a point (x, y) at which to check for intersection with the display object. In our case obstacles is that display object, and endX and endY represents the point of intersection on the stage.

Unclear? Right. To simplify we shall put the logic in sequence as follows:

  1. The for loop allows the projection of the laser to continue (by updating endX and endY) if within maximum distance.
  2. hitTestPoint() is waiting to break the for loop as soon as it sees the intersection. Once the for loop is broken, endX and endY are frozen.
  3. Finally these frozen endX and endY are passed to the graphics.lineTo() method.

Step 16: Applying Rotation to Laser Gun

To add rotation to laser gun we need to perform some major changes:

  1. Re-Structuring ProjectLaser () method.
  2. Placing the entire hit detection logic in new method HitTest().
  3. Adding new statement laser.graphics.clear() in the for loop.
  4. Import statement import flash.events.Event

First we shall restructure the ProjectLaser() method by shifting the entire hit detection logic in new method HitTest().

Then we shall add laser.graphics.clear() statement before laser.graphics.lineStyle( 1, 0xFF0000 ) statement inside the for loop of new method HitTest(). This will remove the old projection of the laser from the stage when the laser gun will start rotating.

Let's see how it will look after making all four major changes:

1
2
public function ProjectLaser():void
3
{
4
	laser = new Sprite();
5
	addChild(laser);
6
7
	//Set origin of the laser as the centre of the laser gun

8
	startX = laserGun.x;
9
	startY = laserGun.y;
10
}
11
12
//Hit test

13
public function HitTest():void
14
{
15
	for (adjustedDist = 0; adjustedDist < maxDist; adjustedDist ++)
16
	{
17
		//Trigonometry to aim the laser w.r.t laser gun's rotation

18
		endX = laserGun.x + Math.cos(laserGun.rotation * rad) * adjustedDist;
19
		endY = laserGun.y + Math.sin(laserGun.rotation * rad) * adjustedDist;
20
21
		//calculate hit test

22
		if (obstacles.hitTestPoint(endX,endY,true))
23
		{
24
			break;
25
		}
26
	}
27
28
	//Draw laser

29
	laser.graphics.clear(); //Removes the old laser projection

30
	laser.graphics.lineStyle( 1, 0xFF0000 );
31
	laser.graphics.moveTo( startX, startY );
32
	laser.graphics.lineTo ( endX, endY );
33
}

You might be asking why we did such major changes? The only reason is ENTER_FRAME event.

To apply continuous rotation we shall add a new method, LaserGunRotation( evt:Event ) which is activated by ENTER_FRAME event (meaning it is run 24 times a second, since our frame rate is 24fps). When this event is used, you must be careful to add only such properties those we want to change over period of time. Avoid putting in those values which remain constant throughout the execution.

In our case, if you observe old ProjectLaser() method, it has:

1
2
laser = new Sprite();
3
addChild(laser); //Rotate Laser Gun

Imagine if you add above statements in a method which uses ENTER_FRAME event; then a new laser Sprite object would be created and added to stage repeatedly -- 24 times every second.

This is absolutely unnecessary. Therefore we restructured ProjectLaser() method and added new method HitTest(). You can rename ProjectLaser() method to InitializeLaser() since it no longer projects the laser. Now it only creates the empty holder for the laser and defines its starting point. The projection is handled in a new method, HitTest().

Now let us see the new method LaserGunRotation( evt:Event ). Before that add the following import statement at the start of the document class:

1
2
import flash.events.Event;

And add the following method:

1
2
//Rotate Laser Gun

3
public function LaserGunRotation( evt:Event ):void
4
{
5
	laserGun.rotation += 0.5;
6
7
	HitTest();
8
}

Also, do not forget to call this method with an ENTER_FRAME event inside constructor function as shown below:

1
2
//Constructor

3
public function Laser_HitDetection()
4
{
5
	CreateLaserGun();
6
	CreateObstacles();
7
	ProjectLaser();
8
    
9
	addEventListener( Event.ENTER_FRAME, LaserGunRotation );
10
}

This sets it up to run the LaserGunRotation() function 24 times a second.

Test the movie.

The rocket has already penetrated clouds. See the beautiful Earth.


Step 17: Adding Control to the Accuracy of Hit Detection

In this step we shall add a control to adjust the accuracy of the hit detection. This is important since you do not need the precise hit detection every time; you might need an average level of hit detection. This will also help in reducing the CPU consumption at the time of hit detection.

We shall introduce a new variable tolerance as:

1
2
var tolerance:Number = 1;

Then we shall modify the for loop's increment statement as:

1
2
for (adjustedDist = 0; adjustedDist < maxDist; adjustedDist += tolerance)

Now the for loop will look like:

1
2
for (adjustedDist = 0; adjustedDist < maxDist; adjustedDist += tolerance)
3
{
4
	//Trigonometry to aim the laser w.r.t laser gun's rotation

5
	endX = laserGun.x + Math.cos(laserGun.rotation * rad) * adjustedDist;
6
	endY = laserGun.y + Math.sin(laserGun.rotation * rad) * adjustedDist;
7
8
	//calculate hit test

9
	if (obstacles.hitTestPoint(endX,endY,true))
10
	{
11
		break;
12
	}
13
}

This means that instead of checking for a collision at every pixel of the laser line, we are only checking for a collision at every other pixel, or every third pixel, etc.

Lowering the value for "tolerance" will increase the accuracy of the hit detection but require more CPU power. Try experimenting with different values for "tolerance".

Friends, it's time to leave the rocket and open the parachute. Land safely on the ground and start using the above technique in your Flash games and applications. Enjoy!


Conclusion:

In this tutorial we mainly saw the perfect utilization of hitTestPoint() and a for loop to create accurate hit detection without using any complex math.

This effect can be used in a security type action game where cameras and laser guns are required.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.