bevy_rapier icon indicating copy to clipboard operation
bevy_rapier copied to clipboard

Beginner question: how to detect when touching ground

Open cameron1024 opened this issue 2 years ago • 2 comments

Hi, I'#m quite new to both bevy and rapier, so apologies in advance if this is already explained somewhere. I came across this, but I'm not sure how to apply that to my situation.

I have a flat plane, and a cube which represents the "player", and I'd like to keep track of whether the cube is touching the plane, but I'm having trouble figuring out how I'd go about doing that.

I'm creating the floor with:

commands.spawn((
    Floor,
    PbrBundle {
        mesh: meshes.add(shape::Plane::from_size(100.0).into()),
        material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
        ..default()
    },
    Collider::cuboid(50.0, 0.0, 50.0),
    Sleeping::disabled(),
    Ccd::enabled(),
));

and to create the player, I have:

commands.spawn((
    Player,
    PbrBundle {
        mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
        material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
        transform,
        ..default()
    },
    Collider::cuboid(0.5, 0.5, 0.5),
    RigidBody::Dynamic,
    Restitution::default(),
    GravityScale::default(),
    Sleeping::disabled(),
    AdditionalMassProperties::Mass(1.0),
    Ccd::enabled(),
    Damping::default(),
    OnGround(false),
    ExternalForce::default(),
));

Currently, I'm tracking the "on ground" status with this code:

#[derive(Debug, Component)]
pub struct OnGround(pub bool);

pub fn update_on_ground(mut query: Query<(&Transform, &mut OnGround), Changed<Transform>>) {
    for (transform, mut on_ground) in query.iter_mut() {
        let is_on_ground = transform.translation.y == 0.0;
        on_ground.as_mut().0 = is_on_ground;
    }
}

but this is very brittle:

  • many shapes (including the cube for the player) aren't at y=0 even when they clearly are touching the ground, due to the size of the collision box
  • I'd like to have the ground change height, and potentially have multiple floors on top of each other

Is there a way to ask rapier: "does this shape collide with an entity with the component Floor"? Or am I thinking about this in the wrong way?

Thanks in advance :grin:

cameron1024 avatar Apr 03 '23 02:04 cameron1024

Hi. I'm not sure if this is what you're looking for, but I was just looking for something similar myself.

I found the contact graph.

If you look at the second code example there, it might lead you down the right direction.

TL;DR: Use the RapierContext resource, with contact_pair(floor_e, player_e) where floor_e is your floor and player_e is your player. Also look at the contacts_with function if you want all contacts with the floor.

Hope I helped, I'm pretty new with rapier myself though. Let me know what you came up with!

flmng0 avatar Apr 14 '23 16:04 flmng0

Hi, @cameron1024.

I had another go at this. The code you're looking for I would say is the following:

#[derive(Debug, Component)]
pub struct OnGround(pub bool);

pub fn update_on_ground(
    floor: Query<Entity, With<Floor>>,
    mut query: Query<&mut OnGround, Changed<Transform>>,
    ctx: Res<RapierContext>,
) {
    // Consider extending this to use all floors?
    let floor_e = floor.get_single().expect("More than 1, or no, floor found!");

    // Clear the state from the previous frame.
    for mut on_ground in &mut query {
        on_ground.0 = false;
    }

    // Get all broad-phase contacts (things that could potentially be, but are not necessarily, touching).
    for contact in ctx.contacts_with(floor_e) {
        // Skip if this contact is not active
        if !contact.has_active_contact() {
            continue;
        }

        // Figure out which collider was the floor, and check the other one.
        let (a_e, b_e) = contact.collider1();

        let other_e = if floor_e == a_e {
            b_e
        } else {
            a_e
        };

        // If the other entity is in the query, then it's on the floor.
        if let Ok(mut on_ground) = query.get_mut(other_e) {
            on_ground.0 = true;
        }
    }
}

Note that I haven't been able to test this, I just wrote it up, hope it works.

flmng0 avatar Apr 21 '23 04:04 flmng0

You can get read the collision event for that: https://docs.rs/bevy_rapier2d/latest/bevy_rapier2d/pipeline/enum.CollisionEvent.html.

Another more advanced option is to shape cast down to detect the ground (https://rapier.rs/docs/user_guides/bevy_plugin/scene_queries).

Closing this, ping me if you need it open again

ThierryBerger avatar May 23 '24 15:05 ThierryBerger