Skip to content

question: Create Bepu simulations at runtime, can we use this? #355

Description

@VaclavElias

Create Bepu simulations at runtime

This page describes how to register additional BepuSimulation instances from
code instead of declaring them in GameSettings.sdgamesettings, and how to
attach a CollidableComponent (body, static, character) to a specific
simulation by index using the IndexBasedSimulationSelector.

It is the runtime counterpart of the Bepu Configuration asset that the
Game Studio exposes. Both paths end up populating the same
BepuConfiguration.BepuSimulations list.

Note

The Bepu integration registers BepuConfiguration as an IService. The
first call to Services.GetService<BepuConfiguration>() triggers
BepuConfiguration.NewInstance(...), which also adds the
PhysicsGameSystem to the game system collection. From that moment on the
simulations declared in the configuration start ticking.

When to use this

You typically want to add simulations from code in the following cases:

  • You ship a game without a Game Studio asset for BepuConfiguration (for
    example a tool, a benchmark, or a project bootstrapped from
    Stride.CommunityToolkit).
  • You need more than one isolated simulation (for example: world physics,
    ragdoll/debris physics, UI/preview physics) and you want gameplay code to
    decide how many of them exist.
  • You want to recreate the configuration on the fly when loading a different
    level.

For shipping projects that already use the editor, prefer editing the
Bepu Configuration in GameSettings.sdgamesettings so designers can tweak
gravity, time scale, solver iterations, etc.

Add simulations from a startup script

The recommended entry point is a StartupScript that runs before any
CollidableComponent is attached. It is safe to call GetService here: if the
configuration was already loaded from GameSettings.sdgamesettings, you get
that instance back; otherwise a fresh one is created and registered.

using Stride.BepuPhysics;
using Stride.BepuPhysics.Definitions;
using Stride.Core.Mathematics;
using Stride.Engine;

namespace MyGame.Physics;

public class BepuSimulationsBootstrap : StartupScript
{
    public override void Start()
    {
        // Resolve (or create) the BepuConfiguration registered as IService.
        // NewInstance is invoked once; subsequent calls return the cached
        // instance held by the ServiceRegistry.
        var bepuConfig = Services.GetService<BepuConfiguration>()
                         ?? (BepuConfiguration)BepuConfiguration.NewInstance(Services);

        // Optional: if you want to take full control of the list, clear the
        // default simulation that NewInstance() inserts when nothing was
        // configured in the game settings.
        // bepuConfig.BepuSimulations.Clear();

        // Index 0 - main world simulation.
        bepuConfig.BepuSimulations.Add(new BepuSimulation
        {
            Enabled                 = true,
            ParallelUpdate          = true,
            PoseGravity             = new Vector3(0, -9.81f, 0),
            FixedTimeStepSeconds    = 1.0 / 60.0,
            TimeScale               = 1f,
            MaxStepPerFrame         = 3,
            SoftStartDuration       = TimeSpan.FromSeconds(1),
            SoftStartSubstepFactor  = 4,
        });

        // Index 1 - effects simulation (debris, ragdolls) running at half speed
        // and without gravity.
        bepuConfig.BepuSimulations.Add(new BepuSimulation
        {
            Enabled        = true,
            PoseGravity    = Vector3.Zero,
            TimeScale      = 0.5f,
            ParallelUpdate = true,
        });

        // Index 2 - preview/UI simulation, disabled by default; enable it on
        // demand when an inventory or character preview is opened.
        bepuConfig.BepuSimulations.Add(new BepuSimulation
        {
            Enabled     = false,
            PoseGravity = new Vector3(0, -2f, 0),
        });
    }
}

Important

The position of each simulation in BepuConfiguration.BepuSimulations is
what IndexBasedSimulationSelector targets. Do not reorder the list once
collidables have been attached, or you will silently move them to a
different simulation the next time they re-attach.

Add simulations during game bootstrapping

