Skip to content

CollisionHelper

Shmellyorc edited this page Sep 1, 2026 · 2 revisions

CollisionHelper provides a complete suite of collision detection and resolution functions for 2D games. It handles everything from basic overlap checks to advanced swept collisions that prevent tunneling.


Why CollisionHelper?

Collision is one of the most common and error-prone parts of game development. CollisionHelper centralizes all collision logic in one place, making it easy to:

  • Detect when shapes overlap
  • Resolve overlaps by pushing objects apart
  • Prevent tunneling with swept collisions
  • Raycast for line-of-sight and aiming
  • Slide along walls with move-and-slide

All methods use Rect2 and Vect2 - no separate Circle struct is needed.


Detection

Rect vs Rect

Check if two rectangles overlap.

if (CollisionHelper.RectRect(player, wall))
{
    // Player hit the wall
}

Circle vs Circle

Check if two circles overlap.

if (CollisionHelper.CircleCircle(playerPos, 16f, enemyPos, 20f))
{
    // Player hit the enemy
}

Rect vs Circle

Check if a rectangle and a circle overlap.

if (CollisionHelper.RectCircle(wall, playerPos, 16f))
{
    // Player circle hit the wall
}

Point vs Shape

Check if a point is inside a rectangle or circle.

if (CollisionHelper.PointRect(mousePos, buttonRect))
{
    // Mouse is over the button
}

if (CollisionHelper.PointCircle(mousePos, explosionCenter, 50f))
{
    // Mouse is within explosion radius
}

Line vs Shape

Check if a line segment intersects a rectangle or circle.

if (CollisionHelper.LineRect(start, end, wall))
{
    // Line of sight is blocked by wall
}

Distance Checks

Distance checks are useful for AI and proximity detection without doing full collision tests.

// Distance between two rectangles
float dist = CollisionHelper.DistanceRectRect(player, enemy);

// Distance between two circles
float dist = CollisionHelper.DistanceCircleCircle(playerPos, 16f, enemyPos, 20f);

// Distance between a rectangle and a circle
float dist = CollisionHelper.DistanceRectCircle(wall, playerPos, 16f);

// Check if enemy is within aggro range
if (CollisionHelper.DistanceCircleCircle(playerPos, 16f, enemyPos, 20f) < 100f)
{
    // Enemy starts chasing
}

Raycasting

Raycasts are useful for line-of-sight, aiming, shooting, and visibility checks.

// Cast a ray from player toward mouse
Vect2 direction = (mousePos - player.Center).Normalized();

if (CollisionHelper.RaycastRect(player.Center, direction, wall, out Vect2 hit, out float distance))
{
    // Wall is in the way at 'hit' position
    DrawLine(player.Center, hit, Color.Red);
}

Raycast Against Multiple Shapes

List<Rect2> walls = new();
List<(Vect2 center, float radius)> enemies = new();

if (CollisionHelper.RaycastAny(origin, direction, walls, enemies, 
    out Vect2 hit, out Vect2 normal, out float dist, out object hitObject))
{
    // Hit something! hitObject tells you what it was
    if (hitObject is Rect2) { /* Hit a wall */ }
    if (hitObject is Vect2) { /* Hit an enemy */ }
}

Swept Collision

Swept collision prevents tunneling - when fast-moving objects pass through thin walls in a single frame.

// Fast-moving bullet
Rect2 bullet = new Rect2(100, 100, 4, 4);
Vect2 bulletVelocity = new Vect2(1000, 0);

if (CollisionHelper.SweptRectRect(bullet, bulletVelocity, wall, 
    out float timeOfImpact, out Vect2 hitPoint, out Vect2 hitNormal))
{
    // Bullet will hit the wall at timeOfImpact (0-1)
    // Move bullet to hitPoint and bounce/stop
}

This works for:

  • SweptRectRect - Rectangle vs rectangle
  • SweptCircleRect - Circle vs rectangle
  • SweptCircleCircle - Circle vs circle

Collision Normals

Normals tell you which direction a collision came from. Useful for bouncing, sliding, and pushing.

// Get the normal between overlapping shapes
Vect2 normal = CollisionHelper.GetCollisionNormal(player, wall);
// normal is (-1, 0) for left, (1, 0) for right, (0, -1) for top, (0, 1) for bottom

// Reflect velocity off the surface
Vect2 reflected = CollisionHelper.Reflect(velocity, normal, 0.8f);
// 80% bounciness

Pushback (Collision Resolution)

Pushback resolves overlaps by pushing objects apart. This is essential for preventing objects from merging into each other.

if (CollisionHelper.RectRect(player, wall))
{
    Vect2 push = CollisionHelper.PushRectRect(player, wall);
    player.Position += push;  // Push player out of the wall
}

Pushback methods:

  • PushRectRect - Rect vs rect
  • PushCircleRect - Circle vs rect
  • PushCircleCircle - Circle vs circle

Move & Slide

Move and slide lets you move an object and have it automatically slide along walls instead of stopping dead.

// Move player with slide along walls
Vect2 velocity = new Vect2(5, 0);
List<Rect2> obstacles = new List<Rect2> { wall, floor };
player.Position = CollisionHelper.MoveAndSlideRect(player, velocity, obstacles);

