Introduction
I've gotten a ton of questions lately on this site about collision detection, so it's about time I start expanding my physics tutorials with some more stuff along these lines.
In this tutorial, we'll take a look at how to use circles to do collision detection.
The Benefits of Circles
You can do collision detection with all sorts of shapes. In the previous tutorial, I introduced some basic concepts about how collision detection works, and we talked about Axis-Aligned Bounding Boxes (AABBs).
AABBs are pretty convenient and powerful, but they break down really quickly when you have objects that have to rotate. That's because a rotated AABB isn't axis-aligned anymore, which is what made it so powerful and convenient. You can stay with bounding boxes, even if you're using rotation, but it requires one of two things: recalculate the bounding box on each update (so the box's dimensions will change as you rotate) or by switching to non-aligned bounding boxes. Neither of these is unreasonable, but we can also just simply try a different shape.
Circles have two big things going for them. The first is that it solves the problem I just described. A rotated circle is still just a circle. There's no recalculation needed, and it doesn't require different math to make things work.
The second part is that the math is simple with circles.
Those two things make it a good choice for many situations, and a good building block in many of the rest.
So in this tutorial, we'll figure out how to make this work.
Building a Circle Type
In the previous tutorial, when we talked about axis-aligned bounding boxes, one of the advantages it had was that the Rectangle struct already exists. We didn't have to write any custom code.
Unfortunately, they didn't make a Circle struct (or class). I don't know why they left this out, but it's not hard for us to build our own, so that's our starting point.
Struct or Class?
The first thing to decide with our new circle type is whether it should be a class or a struct. These two look very similar but behave very differently, as I describe in that C# tutorial on structs, and in a lot more depth in my book.
Structs vs. classes are nuanced and can be a point of frustration, but to help you remember the most important differences, remember that structs copy themselves as you pass them around, classes don't.
So the question we have to ask ourselves before making any type is whether we want a struct or a class. Both have their merits and drawbacks, and you should feel free to choose whichever makes the most sense for your game.
In this tutorial, I'm going to go the struct route for the simple reason of XNA's Rectangle type is also a struct, and I want them to match.
The Properties of a Circle
So we're ready to do a little type design now. Let's consider what properties a circle has.
The way I see it, there are two real components here. First, there's the circle's center point, and second, there's the circle's radius. These two bits of data ought to tell us everything we need to know for our circle, so we can start defining our type from this.
This new Circle type of ours will probably be best defined in a separate file. To get this started, you can right-click on your project (or a folder within the project) in the Solution Explorer in Visual Studio and choose Add > Class… (yes, even if you're making a struct; there's no template for structs). In the dialog that comes up, give the type the name "Circle" (or name the file "Circle.cs") and press the Add button. This creates a new Circle.cs file with some basic template code filled in.
If you're going the struct route, you'll want to change it from saying class to struct, and considering that this is something we might want to reuse later on in other projects, it doesn't hurt to add the public modifier to it.
So our starting code might look like this:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MyGame { public struct Circle { } }
Depending on the version of Visual Studio you're using, your using directives might be slightly different, but that should be OK.
You'll also want to watch the namespace that this is in. In this sample code, it's in the MyGame namespace. In your game, you'll want to make sure it's in the same namespace as the rest of your code, or alternatively, you'll want to add a using directive at the top of other files that you want to use this in. Both options are fine right now.
Now we need to add in our properties and maybe a constructor or two to get the ball rolling.
I'm going to use the Vector2 type from XNA (MonoGame also has it) to store the center and a float for the radius. If you felt the need for more accuracy, you could go with double, but that shouldn't be needed that often, and since XNA uses float so heavily, it would be a pain in the butt to do all the conversions between types. So I'm sticking with float.
public struct Circle { public Vector2 Center { get; set; } public float Radius { get; set; } }
Note that now that we've brought in Vector2 from XNA, we need to add another using directive to the top of our file so that the C# compiler will know what type we're talking about when we say Vector2. So add this with the rest of your using directives at the top:
using Microsoft.Xna.Framework;
Now let's add a constructor. This constructor allows the user to create a circle by supplying the values for its center and radius:
public Circle(Vector2 center, float radius) { Center = center; Radius = radius; }
Also remember that by default, structs have a default constructor (which you can't override) that zeroes everything out. So you'll additionally be able to create a new circle with a center at <0, 0> and a radius of 0 with that default parameterless constructor.
That brings our whole Circle type to the following:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Xna.Framework; namespace MyGame { public struct Circle { public Vector2 Center { get; set; } public float Radius { get; set; } public Circle(Vector2 center, float radius) { Center = center; Radius = radius; } } }
Point-In-Circle Checking
With our Circle type built, we are now ready to start doing some basic collision detection. Let's start with some code to figure out if a point is inside a circle or not.
This will be useful for things like finding out if the player clicked in the circle or not (check if the mouse's location, represented by a Vector2 in the game world, is in the circle or not.
The math here isn't terribly complicated. The basic concept is to figure out how far the point is away from the center of the circle. If that distance is less than the circle's radius, the point is in the circle. If it's more than the radius, it's outside of the circle. (We'll assume that if it's equal to the radius, it's still in the circle as well.)
Remember, we can figure out the distance between two points by subtracting one from the other and computing the length of the resulting vector.
Our whole code for this method looks like either of the following (pick one or the other):
// The "wordy" version... public bool Contains(Vector2 point) { Vector2 relativePosition = point - Center; float distanceBetweenPoints = relativePosition.Length(); if(distanceBetweenPoints <= Radius) { return true; } else { return false; } }
// The concise version... public bool Contains(Vector2 point) { return ((point - Center).Length() <= Radius); }
Intersection of Two Circles
The last thing we want to try with this tutorial is to make some code that will check whether two circles overlap at all.
If all of your game objects are represented by circles, you can see if any of them overlap or have collided by checking each circle against every other circle.
Checking whether two circles intersect at all is pretty similar to checking whether a point is contained in a circle.
In short, we know that two circles must have some overlap if the two centers are closer together than the sum of their radii (radiuses). If the distance is more than the two radii combined, then there will be an empty gap between the two circles. If the distance is equal, the two circles are touching at one point (we'll call that an intersection). If the distance is less, then the two circles must be overlapping.
Like before, I'll give you a couple of choices for how to express this and let you choose between the two:
// The wordy version... public bool Intersects(Circle other) { Vector2 relativePosition = other.Center - this.Center; float distanceBetweenCenters = relativePosition.Length(); if(distanceBetweenCenters <= this.Radius + other.Radius) { return true; } else { return false; } }
// The concise version... public bool Intersects(Circle other) { return ((other.Center - Center).Length() < (other.Radius + Radius)); }
Our final code, with our Circle type ready for use in collision detection, looks like this:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Xna.Framework; namespace MyGame { public struct Circle { public Vector2 Center { get; set; } public float Radius { get; set; } public Circle(Vector2 center, float radius) { Center = center; Radius = radius; } public bool Contains(Vector2 point) { return ((point - Center).Length() <= Radius); } public bool Intersects(Circle other) { return ((other.Center - Center).Length() < (other.Radius + Radius)); } } }