-
-
Notifications
You must be signed in to change notification settings - Fork 261
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
KinematicCharacterController gets stuck on vertical walls #489
Comments
I'm having this issue as well. It seems to me that the expected behavior when hitting a wall would be to advance by the component of the desired movement vector that is perpendicular to the wall, but instead it stops because it can't advance exactly in the desired movement direction. Since the controller will slide a little bit, this seems to be the implemented behavior as well, but I don't know rapier well enough to understand where it's falling short. My attempt at a workaround may give us a clue, however. I've implemented a system that attempts to correct for this: fn correct_movement(
mut query: Query<
(
&mut Transform,
&KinematicCharacterControllerOutput,
),
With<Player>,
>,
) {
let Ok((mut transform, controller_output)) = query.get_single_mut()
else {
return;
};
for (normal, remaining) in controller_output
.collisions
.iter()
.filter_map(|c| c.toi.details.map(|d| (d.normal1, c.translation_remaining)))
{
let reject = remaining.reject_from(normal);
println!("{} reject {} -> {}", remaining, normal, reject);
transform.translation += reject;
}
} It takes the remaining movement vector after a collision along with the normal vector from the collision surface and computes the vector rejection of the remaining movement vector from the normal. However, the normal is often slightly off from where we would expect, which pushes the controller slightly closer to the wall (and in my attempted workaround, through the wall). For example, in this wall collision, it works as we would expect:
But in this one, it doesn't:
I would expect the normal to be I'm probably going to keep digging on this, but hopefully someone with a better understanding of rapier has some idea of what is happening. |
Good news, I have a fix though I haven't tested it on a variety of slopes. I dug in pretty deep and it looks like This is the offending branch in if climbing && angle_with_floor >= self.max_slope_climb_angle {
// Prevent horizontal movement from pushing through the slope.
vertical_translation
} else if !climbing && angle_with_floor <= self.min_slope_slide_angle { The documentation on climbing isn't clear, but my guess is that it's for obstacles too tall for auto-step where the desired behavior is that if the controller continuously pushes into it, it'll eventually rise and move across the surface by alternating between pure vertical movement in climbing mode (when in contact) and standard forward movement (when not in contact). Without this, climbing would result in rapid horizontal movement for steep slopes rather than a climbing effect - the comment also mentions clipping into the ground, but it's not clear how that would occur unless Anyways, the good news to all of this is the fix is simple, just flip the climb angle comparison which appears to have simply been reversed in this commit. So: if climbing && angle_with_floor <= self.max_slope_climb_angle { Offset collapseThe other issue I mentioned, the offset collapse, appears unrelated. I dug into this a bit as well. It has usage sprinkled throughout (toi.toi - (-toi.normal1.dot(&translation_dir)) * offset).max(0.0); I would have expected: (toi.toi - offset / (-toi.normal1.dot(&translation_dir))).max(0.0); That improved the issue, but didn't fix it. It also risks weird behavior at ~90 degree angles - which shouldn't trigger a collision anyways, I'm sure there are edge cases with existing penetration, etc. The full fix seems pretty involved and there's no clear way to do it performantly, at least not in the current setup. In particular the distance given to the shape cast is Next stepsGiven that this is all in Given the remaining issues and that this behavior has been broken for over a year without anyone seeming to notice, I'd also recommend going with a different character controller for the time being. I ran across bevy-tnua which seems well tested and can layer on top of Rapier. |
Thanks for getting to the bottom of this! I applied your fix as a patch in my own project and it completely fixed the issue. Hopefully your PR gets approved and rolled into the next official release. |
I've observed this behavior in a few projects I've put together so far. When moving a character controller towards a vertical wall at an angle, it will get eventually get stuck. It may slide a bit at first, but it always gets stuck until the direction of movement points away from the wall.
I've tried quite a few things so far including fixing the time step size and increasing
offset
and nothing has fixed or mitigated the problem. Note that increasingoffset
wasn't respected - as can be seen in the video below, there's some initial resistance with a large offset when approaching the wall, but it eventually acts as if the offset is 0.Setup details
I've tried this on Windows 10 & 11 using Bevy 0.13.0 with
bevy_rapier3d
0.25.0.Simple Example
I've put together a small example to capture this behavior in this repo. You can see the full source here.
Video examples
Getting stuck
https://youtu.be/lySzeeX68aI
Large offset not being respected
https://youtu.be/q46BHkLa_s4
The text was updated successfully, but these errors were encountered: