DEV Community

Sébastien Belzile
Sébastien Belzile

Posted on • Updated on

Making Games in Rust - Part 7 - Fixing the Player Randomly Stuck Issue

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:

Image description

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)),
Enter fullscreen mode Exit fullscreen mode

This fixes the hole issue:

Image description

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),
Enter fullscreen mode Exit fullscreen mode

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:

Image description

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);
}
Enter fullscreen mode Exit fullscreen mode

First step is to generate our height map, second step is to add our sprites, and finally we add our colliders.

  1. 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
}
Enter fullscreen mode Exit fullscreen mode

Code is similar to what we previously had, we fill a vector with our heights.

  1. 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()
        });
}
Enter fullscreen mode Exit fullscreen mode

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.

  1. 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);
}
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (0)