bevy_rapier icon indicating copy to clipboard operation
bevy_rapier copied to clipboard

Inaccurate collisions when using a lot of `ColliderShape`s with the same height

Open daxpedda opened this issue 2 years ago • 2 comments

When programming a simple platformer, building a straight path out of many ColliderShapes and having a character walk on it doesn't work.

I am assuming that the issue is that the y-coordinate of every ColliderShape is not exactly the same, because of f32 inaccuracies. So the character walks along the straight path but bumps into a block that isn't exactly on the same y-coordinate as the one before it.

Example code
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;

const SCALE: f32 = 16.;
const PLATFORM_Y: f32 = -8.;
const TILES: isize = 100;
const CONTROLLER_JUMP_IMPULSE: f32 = 15.;

fn main() {
	let mut app = App::new();
	app.add_plugins(DefaultPlugins);
	app.add_plugin(RapierRenderPlugin);
	app.add_plugin(RapierPhysicsPlugin::<NoUserData>::default());
	app.add_startup_system(setup)
		.add_system(movement);

	app.run()
}

#[derive(Component)]
struct Controller;

fn setup(mut commands: Commands, mut rapier_config: ResMut<RapierConfiguration>) {
	commands
		.spawn()
		.insert_bundle(OrthographicCameraBundle::new_2d());

	rapier_config.scale = SCALE;

	commands
		.spawn()
		.insert_bundle(RigidBodyBundle {
			mass_properties: RigidBodyMassPropsFlags::ROTATION_LOCKED.into(),
			..Default::default()
		})
		.insert_bundle(SpriteBundle {
			sprite: Sprite {
				color: Color::rgb(1., 0., 0.),
				custom_size: Some(Vec2::new(1. * SCALE, 2. * SCALE)),
				..Default::default()
			},
			..Default::default()
		})
		.insert_bundle(ColliderBundle {
			shape: ColliderShape::cuboid(0.5, 1.).into(),
			..Default::default()
		})
		.insert(ColliderPositionSync::Discrete).insert(Controller);

	for x in -TILES / 2..TILES / 2 {
		commands
			.spawn()
			.insert_bundle(SpriteBundle {
				sprite: Sprite {
                  color: Color::rgb(0., 1., 0.),
                  custom_size: Some(Vec2::new(1. * SCALE, 1. * SCALE)),
                  ..Default::default()
              },
				transform: Transform::from_xyz(x as f32 * SCALE, PLATFORM_Y * SCALE, 0.),
				..Default::default()
			})
			.insert_bundle(ColliderBundle {
				shape: ColliderShape::cuboid(0.5, 0.5).into(),
				position: [x as f32, PLATFORM_Y].into(),
				..Default::default()
			});
	}
}

fn movement(
	keyboard_input: Res<Input<KeyCode>>,
	mut controller: Query<(
		&mut RigidBodyForcesComponent,
		&mut RigidBodyVelocityComponent,
		&RigidBodyMassPropsComponent,
		&mut ColliderMaterialComponent,
	), With<Controller>>,
) {
	let (mut rb_forces, mut rb_vel, rb_mprops, mut co_material) = controller.single_mut();

	if keyboard_input.just_pressed(KeyCode::Space) {
		rb_vel.apply_impulse(rb_mprops, Vec2::new(0., CONTROLLER_JUMP_IMPULSE).into());
	}

	let mut x_axis = 0.;

	if keyboard_input.pressed(KeyCode::A) || keyboard_input.pressed(KeyCode::Left) {
		x_axis -= 1.;
	} else if keyboard_input.pressed(KeyCode::D) || keyboard_input.pressed(KeyCode::Right) {
		x_axis += 1.;
	}

	if x_axis == 0. {
		co_material.friction = 2.;
	} else {
      co_material.friction = 0.;
		x_axis *= 25. * rb_mprops.mass();
		rb_forces.force.x = x_axis;
	}
}

Somebody else had this issue before, probably with a better explanation of what the issue is about: https://dev.to/sbelzile/making-games-in-rust-part-7-fixing-the-player-randomly-stuck-issue-3mj5.

daxpedda avatar Feb 27 '22 16:02 daxpedda

I've run into this as well, but meshing all of the tiles is probably the right solution anyway. Still annoying that it blocks quick n' dirty tests.

brandon-reinhart avatar Sep 01 '22 23:09 brandon-reinhart

I think that's a rapier issue, but I agree it would be convenient to be able to have a smoother experience for "quick n' dirty tests".

Any update on reproduction code is appreciated.

Vrixyz avatar May 24 '24 10:05 Vrixyz