idanarye / bevy-tnua

A floating character controller for Bevy
https://crates.io/crates/bevy-tnua
Apache License 2.0
180 stars 12 forks source link

Weird behavior when standing on an edge #38

Closed HugoPeters1024 closed 6 months ago

HugoPeters1024 commented 6 months ago

First of all, thanks for making this library! This is the nicest 3d character controller for Bevy that I've found.

When my cube character reaches the end of its plane it start almost refuse to move (making walking off the edge quite hard). Also jumping commands are ignored while in this state.

At first I thought it was because I only provide an xz vector as the desired_velocity to the TnuaBuiltinWalk struct. But even if I propagate the current Velocity::linvel (clamping the xz components to some MAX_SPEED), the issue persists.

Locking the rotation by adding the LockedAxes::ROTATION_LOCKED to the player seemingly also has no effect on the issue.

Did I miss another effect/setting, is there a known workaround?

Backend Rapier:

Screenshot:

image

Actually, the code is just a single file at this point, so I might as well include it here:

Full code ```rust use bevy::prelude::*; use bevy_rapier3d::prelude::*; use bevy_tnua::{ builtins::{TnuaBuiltinJump, TnuaBuiltinWalk}, controller::{TnuaController, TnuaControllerBundle, TnuaControllerPlugin}, }; use bevy_tnua_rapier3d::{TnuaRapier3dIOBundle, TnuaRapier3dPlugin}; #[derive(Component)] struct Player; #[derive(Component)] struct MainCamera; fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins); app.add_systems(Startup, setup); app.add_systems(Update, player_controls); app.add_systems(Update, camera_follow_player); app.add_plugins(RapierPhysicsPlugin::::default()); app.add_plugins(TnuaRapier3dPlugin); app.add_plugins(TnuaControllerPlugin); app.run(); } fn setup( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { commands.spawn(( Camera3dBundle { transform: Transform::from_xyz(0., 1.5, 6.).looking_at(Vec3::ZERO, Vec3::Y), ..default() }, MainCamera, )); // plane commands.spawn(( PbrBundle { mesh: meshes.add(Mesh::from(shape::Plane::from_size(1.0))), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), transform: Transform::from_scale(Vec3::new(10.0, 1.0, 10.0)), ..default() }, RigidBody::Fixed, Collider::cuboid(0.5, 0.01, 0.5), )); // cube commands.spawn(( PbrBundle { mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), transform: Transform::from_xyz(0.0, 3.5, 0.0), ..default() }, RigidBody::Dynamic, Collider::cuboid(0.5, 0.5, 0.5), TnuaRapier3dIOBundle::default(), TnuaControllerBundle::default(), Player, )); // light commands.spawn(PointLightBundle { point_light: PointLight { intensity: 1500.0, shadows_enabled: true, ..default() }, transform: Transform::from_xyz(4.0, 8.0, 4.0), ..default() }); } fn player_controls( keyboard: Res>, mut query: Query<(&Velocity, &Transform, &mut TnuaController)>, ) { for (v, player, mut controller) in query.iter_mut() { let mut direction = player.rotation * Vec3::Z * -1.0; let mut desired_velocity = v.linvel; if keyboard.pressed(KeyCode::W) { desired_velocity += direction; } if keyboard.pressed(KeyCode::S) { desired_velocity -= direction; } const MAXSPEED: f32 = 2.0; let xz_speed = Vec2::new(desired_velocity.x, desired_velocity.z).length(); if xz_speed > MAXSPEED { desired_velocity.x *= MAXSPEED / xz_speed; desired_velocity.z *= MAXSPEED / xz_speed; } if keyboard.pressed(KeyCode::A) { direction = Quat::from_rotation_y(0.1) * direction; } if keyboard.pressed(KeyCode::D) { direction = Quat::from_rotation_y(-0.1) * direction; } controller.basis(TnuaBuiltinWalk { desired_velocity, desired_forward: direction.normalize_or_zero(), float_height: 0.5, turning_angvel: 6.0, ..default() }); if keyboard.pressed(KeyCode::Space) { controller.action(TnuaBuiltinJump { height: 3.0, ..default() }); } } } fn camera_follow_player( players: Query<&Transform, With>, mut cameras: Query<&mut Transform, (With, Without)>, ) { let mut camera = cameras.single_mut(); let player = players.single(); let mut player_dir = player.rotation * Vec3::Z; player_dir.y += 0.3; let target = player.translation + player_dir * 10.0; const ALPHA: f32 = 0.12; camera.translation = (1.0 - ALPHA) * camera.translation + ALPHA * target; camera.look_at(player.translation, Vec3::Y); } ```
idanarye commented 6 months ago

Tnua, by default, casts a ray to determine the ground proximity. When you reach the end of a platform (the plane, in this case), this can be a problem because the ray can be outside the platform while part of the character is still above the platform.

To fix this, you need to cast a shape instead of a ray. In your case, the simplest way to do this is to add: this component to the cube:

TnuaRapier3dSensorShape(Collider::cuboid(0.5, 0.0, 0.5)),
HugoPeters1024 commented 6 months ago

Perfect, that's it! Thank you <3