Collision Detection With Cocoa

Just about every game will need collision detection of some form. If you are using Cocoa and Objective-C for your game programming, then you’re in luck. There are a bunch of easy to use methods that can save you some serious time.

For Bullfrog, I’ve started with an all developer graphics based approach. All game objects are created procedurally. For the time being, I’m using no sprites and very little animation. Later, I’ll add these things, but for now, I’m concentrating on the game play.

The biggest challenge I’ve faced to this point was how to handle collision detection. The bullfrog attacks by flicking out its frog tongue to catch the bugs buzzing around his frog pond. The tongue is drawn using the Cocoa class NSBezierPath and its instance methods: moveToPoint:(NSPoint) and lineToPoint:(NSPoint).


NSBezierPath* tonguePath = [[NSBezierPath alloc] init];
[tonguePath setLineWidth: TONGUE_WIDTH];
[tonguePath moveToPoint: [self getMouthPoint]];
[tonguePath lineToPoint: [self getTonguePoint]];
[tonguePath closePath];
[tonguePath stroke];

This code is called when the game loop requests the frog class to draw itself. If the player is currently attacking, the frog tongue gets drawn.

One of the nice things about the NSBezierPath class is that it provides the extremely convenient method bounds. This gives you a bounding rectangle for the graphic we drew above.

Armed with bounds, we can use the fantastically useful Core Foundation function NSIsEmptyRect() combined with NSIntersectionRect to determine if two objects intersect or collide.

Since I’m using NSBezierPath to draw all my bugs, I can get all their bounding rectangles too. If I call NSIntersetionRect with the bounding rectangle for the frog’s tongue and each bug’s bounding rectangle, I can tell if there is a collision by checking the resulting NSRectangle with NSIsEmptyRect. This is illustrated in the following code.


-(void) playerAttackWithTongueRect: (NSRect)tongueRect
{
NSMutableArray* bugsToRemove = [[NSMutableArray alloc] init];

NSEnumerator* bugEnumerator = [_bugs objectEnumerator];
Bug* bug;

while (bug = [bugEnumerator nextObject])
{
NSRect collisionRect = NSIntersectionRect(tongueRect, [bug getBoundingRect]);

if ( !NSIsEmptyRect(collisionRect) )
{
[bugsToRemove addObject: bug];
[_player setBugsEaten: [_player bugsEaten] + 1];
[self updateScoreDisplay];
}
}

[_bugs removeObjectsInArray:bugsToRemove];

[bugsToRemove release];
}

There is one big catch to all this however. This may be because of the way I have implemented my frog’s tongue, but it seems that the NSBezierPath object returns a NIL instead of the correct NSRectangle when the frog is facing in one of the cardinal compass directions: 0, 90, 180, 270, or 360 degrees.

To get around this issue I needed to construct my own NSRectangle whenever my frog was facing one of these directions.


... [snip] ...

NSRect tongueRect = [tonguePath bounds];
if ( NSIsEmptyRect(tongueRect) )
{
if ( [self direction] == 0 || [self direction] == 360 )
{
tongueRect = NSMakeRect( mouthPoint.x, mouthPoint.y, [self attackRange], 3.0);
}
else if ( [self direction] == 180 )
{
tongueRect = NSMakeRect( mouthPoint.x - [self attackRange], mouthPoint.y, [self attackRange], 3.0); }
else if ( [self direction] == 90 )
{
tongueRect = NSMakeRect( mouthPoint.x, mouthPoint.y, 3.0, [self attackRange]); }
else if ( [self direction] == 270 )
{
tongueRect = NSMakeRect( mouthPoint.x, mouthPoint.y - [self attackRange], 3.0, [self attackRange]); }
}

... [snip] ...

Now the tongue will collide with the bug bounding rectangles in the cardinal compass directions.