---
title: "Filter 2D physics collision layers in C#"
url: https://recursiveai.org/onegame/cookbook/filter-physics-collision-layers/
description: "Choose which bodies collide and which layers a ray can see. Layers are numbered slots 0–31 that you assign."
version: "2.0.0"
---

# Filter 2D physics collision layers 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/filter-physics-collision-layers/filter-physics-collision-layers.zip)

Choose which bodies collide and which layers a ray can see. Layers are numbered slots 0–31 that you assign.

```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.SetLayerCollision(1, 2, false);
space.AddBox(new FVec2(1, 1), FVec2.Zero,
    new BodyOptions { Layer = 2 });
Console.WriteLine(space.Raycast(
    new FVec2(-3, 0), new FVec2(1, 0),
    10, out _, layers: 1u << 2));
```

Disables collisions between layers 1 and 2; the layer-2 ray still prints `True`.

```text
True
```

Unzip [the example](https://recursiveai.org/onegame/cookbook/filter-physics-collision-layers/filter-physics-collision-layers.zip), then `dotnet run`.

## The full program

The full program casts past a nearer layer-1 body to a layer-2 target. It then saves and restores the space and checks that two dynamic bodies on the disabled layer pair pass through one another. The restore matters because the collision matrix is saved with the space.

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

var space = new PhysicsSpace2D(new PhysicsOptions
    { Gravity = FVec2.Zero });
space.AddBox(new FVec2(1, 1), new FVec2(2, 0),
    new BodyOptions
    { Kind = BodyKind.Static, Layer = 1 });
var target = space.AddBox(
    new FVec2(1, 1), new FVec2(4, 0),
    new BodyOptions
    { Kind = BodyKind.Static, Layer = 2 });
space.SetLayerCollision(1, 2, false);
var found = space.Raycast(
    FVec2.Zero, new FVec2(1, 0),
    10, out var hit, layers: 1u << 2);
var selected = found && hit.Body == target;
Console.WriteLine($"Selected layer 2: {selected}");
var writer = new SyncWriter();
space.Write(writer);
var restored = new PhysicsSpace2D(new PhysicsOptions
    { Gravity = FVec2.Zero });
var reader = new SyncReader(writer.Written);
restored.Read(ref reader);
if (!reader.Ok)
    throw new InvalidOperationException(
        reader.Error.ToString());
// Check the disabled pair after loading.
var a = restored.AddBox(
    new FVec2(1, 1), new FVec2(0, 5),
    new BodyOptions
    { Layer = 1, Velocity = new FVec2(2, 0) });
var b = restored.AddBox(
    new FVec2(1, 1), new FVec2(2, 5),
    new BodyOptions
    { Layer = 2, Velocity = new FVec2(-2, 0) });
for (var tick = 0; tick < 60; tick++)
    restored.Step();
var passed = restored.Position(a).X >
    restored.Position(b).X;
Console.WriteLine(
    $"Restored pair passes through: {passed}");
```

`dotnet run` prints:

```text
Selected layer 2: True
Restored pair passes through: True
```

Layer indices outside the supported range are rejected; removed body references throw `StaleBodyException`.

## Variations

### Include two layers in one ray mask

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

var space = new PhysicsSpace2D();
space.AddBox(new FVec2(1, 1), new FVec2(3, 0),
    new BodyOptions { Layer = 5 });
uint mask = (1u << 2) | (1u << 5);
Console.WriteLine(space.Raycast(FVec2.Zero,
    new FVec2(1, 0), 10, out _, layers: mask));
```

```text
True
```

## Related recipes

- [2D platformer character controller in C#](https://recursiveai.org/onegame/cookbook/2d-character-controller/)
- [Apply 2D forces and impulses in C#](https://recursiveai.org/onegame/cookbook/apply-2d-forces-and-impulses/)
- [Create a 2D polygon collider in C#](https://recursiveai.org/onegame/cookbook/create-2d-polygon-colliders/)

## The short answer

Assign each body a `BodyOptions.Layer`, disable unwanted pairs with `SetLayerCollision`, and pass a separate bit mask to `Raycast`. The collision matrix controls body interactions; it is not a substitute for a query mask. Disabling a pair does not make either layer invisible to every ray.

OneGame’s [physics guide](https://recursiveai.org/onegame/docs/physics/) specifies 32 layers and says that the collision matrix is saved with the space. The numbers are slots you assign in your game. There are no automatic “player,” “enemy” or “terrain” categories. Choose named constants in your level code so the same number means the same thing everywhere that constructs the world.

## What is the difference between a collision layer and a collision mask?

A body’s layer is one index. A ray’s mask is a set of allowed indices, represented by bits in a `uint`. Layer 2 and the number 2 used as a mask are not equivalent: `1u << 2` selects layer 2, while the mask value 2 selects bit 1. This is a common source of rays that miss the intended body.

Use bitwise OR to include several layers, as in the variation above. Use `uint.MaxValue` to include all layers; that is also the ray method’s default. Validate any configurable layer index before shifting it into a mask. A bad shift expression can create the wrong mask before the physics API ever sees it.

The matrix answers a different question: should bodies on one layer collide with bodies on another? Keep that rule in simulation setup, alongside the body creation options. Apply the same setup on every node. Changing the matrix only in response to a rendering choice would make the physics state depend on presentation rather than the game’s rules.

## How do you make a raycast ignore a layer?

Pass a mask that leaves that layer’s bit unset. The full program makes the distinction concrete: a nearer static box is on layer 1, the farther target is on layer 2, and the ray includes only layer 2. Its hit body is therefore the target rather than the nearer box. Checking the returned body reference is more useful than checking only that something was hit.

For an “everything except this layer” query, invert the single-layer mask with bitwise NOT on the unsigned value. This changes only that query; it does not change how the excluded body collides with the rest of the world. A trigger is a separate property again: the 2D ray method skips triggers, regardless of the mask.

The download also exercises a restore, not just a fresh setup. It writes the space through `SyncWriter`, restores into another space with the same tick rate, checks the reader result, and steps two bodies through one another. That shows the disabled pair remains disabled after loading. In a game, attach the space in `RegisterSystems` so a world built to load a save has the required table and stepping system before the restore starts.
