Flash - Collision Detection using ActionScript 3.0

Published:
Updated:
This is intended to introduce all collision detection principles in flash, their strengths, weaknesses and workarounds. The main method for Collision Detection in flash is using hitTestObject. But unless you'll be pushing rectangular shapes without any rotation into each other, it won't do much work.  It's mainly oriented to flash game developers, but anyone who bumps stuff in flash can find use of it.
 

1. hitTestObject

Simple and Easy     Its strength-it's light and doesn't really require much math and as such is barely noticeable processor-wise. This means you can have many objects using this hitTest-ing at frame rate. It's weakness-it's mostly useless on its own.Let's take a look at some examples.First,let's start with two rectangular shapes and push them together.A new file,two rectangles converted to symbols,given instance names of rect1 and rect2 and a dynamic text field named txt_hit.Frame 1 gets this code:
 
stage.addEventListener(MouseEvent.MOUSE_MOVE, rectMove);
                      stage.addEventListener(Event.ENTER_FRAME, hitTest);
                      function rectMove(event:MouseEvent)
                      {
                      	rect1.x = mouseX;
                      	rect1.y = mouseY;
                      }
                      function hitTest(event:Event)
                      {
                      	if(rect1.hitTestObject(rect2))
                      	{
                      		txt_hit.text = "HIT!";
                      	}
                      	else
                      	{
                      		txt_hit.text = "";
                      	}
                      }

Open in new window

    Perfect hitTest-ing! If you test the movie you can see it detects collision like a charm!
hitTest rectangles     But what if you rotate the rectangles or replace them with circles or any other irregular shape? Let's try.
hitTest irregular shapes     As you can see the collision detection is way off.That's because hitTestObject uses the bounding boxes of objects. In other words it gets the highest and lowest possible points,the leftmost and rightmost points and draws a rectangle around them. If your graphic is a rectangle, the bounding box is the same as the graphic. Otherwise,you get results as in the previous picture.
 

2. hitTestPoint

A little bit more advanced     Its strength - it detects empty space within the bounding box of objects. Its weakness - it doesn't detect pixels with alpha channel, it just detects whether there is or there isn't any information within a point in the bounding box. A transparent pixel is still a pixel, while a blank space in a vector graphic does not contain any information. It pretty much works with vector graphics only. The other disadvantage is it tests point and object not two objects. This makes it difficult for the programmer, but not impossible. For example's sake let's do the same as in the hitTestObject but using hitTestPoint. What we'll do is kind of improvised MOUSE_OVER event. Remove rect2, add a somewhat strange and odd shape(Vector!) as rect1 and replace AS:
 
stage.addEventListener(Event.ENTER_FRAME, hitTest); 
                      function hitTest(event:Event)
                      {
                      	if(rect1.hitTestPoint(mouseX,mouseY,true))
                      	{
                      		txt_hit.text = "HIT!";
                      	}
                      	else
                      	{
                      		txt_hit.text = "";
                      	}
                      }

Open in new window

    Now if you test the movie even if your mouse is way inside the bounding box if it's not over a painted area, it doesn't trigger a hit. But the instant you move it on a painted area, you get a "HIT!".
hitTestPoint UFO     Just a quick hint - The first two parameters are the x and y positions of the point we're testing. The last parameter you pass on to hitTestPoint is the shapeFlag. It tells the function whether to check against the actual pixels of the object (true) or the bounding box (false). Actual quote from the help of flash(you should check it out often, it saves a lot of web searching). Anyway it's default is false, so if you want it to behave the same way as hitTestObject but with a point, you can just omit the shapeFlag.
     Another thing to be careful with - make sure all of your coordinates are in global format. This might seem obvious to some but when you are dug up in 1000's of lines of code this is one of the very-likely-to-happen mistakes. It's so important to understand this, I'll do another example. Draw something small convert it symbol, instance name dot, convert to symbol once again, instance name it mouse. Replace the code:
 
stage.addEventListener(Event.ENTER_FRAME, hitTest);
                      stage.addEventListener(MouseEvent.MOUSE_MOVE, moveMouse); 
                      function moveMouse(event:MouseEvent)
                      {
                      	mouse.x = mouseX;
                      	mouse.y = mouseY;
                      } 
                      function hitTest(event:Event)
                      {
                      	if(rect1.hitTestPoint(mouse.dot.x,mouse.dot.y,true))
                      	{
                      		txt_hit.text = "HIT!";
                      	}
                      	else
                      	{
                      		txt_hit.text = "";
                      	}
                      }

Open in new window

    If you test the movie, you will see it doesn't work. Trace the coordinates you're passing to the hitTestPoint and you'll see the only possible way for it to work is to move the object to the top left corner. Definately not what we want. And that's because those coordinates are relative to the parent of the dot object, which is the mouse object. To fix this, convert them to global coordinates using localToGlobal function. Replace the if condition with this:
 
var cursor:Point = new Point(mouse.dot.x,mouse.dot.y);
                      	if(rect1.hitTestPoint(mouse.localToGlobal(cursor).x,mouse.localToGlobal(cursor).y,true))
                      	{
                      		txt_hit.text = "HIT!";
                      	}
                      	else
                      	{
                      		txt_hit.text = "";
                      	}

Open in new window

    If this is a bit advanced to you, what you do is create a point (still in relative space to mouse object) with the dot's x and y coordinates and then pass it to localToGlobal, which returns a point in absolute space, which x and y coordinates you use for the hitTestPoint.
 

3. Plain old hitTest has a new purpose

