---
title: "Apply 2D forces and impulses in C#"
url: https://recursiveai.org/onegame/cookbook/apply-2d-forces-and-impulses/
description: "Use a force for sustained thrust or an impulse for a single kick."
version: "2.0.0"
---

# Apply 2D forces and impulses 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/apply-2d-forces-and-impulses/apply-2d-forces-and-impulses.zip)

Use a force for sustained thrust or an impulse for a single kick.

```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(
    new PhysicsOptions
    { Gravity = FVec2.Zero });
var body = space.AddBox(
    new FVec2(1, 1), FVec2.Zero);
space.ApplyForce(body, new FVec2(3, 0));
space.ApplyImpulse(body, new FVec2(3, 0));
space.Step();
var vx = space.Velocity(body).X.ToSingle();
Console.WriteLine($"{vx:F2}");
```

The horizontal velocity is `3.05`: `3.00` from the impulse plus `0.05` from one tick of force.

```text
3.05
```

Unzip [the example](https://recursiveai.org/onegame/cookbook/apply-2d-forces-and-impulses/apply-2d-forces-and-impulses.zip), then `dotnet run`.

## The full program

The full program uses separate unit boxes so their velocities can be compared. Generated commands start sustained thrust and apply one kick; `OnTick` supplies the force before each step.

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 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.Physics"
                    Version="2.0.0" />
  <PackageReference Include="RecursiveAI.OneGame.Core"
                    Version="2.0.0" />
</ItemGroup>
```

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

var registry = new EntityRegistry();
OneGame.Generated.Example.EntityTypes.Register(
    registry);
var world = new World(1, registry);
var space = PhysicsSpace2D.AddTo(world,
    new PhysicsOptions { Gravity = FVec2.Zero });
var motor = world.Add(new Motor
{
    Pushed = space.AddBox(
        new FVec2(1, 1), FVec2.Zero),
    Kicked = space.AddBox(
        new FVec2(1, 1), new FVec2(0, 3))
});
motor.Thrust(true);
motor.Kick();
for (var tick = 0; tick < 60; tick++)
{
    world.Step();
    if (tick == 0 || tick == 59)
    {
        var forceVx = space.Velocity(motor.Pushed)
            .X.ToSingle();
        var impulseVx = space.Velocity(motor.Kicked)
            .X.ToSingle();
        Console.WriteLine(
            $"Tick {tick + 1}: " +
            $"force vx={forceVx:F2}, " +
            $"impulse vx={impulseVx:F2}");
    }
}
motor.Thrust(false);
world.Step();
var nextVx = space.Velocity(motor.Pushed)
    .X.ToSingle();
Console.WriteLine($"Next tick force vx={nextVx:F2}");

[Entity]
public partial class Motor : Entity
{
    [Synced]
    public partial BodyRef Pushed { get; set; }
    [Synced]
    public partial BodyRef Kicked { get; set; }
    [Synced]
    public partial bool Thrusting { get; set; }

    [Command]
    public void Thrust(bool enabled) =>
        Thrusting = enabled;

    [Command]
    public void Kick() => PhysicsSpace2D.Of(World!)!
        .ApplyImpulse(Kicked, new FVec2(3, 0));

    protected override void OnTick()
    {
        if (Thrusting) PhysicsSpace2D.Of(World!)!
            .ApplyForce(Pushed, new FVec2(3, 0));
    }
}
```

`dotnet run` prints:

```text
Tick 1: force vx=0.05, impulse vx=3.00
Tick 60: force vx=3.00, impulse vx=3.00
Next tick force vx=3.00
```

Applying a force or impulse through a removed body reference throws `StaleBodyException`.

## Variations

### Set a velocity directly

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

var space = new PhysicsSpace2D(
    new PhysicsOptions { Gravity = FVec2.Zero });
var body = space.AddBox(new FVec2(1, 1), FVec2.Zero);
space.SetVelocity(body, new FVec2(5, 0));
space.Step();
Console.WriteLine(space.Velocity(body).X);
```

```text
5
```

## 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/)
- [Create a 2D polygon collider in C#](https://recursiveai.org/onegame/cookbook/create-2d-polygon-colliders/)

## The short answer

Call `ApplyForce` before every physics step that should receive thrust. Call `ApplyImpulse` once for a kick. Both methods take a `BodyRef` and an `FVec2`; the [physics methods](https://github.com/recursive-ai-oss/onegame/blob/60d5573da204de68cafd5896dc8d41b16c72ff9d/src/OneGame.Physics/PhysicsSpace2D.cs) describes the force as acting on the body’s center for the next step. An impulse also acts at the center.

Use dynamic bodies for this pattern. The [body options](https://recursiveai.org/onegame/docs/physics/) distinguish dynamic bodies, which move under gravity, forces and contacts, from static level geometry and game-driven kinematic bodies. A character controller is another game-driven case: move it by displacement rather than trying to implement walking through rigid-body thrust.

## What is the difference between force and impulse?

The example isolates the difference by turning gravity off, using equal unit boxes and keeping them apart so they do not collide. After the first tick, the continuously pushed box has horizontal velocity 0.05; the kicked box already has velocity 3.00. After the example’s 60 ticks, both read 3.00.

When the next tick receives no force, the pushed body keeps its existing horizontal velocity. Stopping thrust is not braking. Use damping, an opposing game-controlled force or a deliberate velocity change when the game needs the body to slow down. The variation shows the last option: `SetVelocity` replaces the velocity instead of adding another kick.

The physics library accepts fixed-point vectors, while the underlying solver handles their conversion. Keep the simulation inputs in `Fixed` and `FVec2`; the example’s `ToSingle()` calls are only for console formatting after the step. A view can use floating-point pixels without making those pixels the body’s simulation position.

## How do you get force from impulse?

For a constant force over a chosen time interval, compare the accumulated effect over that interval rather than sending the same vector through both APIs and expecting the same first-tick result. The full program uses the same vector for the repeated force and the one-shot impulse specifically to expose their different timing.

Do not multiply the force argument by the tick duration before calling `ApplyForce`: the force applies during the upcoming physics step. The game still needs to decide how many ticks receive it. Reapplying an impulse every tick is a different rule from applying a single kick, just as issuing thrust once is different from holding thrust on.

The generated `Thrust` command records whether thrust is enabled in synced state. `OnTick` reads that state and applies the force. The generated `Kick` command applies an impulse when the queued command executes. This keeps input decisions on the world’s command path. Commands execute before entity ticks, and the attached physics space steps afterward; a standalone space, such as the snippet at the top, instead needs an explicit `Step()` call. Never step an attached space again from a view or input callback.
