Making Games in Rust - Part 7 - Fixing the Player Randomly Stuck Issue
Tags: bevy, rust, programming, tutorial
Published: 2021-12-29 13:28:07

If you played with the code from [part 6](https://dev.to/sbelzile/making-games-in-rust-part-6-generating-a-map-4aic) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2dpnqorwl9b571lx8hbd.png)

## Solution 1

We could make our player smaller:

```rs
// player.rs
shape: ColliderShape::cuboid(0.45, 0.45),
// ...
sprite: Sprite::new(Vec2::new(0.9, 0.9)),
```

This fixes the hole issue:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wtqvth4jbsf0t97x5ldj.png)

But our player still randomly gets stuck on a flat surface.

## Solution 2

What about a round corners on our character?

```rs
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:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/luo3vwd3iichw9u11eo9.png)

The code to do this adds some complexity though:

We will split world creation in 3 steps.

```rs
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.

1. World creation:

```rs
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.

2. Adding the sprites:

```rs
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.

3. Add the colliders:

```rs
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](https://github.com/belzile/platformer/tree/part-7).