Part Seven - Collision Detection
It's finally time! By the end of the installment, you will have something that actually plays a bit like a game (at least until you kill off all of the enemies on the level).
We're going to talk about collision detection, and we will be using a very simply bounding box method to determine when our bullets hit our enemies, or when our enemies hit us.
Since we are using bounding boxes, we need to take a look at our sprites for our enemies and think about how accurately a bounding box will represent them.
Here is a "close up" view of our enemy sprite (I've added a grid that represents individual pixels here as well):
As you can see, we have a bit of space at the top and bottom of our enemy sprite. If we use simply bounding box collision here, we are likely to get bullets that pass under or over the enemy ship, but trigger a hit anyway. Similarly, an ememy may approache the player and the player might collide with the enemie's bounding box before the enemy visually reaches the player. Consider the following image, however:
If we trim two pixels off of all sides of our bounding box, we get a pretty good representation of the area actually occupied by our enemy sprite.
In order to do this in code, lets open up our Enemy class and add a new property. It will be very similar to the BoundingBox property we already have:
public Rectangle CollisionBox
int X = iX - iBackgroundOffset;
if (X > iMapWidth)
X -= iMapWidth;
if (X < 0)
X += iMapWidth;
return new Rectangle(X+2, iY+2, 28, 28);
As you can see, the only change from the BoundingBox property is in the return line. Se add two pixels to the X and Y value of the rectangle, and subtract 4 pixels from the width and height. This will give us a rectangle representing the area inside the yellow box in the image above.
We don't need to do the same thing for our Bullet sprite, because the bullet we are using is a 16x1 pixel image, and it more or less fills the frame.
Why not Pixel-Perfect Collisions?
I've decided that for our purposes here, bounding box collisions will be "good enough". We could go a step further and add pixel perfect collision detection, but there are are number of tutorials out on the web on how to do this. You would want to do bounding box detection first anyway to eliminate a lot of work in your detection engine anyway.
What is a Collision?
So how does bounding box collision detection actually work? Lets look at another diagram:
Here we have two rectangles (remember that all of our objects in the game are represented by rectangles since we are using BoundingBox collision.) In the top set of rectangles, the red and yellow rectangles don't overlap. We can check for this by comparingthe positions of the componts of the rectangle.
Notice that I have put the coorindates of the corners instead of the widths above . Lets step through some logic and see how we will detect collisions.
First, lets consider if these rectangles overlap vertically. If the bottom of the Red is greater than the top of yellow (and it is, 4 is greater 3) then it is possible that the rectangles overlap. Next, we would check to see of the Top of Red is less than the bottom of yellow (and it isâ€¦ 2 < 5). If it was not, but our first case was true, the red rectangle would be completely above the yellow. From those two tests (which were both true) we know that vertically, these rectangles overlap. Then we just repeat the process for the horizontal values.
Is theright edge of Red greater than the left edge of yellow? Nope. 15 is less than 17, so these rectangles can't overlap horizontally. (The red one ends (right edge) before the yellow one begins (left edge)).
So lets move to the second set of rectangles. Here, we know visually that they do overlap, but we need to test it in code. The vertical conditions are the same as the first set, so I won't repeat those here except to say that by following the logic we know that vertically these rectangles overlap.
So is the right edge of Red greater than the left edge of yellow? It sure is. 15 is greater than 14. So now we have to check the left edge of red (10) to see if it is less than the right edge of yellow (19). It is, of course, so we know that horizontally these rectangles overlap.
If the rectangles overlap both horizontally and vertically, they actually do overlap. So now we need a method to test two rectangles and see if they overlap. Despite the length of the above explanation this is actually pretty easy to do. Add this helper function to the Game1.cs file:
protected bool Intersects(Rectangle rectA, Rectangle rectB)
// Returns True if rectA and rectB contain any overlapping points
return (rectA.Right > rectB.Left && rectA.Left < rectB.Right &&
rectA.Bottom > rectB.Top && rectA.Top < rectB.Bottom);
We are using the && operator here (as opposed to the & operator). They are both "and" operators, but the difference between the two is that && will "short circuit" and stop evaluating the expression as soon as it hits something that is false. This saves us a bit of processing time when we are checking a large number of objects for collisions.
We can pass Intersects any two rectangles and it will return "true" if they overlap, and "false" if not.
Lets add a couple more helpers to our Game1.cs file to handle the results of oru collisions (we'll expand on these later):
protected void DestroyEnemy(int iEnemy)
protected void RemoveBullet(int iBullet)
bullets[iBullet].IsActive = false;
Right now, all these functions do is deactivate the Enemy or Bullet specified. We will be adding to the DestroyEnemy function later to add explosions when an enemy is destroyed.
With these in place, lets add our primary function to test bullet-to-enemy collisions:
protected void CheckBulletHits()
// Check to see of any of the players bullets have
// impacted any of the enemies.
for (int i = 0; i < iMaxBullets; i++)
for (int x = 0; x < iTotalMaxEnemies; x++)
We simply loop through our bullet array and, for any active bullet, we check against every active enemy and see if Intersects() returns true for the bullets BoundingBox vs the enemy's CollisionBox rectangles. If so, we call both of your removal helpers.
Now, in your Update() method, add the following line right after the call to player.Update:
Run your game, and you should be able to shoot enemies out of the sky. Of course, they simply disappear without much of a satisfactory feel, but still the concept works.
What about enemies smashing into the player? This is similarly easy to check for, as we simply loop through all of the active enemies and check Intersects agains the player rectangle. Unfortunately, we need a lot of other things in place before we can actually do this, so we're going to hold off on implementing it until we get the structure of our game in place.
(Continued in Part 8...)