Skip to main content

Common recipes

This page provides code snippets that might be relevant for typical usages of the libraries in games.

Making a moving platform​

A moving platform must push the objects resting on it without being pushed back by them, which is exactly what a kinematic rigid-body does: it is moved by your own code, and the solver treats it as if it is immune to gravity and external forces.

A platform following a path is generally position-based: you give it the position it must reach at the end of the next timestep, and the engine derives the velocity needed to get there, so the objects on top of it are pushed with the right velocity:

fn setup_moving_platform(mut commands: Commands) {
commands.spawn((
RigidBody::KinematicPositionBased,
Transform::from_xyz(0.0, 1.0, 5.0),
Collider::cuboid(2.0, 0.1, 2.0),
MovingPlatform,
));
}

fn move_platform(time: Res<Time>, mut platforms: Query<&mut Transform, With<MovingPlatform>>) {
for mut transform in platforms.iter_mut() {
// Setting the next position of the platform: the physics engine derives the
// velocity needed to reach it at the end of the next timestep.
transform.translation.x = time.elapsed_secs().sin() * 2.0;
}
}

Alternately, if the platform's rigid-body was created with the RigidBody::KinematicVelocityBased type, then it needs to be controlled by setting its velocity (with its Velocity component) directly instead of a target position.

warning

Modifying the Transform of a position-based kinematic body sets its next kinematic position. However, modifying the Transform of a velocity-based kinematic body teleports it, so it goes through whatever is in the way instead of pushing it. Note as well that two kinematic bodies never collide with each other, and that a kinematic body pushing another kinematic body has no effect.

Making a one-way platform​

A one-way platform lets the character pass through it from below and holds it from above. This can be done by looking at the contacts before they reach the solver, with the contact modification hook, and by discarding those whose normal isn't the one the platform accepts. Rapier provides a helper function update_as_oneway_platform for that (ContactModificationContextView::update_as_oneway_platform):

#[derive(Component)]
struct OneWayPlatform;

#[derive(SystemParam)]
struct OneWayPlatformHooks<'w, 's> {
platforms: Query<'w, 's, (), With<OneWayPlatform>>,
}

impl BevyPhysicsHooks for OneWayPlatformHooks<'_, '_> {
fn modify_solver_contacts(&self, mut context: ContactModificationContextView) {
// Keep only the contacts pushing along the local +y axis of the platform; the other
// ones (the character arriving from below) are discarded. The normal is expressed in
// the frame of the first collider of the pair, hence the flip.
let allowed_local_n1 = if self.platforms.contains(context.collider1()) {
Vec3::Y
} else if self.platforms.contains(context.collider2()) {
-Vec3::Y
} else {
return;
};
context.update_as_oneway_platform(allowed_local_n1, 0.1);
}
}

The hooks are then given to the RapierPhysicsPlugin as its type parameter, and the platform's collider is flagged as asking for them:

.add_plugins((
DefaultPlugins,
// The hooks are given to the physics plugin.
RapierPhysicsPlugin::<OneWayPlatformHooks>::default(),
))
fn setup_one_way_platform(mut commands: Commands) {
// The hooks are only called for the colliders asking for them.
commands.spawn((
Transform::from_xyz(0.0, 1.0, 0.0),
Collider::cuboid(2.0, 0.1, 2.0),
ActiveHooks::MODIFY_SOLVER_CONTACTS,
OneWayPlatform,
));
}
info

The normal given to update_as_oneway_platform is expressed in the local frame of the first collider of the pair, therefore it must be flipped when the platform happens to be the second one. Don't forget to give the platform's collider the ActiveHooks::MODIFY_SOLVER_CONTACTS active hooks, otherwise the hook is never called for it.

Simulating a conveyor belt​

A conveyor belt is a surface that drags what rests on it without moving itself. This is modeled by an artificial surface velocity, which is set on the solver contacts using a contact modification hook.

#[derive(SystemParam)]
struct ConveyorBeltHooks<'w, 's> {
belts: Query<'w, 's, (), With<ConveyorBelt>>,
}

#[derive(Component)]
struct ConveyorBelt;

impl BevyPhysicsHooks for ConveyorBeltHooks<'_, '_> {
fn modify_solver_contacts(&self, mut context: ContactModificationContextView) {
if !self.belts.contains(context.collider1()) && !self.belts.contains(context.collider2())
{
return;
}
if let Some(contacts) = context.solver_contacts_mut() {
for contact in contacts.iter_mut() {
// The belt drags the objects along the world-space z axis at 12 m/s.
contact.tangent_velocity.z = 12.0;
}
}
}
}