Recursive AI

Add 3D rigid body physics in C#

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

This console example drops a sphere onto a static floor and reads its resting pose without opening a window.

Terminal
dotnet add package RecursiveAI.OneGame.Physics

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

C#
using OneGame.Core;
using OneGame.Physics;
 
var world = new World(1, new EntityRegistry());
var space = PhysicsSpace3D.AddTo(world);
space.AddBox(new FVec3(40, 1, 40),
    new FVec3(0, -1, 0),
    new BodyOptions3D { Kind = BodyKind.Static });
var ball = space.AddSphere(Fixed.One,
    new FVec3(0, 6, 0));
for (var tick = 0; tick < 300; tick++)
    world.Step();
Console.WriteLine($"Pose: {space.Transform(ball).Position}");
Console.WriteLine($"Vertical velocity: {space.Velocity(ball).Y}");

Unzip the example, then dotnet run.

Text
Pose: (0, 0.499931013, 0)
Vertical velocity: 0

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.

The full program

The download mirrors the transform into a synced entity property and runs 300 ticks so the sphere can settle on the floor.

OneGame.Generated.project.EntityTypes is generated from the assembly name project by the package’s entity source generator.

C#
using OneGame.Core;
using OneGame.Physics;
 
var registry = new EntityRegistry();
OneGame.Generated.project.EntityTypes.Register(registry);
var world = new World(1, registry);
var space = PhysicsSpace3D.AddTo(world);
space.AddBox(new FVec3(40, 1, 40),
    new FVec3(0, -1, 0),
    new BodyOptions3D { Kind = BodyKind.Static });
var entity = new FallingBall {
    Body = space.AddSphere(Fixed.One, new FVec3(0, 6, 0))
};
world.Add(entity);
for (var tick = 0; tick < 300; tick++)
    world.Step();
Console.WriteLine($"Mirrored pose: {entity.Pose.Position}");
Console.WriteLine($"Vertical velocity: {space.Velocity(entity.Body).Y}");
 
[Entity]
public partial class FallingBall : Entity
{
    [Synced] public partial BodyRef Body { get; set; }
    [Synced] public partial Transform3 Pose { get; private set; }
    protected override void OnTick() =>
        Pose = PhysicsSpace3D.Of(World!)!.Transform(Body);
}
Text
Mirrored pose: (0, 0.499931013, 0)
Vertical velocity: 0

Variations

Change the initial height. Replace new FVec3(0, 6, 0) in the program with:

C#
new FVec3(0, 10, 0)

With that change, the program prints:

Text
Mirrored pose: (0, 0.499931026, 0)
Vertical velocity: 0

Move a 3D character controller in C# · Drive a 3D vehicle physics controller in C# · Raycast a 3D physics world in C#


The short answer

Add RecursiveAI.OneGame.Physics, register PhysicsSpace3D on a World, add bodies and advance the world with world.Step(). Dynamic bodies are the default. The snippet at the top adds a sphere, steps until it rests on a static floor and reads its transform. There is no graphics-device requirement for this console simulation.

AddTo installs both the hidden physics table and the post-entity stepping system. Calling world.Step() therefore advances physics too. Don’t call space.Step() a second time for that same world tick. A standalone new PhysicsSpace3D() has no world system; you must step it yourself instead.

What is rigid body physics?

In this API, a rigid body has a shape, position, rotation and velocity. A dynamic body responds to gravity and contacts. A static body provides unmoving collision geometry, such as a floor. BodyOptions3D.Kind selects static, kinematic or dynamic behavior. See the physics guide for the supported 3D setup.

AddBox takes the box’s size and position; AddSphere takes a radius and position. These are simulation shapes, not rendered meshes. Adding a sphere won’t put a visible sphere on screen. Keep the collision representation and your rendered art aligned, but create them through their respective APIs.

The defaults use meters, Y-up gravity of −9.81 units per second squared, 60 ticks per second and four solver substeps. Those are the defaults in PhysicsOptions3D, not settings inferred from your render loop. Use identical simulation settings on every node in a networked game.

How do I use 3D rigid body physics in a C# game?

Create the space in your game’s RegisterSystems, so worlds created to load saves have the physics table too. Add bodies during Setup. Store the returned BodyRef on the owning entity rather than assuming a body’s position is its identity. Position, Rotation, Velocity and Transform read the physics state; Transform returns position, rotation and unit scale.

The physics space is the source of truth. Its table is hidden, so clients receiving entity replicas don’t automatically receive that table. Mirror the body’s position and rotation into synced entity properties in OnTick, then use those properties in the view. The published 3D example follows that pattern for its bodies and character.

Entity ticks run before the physics step. A pose read in OnTick therefore describes the previous completed step, not the step about to happen. This ordering matters when comparing a view’s mirrored pose against a fresh read directly from the space.

Physics state is saved, restored and hashed with the world. Keep simulation changes in sim code, not in a draw callback, and let the renderer read the mirrored state. Reading a transform does not move a body, create a mesh or register a view.

All onegame recipes