Making Games in Rust - Part 3 - Floors and Gravity
Tags: rust, bevy, rapier, tutorial
Published: 2021-12-13 16:23:41

Now that we have a [window](https://dev.to/sbelzile/rust-platformer-part-1-bevy-and-ecs-2pci), and a [character drawn on the screen](https://dev.to/sbelzile/making-games-in-rust-part-2-drawing-stuff-and-cameras-1jon), it's time to make our character fall. This article explains how to add gravity to pull our player towards the ground when it is in the air, and make it stop when it meets the ground.

## Theory

Our end goal is to have realistic movements. This is a problem more complex than it looks. We need a way to:

1. Detect that our player is in contact with a surface (our floor)
2. Make the player fall down when it does not touch the ground.

To facilitate our implementation (physics can be hard) we are going to bring in a physics library: [Rapier](https://rapier.rs/docs/). Rapier will allow us to easily simulate gravity, and will handle collision detection (detection of when our player is in the air or in contact with a surface) for us.

## Adding Gravity

Step 1: Add `bevy_rapier2d` to the `cargo.toml` file:

```toml
[dependencies]
bevy = "0.5"
bevy_rapier2d = "0.11.0"
```

Step 2: Register Rapier plugin with our application:

```rs
// main.rs
use bevy_rapier2d::prelude::*;

// ...
    App::build()
        // ...
        .add_plugin(RapierPhysicsPlugin::<NoUserData>::default())
```

Step 3: Add collider and rigid body components to the player:

```rs
fn spawn_player(mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>) {
    let rigid_body = RigidBodyBundle {
        mass_properties: RigidBodyMassPropsFlags::ROTATION_LOCKED.into(),
        activation: RigidBodyActivation::cannot_sleep(),
        ccd: RigidBodyCcd { ccd_enabled: true, ..Default::default() },
        ..Default::default()
    };
    let collider = ColliderBundle {
        shape: ColliderShape::cuboid(0.5, 0.5),
        flags: ColliderFlags {
            active_events: ActiveEvents::CONTACT_EVENTS,
            ..Default::default()
        },
        ..Default::default()
    };
    commands
        .spawn_bundle(SpriteBundle {
            material: materials.add(Color::rgb(0.7, 0.7, 0.7).into()),
            sprite: Sprite::new(Vec2::new(1.0, 1.0)),
            ..Default::default()
        })
        .insert_bundle(rigid_body)
        .insert_bundle(collider)
        .insert(RigidBodyPositionSync::Discrete)
        .insert(Player);
}
```

There is a lot going on here, so let's explain what is going on.

A [rigid body](https://rapier.rs/docs/user_guides/bevy_plugin/rigid_bodies) is responsible for dynamics and kinematics (motion and or motion resulting from forces) of our entities. In this case, we created a basic `RigidBodyBundle` with these properties: 

- `mass_properties`: the `ROTATION_LOCKED` property prevents Rapier from applying rotation forces on our player.
- `activation`: Rapier marks non moving objects as sleeping if they don't move enough. Rapier ignores "sleeping" objects when simulating physics. This prevent setting prevents unwanted behavior triggered by a sleeping object.
- `ccd`: for collision detection to be enabled on our player. More on that later.

A [collider](https://rapier.rs/docs/user_guides/bevy_plugin/colliders) is responsible for collision detection. The collider also defines the shape of our object as well as its mass (important if we are simulating physics). In this case, we created a basic `ColliderBundle` with these properties:

- `shape`: the shape of our player. Here, it's a square. Note that the x and y components of the collider's shape are **half length**. It took me a while to figure that out...
- `flags`: features to activate on the collider. The provided code activates `CONTACT_EVENTS`s, more on that later.

Our code adds the rigid body and the collider bundle to our player, along with a `RigidBodyPositionSync::Discrete` component. This component will synchronize Bevy's transform (position) with the position of our rigid body.

If you run `cargo run` in your terminal, you should see our player fall into the abyss of our window's bottom.

## Floor

Let's add a floor below our player:

```rs
// ...
.add_startup_stage("floor_setup", SystemStage::single(spawn_floor.system()))
// ...

fn spawn_floor(mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>) {
    let width = 10.;
    let height = 1.;
    let rigid_body = RigidBodyBundle {
        position: Vec2::new(0.0, -2.).into(),
        body_type: RigidBodyType::Static,
        ..Default::default()
    };
    let collider = ColliderBundle {
        shape: ColliderShape::cuboid(width / 2., height / 2.),
        ..Default::default()
    };
    commands
        .spawn_bundle(SpriteBundle {
            material: materials.add(Color::rgb(0.7, 0.7, 0.7).into()),
            sprite: Sprite::new(Vec2::new(width, height)),
            ..Default::default()
        })
        .insert_bundle(rigid_body)
        .insert_bundle(collider)
        .insert(RigidBodyPositionSync::Discrete);
}
```

If you run `cargo run`, you should see our player fall on the ground, and stay there.


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

This code is very similar to the other one. One difference is the `body_type: RigidBodyType::Static` property of the rigid body. This tells Rapier that our floor will never move.

All the code is available [here](https://github.com/belzile/platformer/tree/part-3).

Part 1:
{% link https://dev.to/sbelzile/rust-platformer-part-1-bevy-and-ecs-2pci %}
Part 2:
{% link https://dev.to/sbelzile/making-games-in-rust-part-2-drawing-stuff-and-cameras-1jon %}