From fd36e1410455bd6cf28f36238a159c37173092fb Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 8 Jun 2025 20:53:59 -0600 Subject: [PATCH 1/5] Fix circle collision math and a few typos. The circle collision explanation and code sample were incorrect. The Circle class is implemented correctly later in this chapter (see ./snippets/cirlce.cs#methods_intersects). --- .../12_collision_detection/index.md | 16 ++++++++-------- .../snippets/vector2_distance.cs | 19 ++++++++++--------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/articles/tutorials/building_2d_games/12_collision_detection/index.md b/articles/tutorials/building_2d_games/12_collision_detection/index.md index 2965aa32..7d1bf153 100644 --- a/articles/tutorials/building_2d_games/12_collision_detection/index.md +++ b/articles/tutorials/building_2d_games/12_collision_detection/index.md @@ -36,7 +36,7 @@ Shaped based collision detection checks if two shapes overlap. The most common Circle collision detection is computationally a simpler check than that rectangles. There are also no special considerations if the circles are rotated, which makes them easier to use. To determine if two circle shapes are overlapping, we only need to check if the square of the sum of the radii between the two circles is less than the squared distance between the two circles with the following formula: -Two find the distance between two circles, imagine drawing a line from the center of one circle to the center of the other. This length of this line is the distance, but we could also calculate it by first walking up or down and then walking left or right from the center of one circle to another, forming a right triangle. +To find the distance between two circles, imagine drawing a line from the center of one circle to the center of the other. This length of this line is the distance, but we could also calculate it by first walking up or down and then walking left or right from the center of one circle to another, forming a right triangle. | ![Figure 12-1: Showing the distance between the center of two circles forms a right triangle](./images/circle-distance-right-triangle.svg) | | :---------------------------------------------------------------------------------------------------------------------------------------: | @@ -48,22 +48,22 @@ In the *Figure 12-1* above - $b$ is the distance between the center of the two circles on the y-axis (vertical). - $c$ is the total distance between the center of the two circles. -Since this forms a right triangle, to calculate the squared distance, we can use Pythagorean's Theorem: +We can use Pythagorean's Theorem to calculate $c^2$ given $a^2$ and $b^2$: $$c^2 = a^2 + b^2$$ -Then we just check if the squared sum of the radii of the two circles is less than the squared distance: +To check for overlap of two circles, we compare whether the _squared sum of the radii_ of the two circles is greater than the _squared distance_: -$$(radius_{circle1} + radius_{circle2})^2 < c^2$$ +$$(radius_{circle1} + radius_{circle2})^2 > a^2 + b^2$$ -If it is less, then the circles are overlapping; otherwise, they are not. +It's easy to confuse the direction of the inequality sign. As a quick mental test, think of how the math works when the origin of two circles are at the same position, i.e., when the _squared distance_ is zero. -To calculate the squared distance between to points, MonoGame provides the [**Vector2.DistanceSquared**](xref:Microsoft.Xna.Framework.Vector2.DistanceSquared(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2)) method: +To calculate the squared distance between two points, MonoGame provides the [**Vector2.DistanceSquared**](xref:Microsoft.Xna.Framework.Vector2.DistanceSquared(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2)) method: [!code-csharp[](./snippets/vector2_distance.cs)] > [!TIP] -> MonoGame also provides a distance calculation method with [**Vector2.Distance**](xref:Microsoft.Xna.Framework.Vector2.Distance(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2)) which returns the distance by providing the square root of the distance squared. So why do not we use this instead? +> MonoGame also provides a distance calculation method with [**Vector2.Distance**](xref:Microsoft.Xna.Framework.Vector2.Distance(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2)) which returns the distance by providing the square root of the distance squared. So why not use this instead? > > Square root operations are more computationally complex for a CPU. So instead of getting the normal distance, which would require the square root operation, it is more efficient for the cpu to multiply the sum of the radii by itself to get the squared sum and use that for comparison instead. @@ -174,7 +174,7 @@ For example: #### Bounce Collision Response -For games that need objects to bonce off each other (like a the ball in a Pong game), we need to calculate how their velocity should change after the collision. MonoGame provides the [**Vector2.Reflect**](xref:Microsoft.Xna.Framework.Vector2.Reflect(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2)) method to handle this calculation for us. The method needs two pieces of information: +For games that need objects to bounce off each other (like a the ball in a Pong game), we need to calculate how their velocity should change after the collision. MonoGame provides the [**Vector2.Reflect**](xref:Microsoft.Xna.Framework.Vector2.Reflect(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2)) method to handle this calculation for us. The method needs two pieces of information: 1. The incoming vector (the direction something is moving). 2. The normal vector (the direction perpendicular to the surface). diff --git a/articles/tutorials/building_2d_games/12_collision_detection/snippets/vector2_distance.cs b/articles/tutorials/building_2d_games/12_collision_detection/snippets/vector2_distance.cs index 69f87b1b..020f8e32 100644 --- a/articles/tutorials/building_2d_games/12_collision_detection/snippets/vector2_distance.cs +++ b/articles/tutorials/building_2d_games/12_collision_detection/snippets/vector2_distance.cs @@ -1,22 +1,23 @@ Vector2 circle1Position = new Vector2(8, 10); Vector2 circle2Position = new Vector2(5, 6); -float circle1Radius = 5; -float circle2Radius = 5; +float circle1Radius = 3; +float circle2Radius = 3; +// c^2 = a^2 + b^2 // c^2 = (8 - 5)^2 + (10 - 6)^2 // c^2 = 3^2 + 4^2 // c^2 = 9 + 16 // c^2 = 25 -float distanceSquared = Vector2.DistanceSquared(circle1Position, circle2Position); +float distanceSquared = Vector2.DistanceSquared(circle1Position, circle2Position); -// r^2 = (5 + 5)^2 -// r^2 = (10)^2 -// r^2 = 100 -int radiiSquared = (circle1Radius + circle2Radius) * (circle1Radius + circle2Radius) +// r^2 = (3 + 3)^2 +// r^2 = (6)^2 +// r^2 = 36 +int radiiSquared = (circle1Radius + circle2Radius) * (circle1Radius + circle2Radius); -// They do not overlap since 100 is not less than 25 -if(radii < distanceSquared) +// The circles overlap because 36 is greater than 25 +if(radiiSquared > distanceSquared) { } \ No newline at end of file From c6a13f0ee5566a63ea7ecc099e897e05b66b4298 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 9 Jun 2025 17:37:20 -0600 Subject: [PATCH 2/5] Add back "Since this forms a right triangle," --- .../tutorials/building_2d_games/12_collision_detection/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/tutorials/building_2d_games/12_collision_detection/index.md b/articles/tutorials/building_2d_games/12_collision_detection/index.md index 7d1bf153..46b5f7c7 100644 --- a/articles/tutorials/building_2d_games/12_collision_detection/index.md +++ b/articles/tutorials/building_2d_games/12_collision_detection/index.md @@ -48,7 +48,7 @@ In the *Figure 12-1* above - $b$ is the distance between the center of the two circles on the y-axis (vertical). - $c$ is the total distance between the center of the two circles. -We can use Pythagorean's Theorem to calculate $c^2$ given $a^2$ and $b^2$: +Since this forms a right triangle, we can use Pythagorean's Theorem to calculate $c^2$ given $a^2$ and $b^2$: $$c^2 = a^2 + b^2$$ From 55bdb686be7aec8c1d6719c168ae2766f6fef01f Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 9 Jun 2025 17:38:21 -0600 Subject: [PATCH 3/5] Use * instead of _ --- .../building_2d_games/12_collision_detection/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/articles/tutorials/building_2d_games/12_collision_detection/index.md b/articles/tutorials/building_2d_games/12_collision_detection/index.md index 46b5f7c7..46d58b71 100644 --- a/articles/tutorials/building_2d_games/12_collision_detection/index.md +++ b/articles/tutorials/building_2d_games/12_collision_detection/index.md @@ -52,11 +52,11 @@ Since this forms a right triangle, we can use Pythagorean's Theorem to calculate $$c^2 = a^2 + b^2$$ -To check for overlap of two circles, we compare whether the _squared sum of the radii_ of the two circles is greater than the _squared distance_: +To check for overlap of two circles, we compare whether the *squared sum of the radii* of the two circles is greater than the *squared distance*: $$(radius_{circle1} + radius_{circle2})^2 > a^2 + b^2$$ -It's easy to confuse the direction of the inequality sign. As a quick mental test, think of how the math works when the origin of two circles are at the same position, i.e., when the _squared distance_ is zero. +It's easy to confuse the direction of the inequality sign. As a quick mental test, think of how the math works when the origin of two circles are at the same position, i.e., when the *squared distance* is zero. To calculate the squared distance between two points, MonoGame provides the [**Vector2.DistanceSquared**](xref:Microsoft.Xna.Framework.Vector2.DistanceSquared(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2)) method: From 11d68143c1ade390d797bd19d9fe22b02e807f56 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 9 Jun 2025 17:59:59 -0600 Subject: [PATCH 4/5] Switch circle radii back to 5 --- .../snippets/vector2_distance.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/articles/tutorials/building_2d_games/12_collision_detection/snippets/vector2_distance.cs b/articles/tutorials/building_2d_games/12_collision_detection/snippets/vector2_distance.cs index 020f8e32..411c5d7e 100644 --- a/articles/tutorials/building_2d_games/12_collision_detection/snippets/vector2_distance.cs +++ b/articles/tutorials/building_2d_games/12_collision_detection/snippets/vector2_distance.cs @@ -1,8 +1,8 @@ Vector2 circle1Position = new Vector2(8, 10); Vector2 circle2Position = new Vector2(5, 6); -float circle1Radius = 3; -float circle2Radius = 3; +float circle1Radius = 5; +float circle2Radius = 5; // c^2 = a^2 + b^2 // c^2 = (8 - 5)^2 + (10 - 6)^2 @@ -11,12 +11,12 @@ // c^2 = 25 float distanceSquared = Vector2.DistanceSquared(circle1Position, circle2Position); -// r^2 = (3 + 3)^2 -// r^2 = (6)^2 -// r^2 = 36 +// r^2 = (5 + 5)^2 +// r^2 = (10)^2 +// r^2 = 100 int radiiSquared = (circle1Radius + circle2Radius) * (circle1Radius + circle2Radius); -// The circles overlap because 36 is greater than 25 +// The circles overlap because 100 is greater than 25 if(radiiSquared > distanceSquared) { From 2da7edef4c6fd0d08bd1c75d7a4f990ff8bc9414 Mon Sep 17 00:00:00 2001 From: "Simon (Darkside) Jackson" Date: Thu, 12 Jun 2025 12:14:24 +0100 Subject: [PATCH 5/5] Corrections --- .../12_collision_detection/index.md | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/articles/tutorials/building_2d_games/12_collision_detection/index.md b/articles/tutorials/building_2d_games/12_collision_detection/index.md index 46d58b71..1468400c 100644 --- a/articles/tutorials/building_2d_games/12_collision_detection/index.md +++ b/articles/tutorials/building_2d_games/12_collision_detection/index.md @@ -16,7 +16,7 @@ In this chapter you will: We will first start by understanding the basics of collision detection and the different approaches that can be used. > [!NOTE] -> There is a lot to understand when it comes to collision detection and the many complex ways that two objects can be considered IN collsion or NEAR collision. It is critical to get an understanding of the basics before jumping into code. So buckle up, we have a story to tell before you can get back to the keyboard. +> There is a lot to understand when it comes to collision detection and the many complex ways that two objects can be considered IN collision or NEAR collision. It is critical to get an understanding of the basics before jumping into code. So buckle up, we have a story to tell before you can get back to the keyboard. > > Feel free to keep coming back to this chapter and refer to the content when you need to, with a fresh cup of coffee. @@ -34,15 +34,15 @@ Shaped based collision detection checks if two shapes overlap. The most common #### Circle Collision Detection -Circle collision detection is computationally a simpler check than that rectangles. There are also no special considerations if the circles are rotated, which makes them easier to use. To determine if two circle shapes are overlapping, we only need to check if the square of the sum of the radii between the two circles is less than the squared distance between the two circles with the following formula: +Circle collision detection is computationally a simpler check than rectangles. There are also no special considerations if the circles are rotated, which makes them easier to use. To determine if two circle shapes are overlapping, we only need to check if the square of the sum of the radii between the two circles is less than the squared distance between the two circles with the following formula: -To find the distance between two circles, imagine drawing a line from the center of one circle to the center of the other. This length of this line is the distance, but we could also calculate it by first walking up or down and then walking left or right from the center of one circle to another, forming a right triangle. +To find the distance between two circles, imagine drawing a line from the center of one circle to the center of the other. The length of this line is the distance, but we could also calculate it by first walking up or down and then walking left or right from the center of one circle to another, forming a right triangle. | ![Figure 12-1: Showing the distance between the center of two circles forms a right triangle](./images/circle-distance-right-triangle.svg) | | :---------------------------------------------------------------------------------------------------------------------------------------: | | **Figure 12-1: Showing the distance between the center of two circles forms a right triangle** | -In the *Figure 12-1* above +In *Figure 12-1* above - $a$ is the distance between the center of the two on the x-axis (horizontal). - $b$ is the distance between the center of the two circles on the y-axis (vertical). @@ -56,7 +56,7 @@ To check for overlap of two circles, we compare whether the *squared sum of the $$(radius_{circle1} + radius_{circle2})^2 > a^2 + b^2$$ -It's easy to confuse the direction of the inequality sign. As a quick mental test, think of how the math works when the origin of two circles are at the same position, i.e., when the *squared distance* is zero. +It is easy to confuse the direction of the inequality sign. As a quick mental test, think of how the math works when the origin of two circles are at the same position, i.e., when the *squared distance* is zero. To calculate the squared distance between two points, MonoGame provides the [**Vector2.DistanceSquared**](xref:Microsoft.Xna.Framework.Vector2.DistanceSquared(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2)) method: @@ -113,14 +113,14 @@ Implementing SAT is out-of-scope for this tutorial. If you are interested in fur #### Choosing a Collision Detection Method -When determining which collision detection method to use, you should start with the simplest one that meets the needs of your game. If distance checks work for your game mechanic, there's no need to implement more complex shape based detections. Similarly, if a circle can represent the bounding area of a game object, start with that before moving onto rectangles. +When determining which collision detection method to use, you should start with the simplest one that meets the needs of your game. If distance checks work for your game mechanic, there's no need to implement more complex shape based detections. Similarly, if a circle can represent the bounding area of a game object, start with that before moving on to rectangles. Some other points to consider are - Circles: - Better for round objects like balls and coins. - More accurate for rotating objects. - - Simpler check for overlap than rectangles. + - A simpler check for overlap than rectangles. - Rectangles: - Great for walls, platforms, and most game objects. - Easy to visualize and debug. @@ -137,7 +137,7 @@ A blocking collision response is the most basic response which just prevents the 1. Store the location of an object calculating the new location to move it to. 2. Check if it is overlapping an object at the new location: -- If it is overlapping, then set the position to the the position before it was moved. +- If it is overlapping, then set the position to the position before it was moved. - If it is not overlapping, set the position to the new calculated position. For example: @@ -174,9 +174,9 @@ For example: #### Bounce Collision Response -For games that need objects to bounce off each other (like a the ball in a Pong game), we need to calculate how their velocity should change after the collision. MonoGame provides the [**Vector2.Reflect**](xref:Microsoft.Xna.Framework.Vector2.Reflect(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2)) method to handle this calculation for us. The method needs two pieces of information: +For games that need objects to bounce off each other (like the ball in a Pong game), we need to calculate how their velocity should change after the collision. MonoGame provides the [**Vector2.Reflect**](xref:Microsoft.Xna.Framework.Vector2.Reflect(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2)) method to handle this calculation for us. The method needs two pieces of information: -1. The incoming vector (the direction something is moving). +1. The incoming vector (the direction that something is moving). 2. The normal vector (the direction perpendicular to the surface). | ![Figure 12-4: A diagram showing how an incoming vector reflects off of a surface base around the normal vector of the surface](./images/reflection-diagram.svg) | @@ -193,14 +193,14 @@ For example, if we had a ball moving around the screen and wanted it to bounce o [!code-csharp[](./snippets/bounce_example.cs)] > [!TIP] -> [**Vector2.UnitX**](xref:Microsoft.Xna.Framework.Vector2.UnitX) is $(1, 0)$ and [**Vector2.UnitY**](xref:Microsoft.Xna.Framework.Vector2.UnitY) is $(0, 1)$. We use these to get the screen edge normal since the edges of the screen are not at an angle. For more complex surfaces, you would need to calculate the appropriate normal vector based on the surface angle +> [**Vector2.UnitX**](xref:Microsoft.Xna.Framework.Vector2.UnitX) is $(1, 0)$ and [**Vector2.UnitY**](xref:Microsoft.Xna.Framework.Vector2.UnitY) is $(0, 1)$. We use these to get the screen edge normal since the edges of the screen are not at an angle. For more complex surfaces, you would need to calculate the appropriate normal vector based on the surface angle. ### Optimizing Collision Performance When checking for collisions between multiple objects, testing every object against every other object (often called brute force checking) becomes inefficient as your game grows. Brute force checking can be calculated as $(n * (n - 1)) / 2$ where $n$ is the total number of objects. For example, if you have 100 objects in your game, that's $(100 * 99) / 2 = 4950$ collision checks every frame. To improve performance, we can use a two-phase approach: 1. Broad Phase: A quick, simple check to rule out objects that definitely are not colliding. -2. Narrow Phase: A more precise check only performed on objects that passed the broad phase. +2. Narrow Phase: A more precise check that is only performed on objects that have passed the broad phase. For our simple game with just two objects, this optimization is not necessary. However, as you develop more complex games, implementing a broad-phase check can significantly improve performance. @@ -352,11 +352,11 @@ In this chapter, you accomplished the following: - Rectangles for walls and platforms. - Explored different types of collision responses: - Blocking to prevent objects from overlapping. - - Triggering to cause events when objects collide. + - Triggering events when objects collide. - Bouncing to reflect objects off surfaces. - Created reusable components: - Implemented a Circle struct for circle-based collision. - - Added methods to detect circle intersection. + - Added methods to detect circle intersections. - Applied collision concepts to our game: - Added screen boundary collision for the slime. - Implemented bouncing behavior for the bat. @@ -377,10 +377,10 @@ In the next chapter, we will explore using tilesets and tilemaps to create tile ::: question-answer For two rectangles A and B to collide: - 1. A's left edge must be less than B's right edge - 2. A's right edge must be greater than B's left edge - 3. A's top edge must be less than B's bottom edge - 4. A's bottom edge must be greater than B's top edge + 1. A's left edge must be less than B's right edge. + 2. A's right edge must be greater than B's left edge. + 3. A's top edge must be less than B's bottom edge. + 4. A's bottom edge must be greater than B's top edge. ::: @@ -395,8 +395,8 @@ In the next chapter, we will explore using tilesets and tilemaps to create tile ::: question-answer [**Vector2.Reflect**](xref:Microsoft.Xna.Framework.Vector2.Reflect(Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Vector2)) needs: - 1. The incoming vector (direction the object is moving) - 2. The normal vector (direction perpendicular to the surface being hit) + 1. The incoming vector (direction the object is moving). + 2. The normal vector (direction perpendicular to the surface being hit). ::: @@ -405,10 +405,10 @@ In the next chapter, we will explore using tilesets and tilemaps to create tile ::: question-answer Circle collision might be chosen because: - - It is more accurate for round objects - - It handles rotating objects better - - It is simpler for continuous collision detection - - It is natural for radius-based interactions + - It is more accurate for round objects. + - It handles rotating objects better. + - It is simpler for continuous collision detection. + - It is natural for radius-based interactions. :::