Circle Version

Vect2 circlePos = new Vect2(150, 150);
float radius = 16f;
Vect2 velocity = new Vect2(5, 0);

List<Rect2> rectObstacles = new() { wall };
List<(Vect2 center, float radius)> circleObstacles = new()
{
    (new Vect2(250, 250), 20f)
};

circlePos = CollisionHelper.MoveAndSlideCircle(
    circlePos, radius, velocity, rectObstacles, circleObstacles);

Containment

Check if one shape fully contains another.

// Is the player fully inside the safe zone?
if (CollisionHelper.RectContainsRect(safeZone, player))
{
    // Player is safe!
}

// Is the circle fully inside the rectangle?
if (CollisionHelper.RectContainsCircle(rect, circleCenter, radius))
{
    // Circle is fully contained
}

Bounds Conversion

Convert a circle to its bounding box (AABB) for broadphase optimization - skip expensive checks when objects are far apart.

// Get the bounding box of a circle
Rect2 circleBounds = CollisionHelper.GetCircleBounds(circlePos, 25f);

// Quick broadphase check
if (CollisionHelper.RectRect(circleBounds, wall))
{
    // Now do the precise circle-rect check
    if (CollisionHelper.RectCircle(wall, circlePos, 25f))
    {
        // Actually hit the wall
    }
}

Closest Point

Find the closest point on a shape to a given point.

// Closest point on a rectangle
Vect2 closest = CollisionHelper.ClosestPointRect(mousePos, buttonRect);

// Closest point on a circle
Vect2 closest = CollisionHelper.ClosestPointCircle(mousePos, explosionCenter, 50f);

Reflection

Bounce a velocity off a surface with optional bounciness.

// Bounce a ball off a wall
Vect2 normal = CollisionHelper.GetCollisionNormal(ball, wall);
ballVelocity = CollisionHelper.Reflect(ballVelocity, normal, 0.9f); // 90% bounce

Full Example: Wall Collision

Here's a complete, runnable example showing a rectangle (player) moving and colliding with walls using MoveAndSlideRect. It defines input actions for WASD / Arrow keys and draws the results with PrimitiveBatcher.

using Void.Engine;
using Void.Engine.Graphics;
using Void.Engine.Helpers;
using Void.Engine.Inputs.InputActions;
using Void.Engine.Inputs.Keyboards;
using Void.Engine.Systems;

public class CollisionDemo : Game
{
    private Rect2 _player = new Rect2(100, 100, 32, 32);
    private List<Rect2> _walls = new()
    {
        new Rect2(200, 100, 64, 128),
        new Rect2(0, 400, 800, 32),  // Floor
    };
    private Vect2 _velocity = new Vect2(0, 0);
    private float _speed = 200f;

    public CollisionDemo(GameSettings settings) : base(settings) { }

    protected override void OnEnter()
    {
        // Define input actions
        InputAction.AddAction("MoveLeft").AddKey(KeyboardKey.A).AddKey(KeyboardKey.Left);
        InputAction.AddAction("MoveRight").AddKey(KeyboardKey.D).AddKey(KeyboardKey.Right);
        InputAction.AddAction("MoveUp").AddKey(KeyboardKey.W).AddKey(KeyboardKey.Up);
        InputAction.AddAction("MoveDown").AddKey(KeyboardKey.S).AddKey(KeyboardKey.Down);
    }

    protected override void OnUpdate(FrameTime frameTime)
    {
        float dt = frameTime.DeltaTime;

        var input = InputAction.GetState();

        // Gather input
        Vect2 move = Vect2.Zero;
        if (input.IsHeld("MoveLeft")) move.X -= 1;
        if (input.IsHeld("MoveRight")) move.X += 1;
        if (input.IsHeld("MoveUp")) move.Y -= 1;
        if (input.IsHeld("MoveDown")) move.Y += 1;

        if (move.LengthSquared() > 0)
            move = move.Normalized();

        _velocity = move * _speed;

        // Move and slide
        _player.Position = CollisionHelper.MoveAndSlideRect(_player, _velocity * dt, _walls);
    }

    protected override void OnDraw(FrameTime frameTime)
    {
        var batcher = new PrimitiveBatcher();
        batcher.Begin(SortMode.Immediate);

        // Draw walls
        foreach (var wall in _walls)
            batcher.DrawRect(wall, Color.Red);

        // Draw player
        batcher.DrawRect(_player, Color.Blue);

        batcher.End();
    }

    protected override void OnExit() { }
}

Summary

Category What It Does
Detection Check if shapes overlap (Rect, Circle, Point, Line)
Distance Measure distance between shapes
Raycast Cast rays against shapes
Swept Collision Prevent tunneling for fast-moving objects
Normals Get collision direction
Pushback Resolve overlaps
Move & Slide Slide along walls
Containment Check if one shape contains another
Bounds Conversion Get AABB for broadphase
Closest Point Find nearest point on a shape
Reflection Bounce velocities off surfaces

See Also


Back to Home

Clone this wiki locally