Recursive AI

Drive a 3D vehicle physics controller in C#

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

Attach a four-wheel suspension and drivetrain to a dynamic chassis, then supply throttle and steering each simulation tick.

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 space = new PhysicsSpace3D();
space.AddBox(new FVec3(40, 1, 60),
    new FVec3(0, -1, 0),
    new BodyOptions3D { Kind = BodyKind.Static });
var chassis = space.AddBox(
    new FVec3(2, Fixed.FromRatio(1, 2), 4),
    new FVec3(0, 1, 0),
    new BodyOptions3D { Density = 100 });
var car = space.AddVehicle(chassis);
for (var tick = 0; tick < 120; tick++)
{
    space.Drive(car, Fixed.One,
        steering: Fixed.FromRatio(1, 10));
    space.Step();
}
Console.WriteLine($"Chassis position: {space.Position(chassis)}");
Console.WriteLine($"Wheels: {space.WheelCount(car)}");
Console.WriteLine($"Steering angle: {space.Wheel(car, 0).SteerAngle}");
Console.WriteLine($"Wheel 0 grounded: {space.Wheel(car, 0).Grounded}");

The floor, chassis and wheel setup are generated in code; no model or other input file is required.

Unzip the example, then dotnet run.

Text
Chassis position: (0.279802709, 0.228901593, 4.208660767)
Wheels: 4
Steering angle: 0.050000001
Wheel 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

Drive with half throttle. Replace space.Drive(car, Fixed.One, steering: Fixed.FromRatio(1, 10)); in the program with:

C#
space.Drive(car, Fixed.FromRatio(1, 2),
    steering: Fixed.FromRatio(1, 10));

With that change, the program prints:

Text
Chassis position: (0.728411701, 0.22896192, 7.923016669)
Wheels: 4
Steering angle: 0.050000001
Wheel 0 grounded: True

Add 3D rigid body physics in C# · Move a 3D character controller in C# · Raycast a 3D physics world in C#


The short answer

Create a dynamic box chassis, attach the standard car with PhysicsSpace3D.AddVehicle, and set its controls through Drive before stepping physics. The public wrapper supplies a standard four-wheel car, forward along +Z. It is not an arbitrary vehicle-definition editor.

The download creates a floor and a chassis, drives for 120 fixed ticks and reads the first wheel’s ground-contact state. It runs as a console simulation: it does not load a car model or open a game window. The printed chassis position comes from the simulation, not an animation.

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

Start with a dynamic chassis sized roughly (2, 0.5, 4), as specified by the wrapper’s documented setup. The example uses density 100. Add a floor that is large enough to support the car’s motion, then call AddVehicle(chassis). Keep both references: the BodyRef identifies the chassis body and the VehicleRef identifies its suspension and drivetrain.

Drive takes throttle and steering from −1 to 1, plus brake and handbrake from 0 to 1. All four values are Fixed. The example sends full forward throttle and steering of 0.1, with both brakes left at zero. To feed real input into a game, convert actions to those control values in simulation code; don’t derive physics movement from a rendered mesh’s frame rate.

For a registered space, one world step advances the vehicle through the space’s post-entity system. Set controls before that step. For a standalone space, as in this console program, call space.Step() yourself. Don’t combine both stepping paths for a single simulation tick.

How can I read a car’s wheel state in C#?

WheelCount(car) gives the number of attached wheels. Wheel(car, index) uses a zero-based index and returns center, radius, grounded state, contact point, suspension length, spin angle and steering angle. Spin and steering angles are in radians. The program reads index zero only after advancing the simulation.

Use that wheel state to position and rotate wheel art. Read the chassis pose separately with space.Transform(chassis) or its position and rotation methods. A wheel’s Grounded flag reports suspension contact; it is not a promise that all wheels touch ground or that the car is stationary. Inspect each wheel when the distinction matters.

Keep physics as the movement authority. In a networked game, mirror chassis pose and any required wheel view state into synced entity properties; the hidden physics table is not automatically copied to subscribed replicas. Rendering those properties should not issue throttle or steering changes.

Removing the vehicle removes its suspension and drivetrain while retaining its chassis body. Removing or recreating bodies is a separate lifetime decision. Avoid holding stale references after destruction; the wrapper checks references and throws StaleBodyException when they no longer identify a live object.

All onegame recipes