When the project does not load a game settings asset (for example
game.AutoLoadDefaultSettings = false in a benchmark or a tool), populate the
configuration in the Game.Run(start: ...) callback so that it exists before
the first scene loads:

using Stride.BepuPhysics;
using Stride.Engine;

using var game = new Game();
game.AutoLoadDefaultSettings = false;

game.Run(start: rootScene =>
{
    var bepuConfig = (BepuConfiguration)BepuConfiguration.NewInstance(game.Services);

    bepuConfig.BepuSimulations.Add(new BepuSimulation
    {
        PoseGravity = new System.Numerics.Vector3(0, -9.81f, 0).ToStride(),
    });

    // ... build scene, add collidables, etc.
});

Attach a collidable to a specific simulation

Every CollidableComponent (i.e. BodyComponent, StaticComponent,
CharacterComponent) exposes a SimulationSelector property. By default this
is SceneBasedSimulationSelector.Shared, which picks the simulation whose
AssociatedScene matches the entity's scene.

To bypass that lookup and target a known simulation by its index in
BepuConfiguration.BepuSimulations, assign an IndexBasedSimulationSelector:

using Stride.BepuPhysics;
using Stride.BepuPhysics.Definitions;
using Stride.BepuPhysics.Definitions.Colliders;
using Stride.Core.Mathematics;
using Stride.Engine;

public class SpawnDebrisOnEffectsSimulation : StartupScript
{
    public override void Start()
    {
        // Target simulation index 1 (the "effects" simulation defined above).
        var selector = new IndexBasedSimulationSelector { Index = 1 };

        // Dynamic body.
        var body = new BodyComponent
        {
            Collider           = new BoxCollider { Size = new Vector3(0.5f) },
            SimulationSelector = selector,
            CollisionLayer     = CollisionLayer.Layer0,
            Mass               = 1f,
            Gravity            = false, // simulation 1 has no gravity anyway
        };

        // Static collider on the same simulation.
        var ground = new StaticComponent
        {
            Collider           = new BoxCollider { Size = new Vector3(50, 1, 50) },
            SimulationSelector = selector,
        };

        var debris = new Entity("Debris") { body };
        debris.Transform.Position = new Vector3(0, 5, 0);
        Entity.Scene.Entities.Add(debris);

        var floor = new Entity("EffectsFloor") { ground };
        Entity.Scene.Entities.Add(floor);
    }
}

Move a collidable between simulations at runtime

Assigning a new SimulationSelector to an attached CollidableComponent
detaches it from its current simulation and reattaches it to the one returned
by the new selector. There is no need to remove and re-add the component:

collidable.SimulationSelector = new IndexBasedSimulationSelector { Index = 2 };

This is convenient for transferring objects between, for example, a paused
"preview" simulation and the live world simulation.

Things to keep in mind

  • IndexBasedSimulationSelector.Index must be valid the moment a collidable
    is attached. Add your simulations in a StartupScript (or in
    Game.Run's start callback) so the configuration is populated before any
    physics entity enters the scene.
  • Simulations created in code are not persisted in
    GameSettings.sdgamesettings. They live only for the duration of the
    process.
  • BepuSimulation owns a Bepu BufferPool and a ThreadDispatcher. Removing
    a simulation at runtime is not handled out of the box; design your code so
    the set of simulations is stable once gameplay starts.
  • SceneBasedSimulationSelector is still available if you prefer to associate
    simulations with scenes via BepuSimulation.AssociatedScene. Use whichever
    selector matches how you organize your project.

See also

  • BepuConfiguration - holds the list of simulations, registered as an
    IService and serialized in GameSettings.sdgamesettings.
  • BepuSimulation - the runtime simulation object that wraps a Bepu
    Simulation and exposes ray casts, sweeps and overlap queries.
  • ISimulationSelector / SceneBasedSimulationSelector /
    IndexBasedSimulationSelector - strategies used by CollidableComponent to
    resolve which simulation an entity belongs to.
  • CollidableComponent - base class for BodyComponent, StaticComponent
    and CharacterComponent.

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions