Multiple physics contexts
Some projects may require multiple physics contexts. For example, a game with multiple levels may want to have a different physics context for each level. An AI-training project may want to simulate multiple physics contexts in parallel.
This page explains how to manage multiple physics contexts with the bevy_rapier plugin.
Components
A physics context is an entity with the components described in the
simulation structures page. By default, on PreStartup, bevy_rapier
spawns one physics context and marks it with the DefaultRapierContext component, so most users can forget about
multiple contexts support and use "low boilerplate" accessors such as ReadRapierContext.
If you don't want a default context to be spawned,
you can disable it using RapierContextInitialization:
RapierPhysicsPlugin::<NoUserData>::default()
.with_custom_initialization(RapierContextInitialization::NoAutomaticRapierContext),
If you need multiple physics contexts, you can spawn them manually. Spawning the RapierContextSimulation component is
enough since it requires the other components of the physics context (a RapierConfiguration, with a gravity
matching the length unit of the integration parameters, is added automatically if you don't provide one). Here is an
example of how to spawn a new physics context:
let mut context = commands.spawn(RapierContextSimulation::default());
Each physics context has its own integration parameters and its own configuration, so they can have a different gravity, be paused independently, or run in collision-only mode while the others don't:
// Each context has its own configuration, e.g., its own gravity.
context.insert(RapierConfiguration {
gravity: Vec2::new(0.0, -9.81 * (i + 1) as f32),
..RapierConfiguration::new(1.0)
});
If you disabled the default context, one of your own contexts can be given the DefaultRapierContext component. It is
then the one used by the system parameters like ReadRapierContext and WriteRapierContext, as well as by the
entities which don't specify their context:
context.insert(DefaultRapierContext);
Any entity managed by a physics context (colliders, joints, rigid-bodies, soft-bodies) has a RapierContextEntityLink
component attached to it, which refers to the entity of its physics context. If an entity is spawned without it, it is
added automatically and refers to the physics context of its closest ancestor with a RapierContextEntityLink, or to
the default context if none of its ancestors has one. Therefore the RapierContextEntityLink is what selects the
physics context simulating an entity when it is spawned:
commands.spawn((
Transform::from_xyz(0.0, 1.0 + id as f32 * 5.0, 0.0),
RigidBody::Dynamic,
Collider::cuboid(0.5, 0.5),
ColliderDebugColor(color),
// This rigid-body and its collider are simulated by the context `context_entity`.
RapierContextEntityLink(context_entity),
));
Modifying the RapierContextEntityLink of an entity moves it (and its children) to the other physics context:
/// Demonstrates how easy it is to move one entity to another context.
fn change_context(
query_context: Query<Entity, With<DefaultRapierContext>>,
mut query_links: Query<(Entity, &mut RapierContextEntityLink)>,
) {
let default_context = query_context.single().unwrap();
for (e, mut link) in query_links.iter_mut() {
if link.0 == default_context {
continue;
}
link.0 = default_context;
println!("changing context of {} for context {}", e, link.0);
}
}
The children of an entity with a RapierContextEntityLink are moved to the physics context of their parent when it
changes, and the ones spawned without a RapierContextEntityLink of their own join that context right away. So there
is no need to give them the link of their parent explicitly.
The ReadRapierContext and WriteRapierContext system parameters only give access to one physics context (the one
with the DefaultRapierContext component, unless another query filter is given as their type parameter). The
RapierContext structure can be used directly as the data of a Query to access all of them:
fn print_contexts(contexts: Query<(RapierContext, &ContextId)>) {
for (context, id) in contexts.iter() {
println!(
"Context {} has {} colliders.",
id.0,
context.colliders.colliders.len()
);
}
}
Resources
bevy_rapier uses a schedule to parameterize its execution, this means all physics contexts share the same
TimestepMode resource: all physics contexts execute at the same time, at the same rate. Similarly, the physics hooks
(given as the type parameter of the RapierPhysicsPlugin) and the DebugRenderContext of the debug-renderer are
shared by all the physics contexts.
The RapierDiagnosticsPlugin measures every physics context: its diagnostics are the sums of the measurements of all
the contexts. Each context can also be measured separately, as described in the
diagnostics section.