Jan
19
2010

Collision detection methods, hitTest and hitTestObject alternatives

Flash has never had a perfect solution to detect collisions. HitTest from ActionScript 2 has been replaced by two separate methods in ActionScript 3: hitTestObject and hitTestPoint. They are very similar to their old brother... they are still not perfect. Fortunately there are few alternative collision detection methods available, I'm going to test them here in this article.

Let's start from beginnig and see what the ActionScript 3 native hitTestObject offers.

 

hitTestObject

In below implementation I have two objects on the stage, polygon and circle (named polygon_mc and circle_mc). I'm going to use hitTestObject to detect collisions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package
{
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.display.Graphics;

public class Example1 extends MovieClip
{
var boundaries1:Sprite = new Sprite();
var boundaries2:Sprite = new Sprite();

public function Example1()
{
//draw 2 boxes to graphically present object's rectangular boundaries
boundaries1.graphics.beginFill(0x000000);
boundaries1.graphics.drawRect(0, 0, circle_mc.width, circle_mc.height);
boundaries1.graphics.endFill();
boundaries1.alpha = .4;

boundaries2.graphics.beginFill(0x000000);
boundaries2.graphics.drawRect(0, 0, polygon_mc.width, polygon_mc.height);
boundaries2.graphics.endFill();
boundaries2.alpha = .4;

addChild(boundaries1); addChild(boundaries2);

//on every played frame check if there is any collision detected
addEventListener(Event.ENTER_FRAME, checkIfHitTest);
}

public function checkIfHitTest(e:Event):void
{
circle_mc.x = mouseX - circle_mc.width / 2;
circle_mc.y = mouseY - circle_mc.height / 2;
if(circle_mc.hitTestObject(polygon_mc))
{
myText.text ="Yes";
showBoundaries(true);
}
else
{
myText.text ="No";
showBoundaries(false);
}
}

//function to show/hide boundary boxes if collision was detected
public function showBoundaries(isShow:Boolean):void
{
if(isShow)
{
boundaries1.visible = true;
boundaries2.visible = true;
boundaries1.x = circle_mc.x;
boundaries1.y = circle_mc.y;

boundaries2.x = polygon_mc.x;
boundaries2.y = polygon_mc.y;
}
else
{
boundaries1.visible = false;
boundaries2.visible = false;
}
}
}
}

And this is how it works:

As you can see Flash uses the bounding boxes of objects - it takes the highest and lowest possible points, the leftmost and rightmost points and draws a rectangle around them. Above example draws the boxes to illustrate, where Flash detects collision.

 

HitTestPoint

Now I changed the code a bit to detect collision between objects on the stage and a point. The biggest advantage of using this detection method is that it also checks the empty space withinn the objects boundaries. Just remember set last parameter shapeFlag as true: (shapeFlag:Boolean (default = false) — Whether to check against the actual pixels of the object (true) or the bounding box (false)).

So what's the problem with hitTestPoint? First, it tests point and object not two objects. Secondly it "doesn't see" boundaries only for vector objects, not for bitmaps. To demonstrate that, I'm going to modify our code a little to convert our vector objects to bitmaps using BitmapData (transparent bitmaps). I also added yellow box as a background to the stage to make sure bitmaps are transparent.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package
{
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.display.Graphics;
import flash.display.BitmapData;
import flash.display.Bitmap;

public class Example2 extends MovieClip
{
var boundaries1:Sprite = new Sprite();
var boundaries2:Sprite = new Sprite();

var circleBD:BitmapData;
var polygonBD:BitmapData;
var circleBMP:Bitmap;
var polygonBMP:Bitmap;

public function Example2()
{
//clone the vector object to bitmap object and place them 250px to the right
circleBD = new BitmapData(circle_mc.width, circle_mc.height, true, 0x00000000);
polygonBD = new BitmapData(polygon_mc.width, polygon_mc.height, true, 0x00000000);
circleBMP = new Bitmap(circleBD);
polygonBMP = new Bitmap(polygonBD);
circleBD.draw(circle_mc);
polygonBD.draw(polygon_mc);
circleBMP.x = circle_mc.x + 250; circleBMP.y = circle_mc.y;
polygonBMP.x = polygon_mc.x + 250; polygonBMP.y = polygon_mc.y;

addChild(circleBMP);
addChild(polygonBMP);

//draw 2 boxes to graphically present object's rectangular boundaries
boundaries1.graphics.beginFill(0x000000);
boundaries1.graphics.drawRect(0, 0, circle_mc.width, circle_mc.height);
boundaries1.graphics.endFill();
boundaries1.alpha = .4;

boundaries2.graphics.beginFill(0x000000);
boundaries2.graphics.drawRect(0, 0, polygon_mc.width, polygon_mc.height);
boundaries2.graphics.endFill();
boundaries2.alpha = .4;

addChild(boundaries1); addChild(boundaries2);

//on every played frame check if there is any collision detected
addEventListener(Event.ENTER_FRAME, checkIfHitTest);

//convert vector objects to bitmap

}

public function checkIfHitTest(e:Event):void
{
if(circle_mc.hitTestPoint(mouseX, mouseY, true))
{
myText.text ="Yes - vector circle";
showCircleBoundaries();
}
else if (polygon_mc.hitTestPoint(mouseX, mouseY, true))
{
myText.text ="Yes - vector polygon";
showPolygonBoundaries();
}
else if(circleBMP.hitTestPoint(mouseX, mouseY, true))
{
myText.text ="Yes - bitmap circle";
showCircleBoundaries(true);
}
else if (polygonBMP.hitTestPoint(mouseX, mouseY, true))
{
myText.text ="Yes - bitmap polygon";
showPolygonBoundaries(true);
}
else
{
myText.text ="No";
hideBoundaries();
}
}

//functions to show/hide boundary boxes if collision was detected
public function showCircleBoundaries(bmp:Boolean=false):void
{
boundaries1.visible = true;
if(!bmp)
{
boundaries1.x = circle_mc.x;
boundaries1.y = circle_mc.y;
}
else
{
boundaries1.x = circleBMP.x;
boundaries1.y = circleBMP.y;
}
}

public function showPolygonBoundaries(bmp:Boolean=false):void
{
boundaries2.visible = true;
if(!bmp)
{
boundaries2.x = polygon_mc.x;
boundaries2.y = polygon_mc.y;
}
else
{
boundaries2.x = polygonBMP.x;
boundaries2.y = polygonBMP.y;
}
}

public function hideBoundaries():void
{
if (boundaries1.visible)
boundaries1.visible = false;
if (boundaries2.visible)
boundaries2.visible = false;
}
}
}

First move mouse over the objects on the left to see that Flash "doesn't see" the boundary boxes anymore around the objects. Now, move the mouse over the pair of the objects on the right hand side to see that the same hitTestPoint method doesn't skip the boundary box around the objects anymore. Flash detects if there is any information within a point in the bounding box, no matter whether this is within the alpha channel or actual color.

Ok, Now when we know that methods given by Flash are not perfect,let's look for some alternatives?

Grant Skinner's Shape based collision detection

Very popular and well known to AS2 developers collision detection method, created by Grant Skinner is available at this address: http://www.gskinner.com/blog/archives/2005/08/flash_8_shape_b.html. This simple but effective script was ported to AS3 and is available here: http://labs.boulevart.be/index.php/2007/06/08/skinner-collision-detection-in-as3/

Let's see how is Grant Skinner algorithm working. I modified our last code by adding a line to instantiate the Collision class:

1
var myCollision:Collision = new Collision();

I also added new object to the stage, small square to follow the mouse pointer and gave it an instance name "pointer_mc". To detect collisions we call isColliding method of the Collision object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function checkIfHitTest(e:Event):void
{
point_mc.x = mouseX - point_mc.width / 2;
point_mc.y = mouseY - point_mc.height / 2;
if (myCollision.isColliding(circle_mc, point_mc))
myText.text ="Yes - vector circle";
 
else if (myCollision.isColliding(polygon_mc, point_mc))
myText.text ="Yes - vector polygon";
 
else if (myCollision.isColliding(circleBMP, point_mc))
myText.text ="Yes - bitmap circle";
 
else if (myCollision.isColliding(polygonBMP, point_mc))
myText.text ="Yes - bitmap polygon";
else
myText.text ="No";
}

And this is the result (again objects on the right are bitmaps with transparent boundary boxes):

Well, works great for either vector or bitmap objects. But it unfortunately has major disadvantage, it won't work if you do any transformations on the target objects (like rotating, resizing). I modified my first project with circle moving with the cursor by adding Collision method and I also rotated the polygon object...

If you are curious how this 90 lines long piece of code work, have a look on my article here: Lab: Autopsy of Skinner's collision detection in AS3

Corey O'Neil's Collision Detection Kit...

I think this one is currently the most popular... You can download it from here: http://code.google.com/p/collisiondetectionkit/downloads/list. It's very easy to implement, very efficient and works great. The documentation with simple examples is available here: http://www.coreyoneil.com/Flash/CDK/documentation/

I have modified my code from the first project again. After importing the class:

import com.coreyoneil.collision.CollisionList;

