Beginner question: how to detect when touching ground
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:
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!
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.
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