My personal opinion is that game engines like Unity or Game Maker introduce lots of unnecessary overhead and constraints when it comes to 2D games. MonoGame provides a lot of the core stuff, like drawing, audio and input, so there’s no need to fully reinvent the wheel. There are also many handy APIs and specialized engines out there that can be implemented into a custom engine.

Since my game idea doesn’t require fancy simulation, I’m not going to use a physics engine, like Box2D or Farseer physics. Instead I’m just going to write a custom engine that does exactly what I need, and nothing else. 2D math is pretty easy, right?

Collision detection is as good a place to start as any. To keep things simple, there are only two kinds of colliders – rectangles and right triangles. The latter are only going to be used for static terrain, so I can leave the tricky triangle-triangle collisions out of the equation. There might be some use for a circle collider too, but I’m just going leave that out for now.

Rectangles are trivial and only need a couple of comparisons. Triangles on the other hand require a bit elementary school math. We need the side ratio and basic knowledge of the whereabouts of the right angle. Here’s my implementation:

/// <summary> /// Tests if a <see cref="ICollider"/> collides with a <see cref="RightTriangleCollider"/> /// </summary> /// <param name="rect">Rectangle</param> /// <param name="tri">Right triangle</param> /// <returns></returns> private bool TestTriangle(ICollider rect, RightTriangleCollider tri) { // Bounding rect if (!this.TestBounds(rect, tri)) return false; // Rectangle edges var x_left = rect.Bounds.Left; var x_right = rect.Bounds.Right; var y = (tri.CollisionBelowHypotenuse) ? rect.Bounds.Bottom : rect.Bounds.Top; // Hypotenuse var y0 = (tri.Ratio < 0) ? tri.Bounds.Bottom : tri.Bounds.Top; var y_limit = 0f; // Rectangle left edge if (x_left >= tri.Bounds.Left) { y_limit = y0 + tri.Ratio * x_left; if (tri.CollisionBelowHypotenuse) { if (y >= y_limit) return true; } else { if (y <= y_limit) return true; } } // Rectangle right edge if (x_right <= tri.Bounds.Right) { y_limit = y0 + tri.Ratio * x_right; if (tri.CollisionBelowHypotenuse) { if (y >= y_limit) return true; } else { if (y <= y_limit) return true; } } return false; }

ICollider interface simply states that the object has a bounding rectangle. RectangleCollider is the simplest implementation of the interface, but all other colliders implement it too. That way we can first do a very fast proximity test and then continue with the complex calculations only when a collision is likely to occur.

To wrap things up, I wrote a couple of unit tests to see if the algorithms actually work:

Boom! Headshot.