The bitmap bumber     Its strength - it doesn't ignore alpha channels and acts at pixel-level, its weakness - it's heavy, can only be used to bump bitmaps into other objects and ignores any transformation other than the translation. Also it is quite strange and hard to figure out AND the documentation on the Flash help is not too good.
     I'm sure this method has its uses, but the purpose of this Article is to find the ultimate way to hitTest symbols. Just so I don't totally ignore this method, I'm going to explain how to use it.
     First off, you start out with a Bitmap or Bitmap Data. That's your main object. To bump something into it, you have to get the coordinates of the upper left corner of the main object, specify an alpha threshold and give it an object to test against. The important thing when passing the Point objects for the hitTest is to make sure you use the same coordinate space for both bitmap's Points. You actually do not need this when hittesting with rectangles or points but if you hitTest against another bitmap, you can use globalToLocal and localToGlobal to make sure all your points are in the same space. The second parameter you pass is the alpha threshold for the main object. This is a hexadecimal (0x00 to 0xFF) number that specifies how opaque is actually considered opaque by flash. Any alpha value over the one specified is considered opaque and will return true if hit. The third parameter is  the object you are bumping into the main bitmap. It may be a Point,a Rectangle, a Bitmap or a BitmapData. Nothing else. The other two arguments are basically the same as the first two and you use them only if you're hittesting two bitmaps. They represent the upper left corner (in the same coordinate space) and the alpha threshold of the second object.
 

4. Ultimate hit testing

How to use the advantages and lose the disadvantages     If you've made it this far you either skipped all of the above and scrolled down, or read it and understood it. Either way, I'm assuming you totally understand both HitTestObject and HitTestPoint as these two make up the most adequate, error-proof hitTesting with pixel-precision. The coding is quite advanced but the methods behind it are quite simple. Here is the main concept :
ultimate hittesting1     First off, if hitTestObject returns true, we find the rectangular area in which both bounding boxes overlap. Then, we start hitTestPoint-ing points from this area. Depending on how much precision we want we can set the distance between points. Basically the idea is to find a point, which when hitTested with both symbols returns true. If such exists, that means that visually the two symbols are overlapping. In the next figure the orange point is one such point.
ultimate hittesting2     As you can tell this hitTesting method is quite processor-intense, especially if the collision rectangle is bigger as in the first example. If you're running at 30 fps and have this hittesting at frame rate with 1 pixel precision you might experience some lag on a slower PC. Now for the actionscript part. The first is how to use it, the second is the class itself.

 
//code usage: hitTesting.isHitting(first object,second object,first upper left corner(Point),second upper left corner(Point),precision) 
                      //simple code:
                      hitTesting.isHitting(mc_obj1,mc_obj2,new Point(mc_obj1.x,mc_obj1.y),new Point(mc_obj2.x,mc_obj2.y),5)

Open in new window

Note: The two points have to be the upper left corner of the objects, caused me a lot of trouble when I passed the objects x and y when they were with central registration. If you want to use Objects with their register points in the center you can use new Point(object.x-object.width/2,object.y-object.height/2). You can use any relation of x/width and y/height for different register points.
 
package
                      {
                      	import flash.geom.Point;
                      	import flash.geom.Rectangle;
                      	
                      	
                      	public class hitTesting
                      	{
                      		public static function isHitting(hitter:Object,hittee:Object,upper1:Point,upper2:Point,precision:Number):Boolean
                      		{
                      			if(hittee.hitTestObject(hitter))
                      			{
                      				return searchCommon(hitter,hittee,getRectangle(hitter,hittee,upper1,upper2),precision)
                      			}
                      			else
                      			{
                      				return false;
                      			}
                      		}
                      		
                      		static function getRectangle(object1:Object,object2:Object,upper1:Point,upper2:Point):Rectangle
                      		{
                      			var returnRectangle:Rectangle;
                      			
                      			if(upper1.x <= upper2.x)
                      			{
                      				if(upper2.y >= upper1.y)
                      				{
                      					returnRectangle = new Rectangle(upper2.x,upper2.y,(upper1.x object1.width)-upper2.x,(upper1.y object1.height)-upper2.y);
                      				}
                      				else
                      				{
                      					returnRectangle = new Rectangle(upper2.x,upper1.y,(upper1.x object1.width)-upper2.x,(upper2.y object2.height)-upper1.y);
                      				}
                      			}
                      			else
                      			{
                      				if(upper2.y >= upper1.y)
                      				{
                      					returnRectangle = new Rectangle(upper1.x,upper2.y,(upper2.x object2.width)-upper1.x,(upper1.y object1.height)-upper2.y);
                      				}
                      				else
                      				{
                      					returnRectangle = new Rectangle(upper1.x,upper1.y,(upper2.x object2.width)-upper1.x,(upper2.y object2.height)-upper1.y);
                      				}
                      			}
                      			return returnRectangle;
                      		}
                      		static function searchCommon(object1:Object,object2:Object,rect:Rectangle,precision:Number):Boolean 
                      		{
                      			var checker:Point;
                      			var returnValue:Boolean = false;
                      			for(var j = 0;j < Math.floor(rect.height/precision);j  )
                      			{
                      				if(!returnValue)
                      				{
                      					for(var i=0;i < Math.floor(rect.width/precision);i  )
                      					{
                      						if(!returnValue)
                      						{
                      							checker = new Point(rect.x i*precision,rect.y j*precision);
                      							if(object1.hitTestPoint(checker.x,checker.y,true) && object2.hitTestPoint(checker.x,checker.y,true))
                      							{
                      								returnValue=true;
                      							}
                      						}
                      						else
                      						{
                      							break;
                      						}
                      					}
                      				}
                      				else
                      				{
                      					break;
                      				}
                      			}
                      			return returnValue
                      		}
                      	}
                      }

Open in new window

    I hope I helped someone with all of this information.
3
32,146 Views

Comments (1)

Great article, but none of your formatting or embeds worked. I'm not sure how it got published already, but all the special tags should be using square brackets, not triangular brackets.

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.