---
title: "Draw a 2D physics collider debug overlay in C#"
url: https://recursiveai.org/onegame/cookbook/draw-2d-physics-debug-overlay/
description: "Inspect collider outlines over a 2D game. F3 enables `Draw2D.DebugVisible` in development; the rendering example needs a GPU."
version: "2.0.0"
---

# Draw a 2D physics collider debug overlay in C#

**onegame** is a free, open-source game engine for .NET from Recursive AI. MIT or Apache-2.0, no editor required.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneGame.Physics) · [GitHub](https://github.com/recursive-ai-oss/onegame) · [Docs](https://recursiveai.org/onegame/docs/) · [Download example (.zip)](https://recursiveai.org/onegame/cookbook/draw-2d-physics-debug-overlay/draw-2d-physics-debug-overlay.zip)

Inspect collider outlines over a 2D game. F3 enables `Draw2D.DebugVisible` in development; the rendering example needs a GPU.

```shell
dotnet new console
dotnet add package RecursiveAI.OneGame.Physics
```

Requires [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0) (net10.0); .NET 9 and earlier aren’t supported.

`Fixed` and `FVec2` are onegame’s deterministic fixed-point number and 2D vector types.

```csharp
using OneGame.Core;
using OneGame.Physics;

var space = new PhysicsSpace2D();
space.AddBox(new FVec2(2, 1), FVec2.Zero);
var shapes = new PhysicsDebugShapes();
space.AddDebugShapes(shapes);
Console.WriteLine(shapes.Lines.Count);
```

In the full program’s `ColliderView`, this method draws the collected lines when F3 has enabled `DebugVisible`; `shapes`, `World` and `Pixels` are defined in the complete class below.

```csharp
[View]
public void Draw(Draw2D draw)
{
    LineCount = 0;
    if (!draw.DebugVisible ||
        PhysicsSpace2D.Of(World!) is not { } space)
        return;
    shapes.Clear();
    space.AddDebugShapes(shapes);
    foreach (var line in shapes.Lines)
    {
        draw.Line(
            Pixels(line.From), Pixels(line.To),
            Color.Rgba(line.Rgba), 1.5f, 9);
        LineCount++;
    }
}
```

Collects the box outline and prints `4`; the full program draws those lines through a view.

```text
4
```

Unzip [the example](https://recursiveai.org/onegame/cookbook/draw-2d-physics-debug-overlay/draw-2d-physics-debug-overlay.zip), then `dotnet run`.

## The full program

The full program renders headless frames with the overlay off and on. It draws via `Draw2D` only when `DebugVisible` is true, and converts physics coordinates into pixels. The zip includes Noto Sans under its OFL license so the host creates the 2D rendering stage; it needs no external input files.

The project references Core directly so its source generator supplies the entity, synced properties and command code. Add it alongside Physics:

```shell
dotnet add package RecursiveAI.OneGame.Core
```

The view and headless captures also use the host package:

```shell
dotnet add package RecursiveAI.OneGame.Host
```

The generated registry namespace uses the assembly name `Example`. Keep this project setting if you copy the full program into a differently named project:

```xml
<PropertyGroup>
  <AssemblyName>Example</AssemblyName>
</PropertyGroup>
<ItemGroup>
  <PackageReference Include="RecursiveAI.OneGame.Core"
                    Version="2.0.0" />
</ItemGroup>
```

```csharp
using System.Numerics;
using OneGame.Core;
using OneGame.Host;
using OneGame.Physics;
using OneGame.UI;

using var game = GameInstance.Create(
    new OverlayGame(),
    new GameOptions
    { Headless = true, Gpu = GpuMode.Required });
var stage = (Render2DStage)game.Stage;
stage.Draw.DebugVisible = false;
game.Capture(320, 180);
var view = (ColliderView)game.World.Entities[0];
Console.WriteLine(
    $"Overlay off: {view.LineCount} lines");
stage.Draw.DebugVisible = true;
game.Capture(320, 180);
Console.WriteLine(
    $"Overlay on: {view.LineCount} lines");

[Entity]
public partial class ColliderView : Entity, IView2D
{
    private readonly PhysicsDebugShapes shapes =
        new();
    public int LineCount { get; private set; }

    [View]
    public void Draw(Draw2D draw)
    {
        LineCount = 0;
        if (!draw.DebugVisible ||
            PhysicsSpace2D.Of(World!) is not { } space)
            return;
        shapes.Clear();
        space.AddDebugShapes(shapes);
        foreach (var line in shapes.Lines)
        {
            draw.Line(
                Pixels(line.From), Pixels(line.To),
                Color.Rgba(line.Rgba), 1.5f, 9);
            LineCount++;
        }
    }

    [View]
    private static Vector2 Pixels(Vector2 p) =>
        new(160 + p.X * 32, 90 - p.Y * 32);
}

public sealed class OverlayGame : GameDefinition
{
    public override string Name => "Collider overlay";
    public override void RegisterEntities(
        EntityRegistry registry) =>
        OneGame.Generated.Example.EntityTypes.Register(
    registry);
    public override void RegisterSystems(
        World world) => PhysicsSpace2D.AddTo(world);
    public override void Setup(World world)
    {
        PhysicsSpace2D.Of(world)!.AddBox(
            new FVec2(2, 1), FVec2.Zero,
            new BodyOptions
            { Kind = BodyKind.Static });
        world.Add(new ColliderView());
    }
    public override void DefineScreens(
        ScreenSet screens) =>
        screens.Add("main", _ => Ui.Column());
}
```

`dotnet run` prints:

```text
Overlay off: 0 lines
Overlay on: 4 lines
```

The full program uses `GpuMode.Required`, which fails if a graphics device cannot be created.

## Related recipes

- [2D platformer character controller in C#](https://recursiveai.org/onegame/cookbook/2d-character-controller/)
- [Filter 2D physics collision layers in C#](https://recursiveai.org/onegame/cookbook/filter-physics-collision-layers/)
- [Apply 2D forces and impulses in C#](https://recursiveai.org/onegame/cookbook/apply-2d-forces-and-impulses/)

## The short answer

Collect a space’s debug geometry with `AddDebugShapes`, then draw its `PhysicsDebugShapes.Lines` from a view. Guard the work with `Draw2D.DebugVisible`. The small snippet above collects a box’s four outline lines; the full program connects that data to the actual 2D drawing stage and renders with visibility both off and on.

The [physics guide](https://recursiveai.org/onegame/docs/physics/) describes the adapter as outlines of shapes plus contact points and normals, in world units. It draws geometry from the physics space, which is why it can reveal a mismatch between the collider and the visible artwork.

## How do you draw 2D physics colliders for debugging?

Keep a `PhysicsDebugShapes` instance on the view side. Clear it before collecting the current frame, call `space.AddDebugShapes(shapes)`, and send each returned segment to `draw.Line`. The full program’s `Draw` method is the working adapter. It converts both ends through the same pixel transform and uses the returned color for each line.

Physics uses Y-up coordinates. The example multiplies world coordinates by a pixel scale and negates Y, then offsets the result to the center of its capture. Use the same transform as your game artwork; otherwise the outlines can be correct physics geometry drawn in the wrong place. Thickness is a drawing choice too: this example uses a fixed pixel thickness rather than treating the returned world-unit width as pixels.

The [adapter source](https://github.com/recursive-ai-oss/onegame/blob/60d5573da204de68cafd5896dc8d41b16c72ff9d/src/OneGame.Physics/PhysicsDebug.cs) defines separate colors for static, kinematic, awake, sleeping and trigger geometry. It adds contact normal lines and contact-point marks. Circles are represented by line segments; do not mistake the segmented debug outline for a change to the underlying collision shape.

## Why is the physics debug overlay not showing?

First check `DebugVisible`. In the development host, F3 turns on the overlay; a scenario can use `overlay: on`. The guide says Release builds never set that flag. The console download sets it explicitly for two headless captures so its output is repeatable without keyboard input.

Next check whether this world has the space. Only the host world has physics; clients don’t, so clients cannot collect these outlines directly. The full view returns without drawing when `PhysicsSpace2D.Of(World!)` cannot find a space. To draw these outlines on a networked client, your game must send the geometry from the host.

Finally, check the rendering stage and its resources. `Draw2D.Line` must run during a rendered frame, not from `OnTick` or a random console callback. This example supplies a redistributable font and requests a GPU so the host uses its 2D stage. It calls `Capture` to render each frame; simply collecting outlines does not render a frame. The output distinguishes the zero lines submitted while hidden from the four box lines submitted while visible.
