Making Games in Rust - Part 4 - Jumps
Tags: rust, programming, bevy, tutorial
Published: 2021-12-17 14:42:23

[Part 3](https://dev.to/sbelzile/making-games-in-rust-part-3-floors-and-gravity-3lag) of this tutorial added gravity and collision detection to our game. This part will explain how to implement jumps.

## Jump Basis

1. We will start by defining a Jumper component. This will allow us to centralize all jumping logic.

```rs
struct Jumper {
    jump_impulse: f32,
}
```

2. In the `spawn_player` method, we will add this new component to our player.

```rs
.insert(Jumper { jump_impulse: 10. });
```

3. We will then add a player jumps system and register it with our application.

```rs
// In main, register a player movement system
.add_system(player_jumps.system())

// Define a player movement system
fn player_jumps(
    keyboard_input: Res<Input<KeyCode>>,
    mut players: Query<(&Jumper, &mut RigidBodyVelocity), With<Player>>
) {
    for (jumper, mut velocity) in players.iter_mut() {
        if keyboard_input.pressed(KeyCode::Up) {
            velocity.linvel = Vec2::new(0., jumper.jump_impulse).into();
        }
    }
}
```

The `keyboard_input: Res<Input<KeyCode>>` parameter of our method allows us to detect key pressed.

Our `mut players: Query<(&Jumper, &mut RigidBodyVelocity), With<Player>>` parameter returns all the entities with a `Jumper`, a `RigidBodyVelocity` and a `Player` component. The `With` tells Bevy that we don't need the `Player` component.

The code in the method sets the upward speed of our player to the jump impulse that is defined on the component.

If you run `cargo run`, and press the up key, you should see our player go up, and then come back down until it reaches the floor.

## Disable Multiple Jumps

With our current code, if you keep the up key pressed, the player will keep going up and will not stop until the is released. We need a way to disable jumps when the player is in the air.

One way to do this is to add a `is_jumping` boolean property to our `Jumper` component.

```rs
struct Jumper {
    jump_impulse: f32,
    is_jumping: bool
}
// ...
.insert(Jumper { jump_impulse: 10., is_jumping: false })
```

Then, we can allow jumps only when this boolean flag is false, and set this flag to true when the player is in the air.

```rs
fn player_jumps(
    keyboard_input: Res<Input<KeyCode>>,
    mut players: Query<(&mut Jumper, &mut RigidBodyVelocity), With<Player>>
) {
    for (mut jumper, mut velocity) in players.iter_mut() {
        if keyboard_input.pressed(KeyCode::Up) && !jumper.is_jumping {
            velocity.linvel = Vec2::new(0., jumper.jump_impulse).into();
            jumper.is_jumping = true
        }
    }
}
```

If you run the application, the player can no longer jump while it's in the air. The only issue we have is that it can only jump once...

We need a way reset the `is_jumping` flag when the player is not jumping.

## Reset the Flag

To do this kind of things, Rapier has an event system that triggers whenever a contact occurs between 2 colliders. We will leverage that to reset our flag.

Add a new `jump_reset` system and register it with our application:

```rs
// In main
.add_system(jump_reset.system())

// ...
pub fn jump_reset(
    mut query: Query<(Entity, &mut Jumper)>,
    mut contact_events: EventReader<ContactEvent>,
) {
    for contact_event in contact_events.iter() {
        for (entity, mut jumper) in query.iter_mut() {
            set_jumping_false_if_touching_floor(entity, &mut jumper, contact_event);
        }
    }
}

fn set_jumping_false_if_touching_floor(entity: Entity, jumper: &mut Jumper, event: &ContactEvent) {
    if let ContactEvent::Started(h1, h2) = event {
        if h1.entity() == entity || h2.entity() == entity {
            jumper.is_jumping = false
        }
    }
}
```

This system retrieves all the `Jumper` components and all the contact events that occurred. It then looks at whether one of the contact events involves a given jumper. If it's the case, it sets the `is_jumping` to false flag on our jumper.

If we want this to work, we need to activate contact events for our player collider:

```rs
flags: ColliderFlags {
    active_events: ActiveEvents::CONTACT_EVENTS,
    ..Default::default()
},
```

Run `cargo run` and play with the up key. Our player can no longer double jump, and it can re-jump after touching the floor.

Part 1:
{% link https://dev.to/sbelzile/rust-platformer-part-1-bevy-and-ecs-2pci %}