If you played with the code from part 6 of this tutorial, you probably noticed that our player often gets randomly stuck on the ground. You may also have gotten stuck in that way:
Solution 1
We could make our player smaller:
// player.rs
shape: ColliderShape::cuboid(0.45, 0.45),
// ...
sprite: Sprite::new(Vec2::new(0.9, 0.9)),
This fixes the hole issue:
But our player still randomly gets stuck on a flat surface.
Solution 2
What about a round corners on our character?
shape: ColliderShape::round_cuboid(0.35, 0.35, 0.1),
This way, our character is no longer stuck, but it looks like it bumps on the corner of the colliders...
Solution 3
We could modify our drawing strategy to only have flat colliders. Instead of one collider per square, we could layer colliders one on top of the other:
The code to do this adds some complexity though:
We will split world creation in 3 steps.
pub fn spawn_floor(mut commands: Commands, materials: Res<Materials>) {
let world = create_world(150);
add_sprites(&mut commands, materials, &world);
add_colliders(&world, &mut commands);
}
First step is to generate our height map, second step is to add our sprites, and finally we add our colliders.
- World creation:
fn create_world(width: usize) -> Vec<usize> {
let mut heights: Vec<usize> = Vec::with_capacity(width);
let mut height = 1;
(0..width).for_each(|_| {
heights.push(height);
height = get_next_height(height)
});
heights
}
Code is similar to what we previously had, we fill a vector with our heights.
- Adding the sprites:
fn add_sprites(commands: &mut Commands, materials: Res<Materials>, world: &Vec<usize>) {
world.iter().enumerate().for_each(|(x, height)| {
add_tile(commands, &materials, x as f32, *height);
});
}
fn add_tile(commands: &mut Commands, materials: &Res<Materials>, x: f32, height: usize) {
commands
.spawn_bundle(SpriteBundle {
material: materials.floor_material.clone(),
sprite: Sprite::new(Vec2::new(1., height as f32)),
transform: Transform::from_translation(Vec3::new(x, height as f32 / 2., 0.)),
..Default::default()
});
}
Also similar to what we previously had. Our entity no longer includes the rigid bodies and colliders components. The code iterates on our vector, and prints our world.
- Add the colliders:
fn add_colliders(world: &Vec<usize>, commands: &mut Commands) {
let max = match world.iter().max() {
Some(m) => m,
_ => panic!("add_colliders: World is empty")
};
(1..=*max).for_each(|floor_height| {
let mut start: Option<usize> = None;
world.iter().enumerate().for_each(|(index, height_at_index)| {
if *height_at_index >= floor_height && start.is_none() {
start = Some(index);
} else if *height_at_index < floor_height && start.is_some() {
add_collider(commands, floor_height, *start.get_or_insert(0), index);
start = None
}
});
if start.is_some() {
add_collider(commands, floor_height, *start.get_or_insert(0), world.len());
}
})
}
fn add_collider(commands: &mut Commands, height: usize, from: usize, to: usize) {
let width = to - from;
let half_width = width as f32 / 2.;
let rigid_body = RigidBodyBundle {
position: Vec2::new(from as f32 + half_width - 0.5, height as f32 - 0.5).into(),
body_type: RigidBodyType::Static,
..Default::default()
};
let collider = ColliderBundle {
shape: ColliderShape::cuboid(half_width, 0.5),
..Default::default()
};
commands
.spawn_bundle(rigid_body)
.insert_bundle(collider)
.insert(RigidBodyPositionSync::Discrete);
}
This code renders our world layer by layer. It adds a rigid body and a collider for every continuous length of floor on the layer.
The final code is available here.
Top comments (0)