and defining variable:

var myCollisionList:CollisionList;

I added our two objects. First when instantiating the collision object (let's call it target object) and next one using addItem method. I could add actually more objects using addItem and check them all against the object passed when instantiating the class. By the way another class Collision group allows to check group against group...

myCollisionList = new CollisionList(circle_mc);
myCollisionList.addItem(polygon_mc)
Next in my checkIfHitTest method I added:
var collisions:Array = myCollisionList.checkCollisions();
 
if(collisions.length > 0)
{

If return array that is being held by collision array is non - zero, it means the collision was detected. And actually this array now holds references to all objects that collides with our target object. You can easly access it like below:

// tracing the name of the colliding object collisions[i].object1:
trace(collisions[i].object1.name);

And here is the our project compiled:

This class works very well and what is most important, it is resistant to any transformations on the MovieClips, they can be resized, rotated etc.

Comments 

 
0 #16 anon 2013-02-08 11:48
I would love to see performance tests on these. I did some of my own and Corey's kit had the worst performance. I guess you have to pay a price for better collision detection. If all you need to do is check if two objects are colliding then this is great, but the fps really slows down when checking an entire list of objects every frame.
Quote
 
 
0 #15 Jon 2013-01-24 21:03
Hi, need help. I want to implement either of these two collision detection methods (Grant Skinner's or Corey Oneill's). I have an array of Shapes (a bunch of lines) and another Shape (a ball) and I want to know whenever the ball is hitting any of the lines. The normal hitTestObject method won't work because the lines are diagonal. Which algorithm would work best and could someone explain what functions/methods to look at. Thanks much appreciated
Quote
 
 
-3 #14 Lolek 2013-01-19 15:53
Quote:
Hej, szczerze to bym wybrał jakiś silnik z fizyką, który by wszystko zrobił za mnie, Nape lub Box2d. Nie tylko detekcja kolizji juz jest zalatwiona ale sa dostepne edtory leveli gdzie mozna sobie rysowac poziom ziemii.


No w sumie tak. Tylko w box2d nie ogarniam jak zrobić animację np dla nóg postaci itp. Testowałem też WCK, ale jest podobnie, jak dodaję goToAndStop to przestaje reagować ;/
Quote
 
 
-4 #13 Wojtek 2013-01-09 20:54
Quoting Lolek:
To, który system kolizji polecałbyś do zrobienia gry. Zrobienie poprawnego systemu kolizji postaci z ziemią zawsze był dla mnie nieosiągalny. Postać najczęściej "utyka" w ziemi, jeśli są to kształty inne niż prostokąty :/ chciałbym móc ręcznie rysować tło gry i żeby postać dostosowywała swoje położenie do poziomu ziemi. Dzięki za replay ;)

Hej, szczerze to bym wybrał jakiś silnik z fizyką, który by wszystko zrobił za mnie, Nape lub Box2d. Nie tylko detekcja kolizji juz jest zalatwiona ale sa dostepne edtory leveli gdzie mozna sobie rysowac poziom ziemii.
Quote
 
 
-2 #12 Lolek 2013-01-04 11:34
To, który system kolizji polecałbyś do zrobienia gry. Zrobienie poprawnego systemu kolizji postaci z ziemią zawsze był dla mnie nieosiągalny. Postać najczęściej "utyka" w ziemi, jeśli są to kształty inne niż prostokąty :/ chciałbym móc ręcznie rysować tło gry i żeby postać dostosowywała swoje położenie do poziomu ziemi. Dzięki za replay ;)
Quote
 
 
+3 #11 Rayno 2012-12-31 22:50
Thanks, this post helped me start out with CDK.
Quote
 
 
-2 #10 edualc 2012-12-07 21:06
// tracing the name of the colliding object collisions.object1:
trace(collision s.object1.name);

i'm having problems with this.
because i have multiple gremlins added to the stage that may collide with the character, when collision occur i need to find out which gremlin it was.
but i can only show collisions[0].object1.name if i put [1] or [2] it doesnt exist.
[0] contains most of the time the name of the instance that has collided (which is good) but sometimes it contains the name of the character itself ! which is not very useful !
what am i doing wrong ?

help please
Quote
 
 
+1 #9 Lex 2012-08-17 14:34
I'm having problems tracing the name of the colliding object!

collisions.object1.name;

Can anybody help?
Quote
 
 
-3 #8 saravanan.R 2012-06-25 11:55
Really nice
Quote
 
 
-2 #7 Goon 2012-03-14 07:34
Thanks a bunch! Keep it up, man!
Quote
 

AS3 Tips

Follow me on Twitter