---
title: "Move a 3D character controller in C#"
url: https://recursiveai.org/onegame/cookbook/3d-character-controller/
description: "Move a 3D character controller in C# with onegame: a runnable .NET 10 console example using the published physics package, with real output and a download."
version: "1.26.0"
---

# Move a 3D character controller 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/3d-character-controller/3d-character-controller.zip)

A character controller moves an upright capsule through collision geometry. You supply displacement; it does not add gravity or jumping for you.

```shell
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.

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

var space = new PhysicsSpace3D();
space.AddBox(new FVec3(40, 1, 40),
    new FVec3(0, -1, 0),
    new BodyOptions3D { Kind = BodyKind.Static });
var hero = space.AddCharacter(new FVec3(0, 3, 0));
CharacterMove3D move = default;
for (var tick = 0; tick < 120; tick++)
{
    move = space.Move(hero,
        new FVec3(space.StepSeconds,
            Fixed.FromRatio(-1, 10), 0));
    space.Step();
}
Console.WriteLine($"Position: {space.Position(hero)}");
Console.WriteLine($"Grounded: {move.Grounded}");
```

A removed character, vehicle or body reference causes `StaleBodyException` when used again.

Unzip [the example](https://recursiveai.org/onegame/cookbook/3d-character-controller/3d-character-controller.zip), then `dotnet run`.

```text
Position: (1.999999881, 0.399999991, 0)
Grounded: True
```

`Fixed` and `FVec3` are deterministic fixed-point scalar and 3D vector types: use `Fixed.FromInt(2)` or `new FVec3(2, 0, 0)` for integers, and convert a float with `Fixed.FromDouble(value)` outside sim code.

## Variations

**Move along Z instead of X.** Replace the displacement in `Move` with:

```csharp
new FVec3(0, Fixed.FromRatio(-1, 10),
    space.StepSeconds)
```

With that change, the program prints:

```text
Position: (0, 0.399999991, 1.999999881)
Grounded: True
```

## Related recipes

[Add 3D rigid body physics in C#](https://recursiveai.org/onegame/cookbook/3d-rigid-body-physics/) · [Drive a 3D vehicle physics controller in C#](https://recursiveai.org/onegame/cookbook/drive-3d-vehicle/) · [Raycast a 3D physics world in C#](https://recursiveai.org/onegame/cookbook/raycast-3d-world/)

---

## The short answer

Use `PhysicsSpace3D.AddCharacter` to create an upright capsule, then call `Move` with a displacement for one fixed tick. The result reports grounded, ceiling and side contact. Unlike a falling rigid body, the character does not acquire a vertical displacement just because the space has gravity configured.

The console example moves along X while applying a downward displacement each tick. The capsule starts at Y = 3, reaches the static floor and reports `Grounded: True`; the downward motion comes from the supplied displacement, not automatic gravity.

## How do I make a 3D character controller in C#?

Create a physics space, add collision geometry, and retain the `CharacterRef` returned by `AddCharacter`. The position you pass is the capsule’s center, not the bottom of its feet. `CharacterOptions3D` exposes radius, height, step offset, slope limit, push strength and layer. The slope limit is in radians.

Choose movement in simulation units. For speed measured in units per second, multiply by `space.StepSeconds` before passing the result to `Move`. Passing speed directly as displacement makes movement depend on tick count rather than elapsed simulation time. The example uses one unit per second along X and a fixed downward displacement each tick.

For a game-world integration, register the space with `PhysicsSpace3D.AddTo(world)` and issue movement from entity sim code before the world’s physics system steps. The download uses a standalone space to keep the example independent of input bindings, a game window and a source-generated entity registry.

## Should I use a character controller or a rigid body?

Use a character when the game owns its intended movement and needs grounded, side and ceiling results. Use a dynamic rigid body when gravity, impulses and contact response should control its movement.

The [3D physics guide](https://recursiveai.org/onegame/docs/physics/#3d-spaces) explicitly describes characters as displacement-driven. Keep vertical speed as synced entity state when implementing gravity and jumping. Update that speed according to your game’s rules and put the resulting vertical displacement into `Move` along with horizontal movement. Resetting or retaining vertical speed after a contact is also a game rule; the wrapper does not choose your jump behavior.

Read the movement result rather than inferring grounded state from a fixed height. A capsule can stand on geometry at different elevations, and side contact does not mean it is grounded. `Grounded` means contact with walkable ground; `HitCeiling` and `HitSide` identify other blocked directions.

The character reference and physics state are saved with the simulation. For a rendered or replicated character, mirror position and grounded state into synced entity properties. Views should consume those values rather than moving the capsule. This keeps the controller’s simulation independent of how often the screen redraws.
