Making Games in Rust - Part 10 - Death & Enemies
Tags: bevy, rust, programming, tutorial
Published: 2022-01-17 12:57:57

We can start and stop our game, we have a map to explore, but our player cannot win or lose.

This article will allow our player to die, and will add enemies to the game.

## Death By Falling Down

The easiest way to die to implement is death by falling down. If our player falls below some level, he dies.

```rs
fn death_by_height(
    mut commands: Commands,
    players: Query<(Entity, &RigidBodyPosition), With<Player>>,
) {
    for (entity, position) in players.iter() {
        if position.position.translation.y < -1. {
            commands.entity(entity).despawn_recursive();
        }
    }
}
```

If you add this system, register it, and run the game, you should be able to kill our player by jumping into a pit.

You should notice one problem though: this death also destroy our camera since it is defined as a child of our player...

To fix this, let's render the camera on the same level as our player:

```rs
// player.rs
struct PlayerData {
    player_entity: Entity,
    camera_entity: Entity,
}

// in spawn_player, to remove
- .with_children(|parent| {
-     parent.spawn_bundle(new_camera_2d());
- })

// in spawn_player, to add
let camera_entity = commands.spawn_bundle(new_camera_2d()).id();
    commands.insert_resource(PlayerData {
        player_entity,
        camera_entity,
    });

// in cleanup_player
commands
    .entity(player_data.camera_entity)
    .despawn_recursive();
```

And let's re-implement the camera should follow player requirement with a system:

```rs
fn camera_follow_player(
    mut cameras: Query<&mut Transform, With<Camera>>,
    players: Query<&RigidBodyPosition, With<Player>>,
) {
    for player in players.iter() {
        for mut camera in cameras.iter_mut() {
            camera.translation.x = player.position.translation.x;
            camera.translation.y = player.position.translation.y;
        }
    }
}
```

Now, running the game and falling into a pit should kill our player:


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

## Monsters

To add monsters:

1. Add an `Enemy` and a `Monster` component:

```rs
pub struct Enemy;
pub struct Monster;
```

The `Enemy` component will be useful for everything "enemy" related, and the `Monster` component is the specific for monsters. This way we could easily implement a `Trap` entity and have it automatically catch up every `Enemy` related functionalities such as death by contact, but not things like a monster artificial intelligence.

2. Add a monster material to the materials:

```rs
pub struct Materials {
    pub player_material: Handle<ColorMaterial>,
    pub floor_material: Handle<ColorMaterial>,
    pub monster_material: Handle<ColorMaterial>,
}

fn setup(mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>) {
    commands.insert_resource(Materials {
        player_material: materials.add(Color::rgb(0.969, 0.769, 0.784).into()),
        floor_material: materials.add(Color::rgb(0.7, 0.7, 0.7).into()),
        monster_material: materials.add(Color::rgb(0.8, 0., 0.).into()),
    });
}
```

3. In `monster.rs`, we will add a function to add our monsters to the scene:

```rs
pub fn insert_monster_at(commands: &mut Commands, x: usize, y: usize, materials: &Res<Materials>) {
    let rigid_body = RigidBodyBundle {
        position: Vec2::new(x as f32, y as f32).into(),
        mass_properties: RigidBodyMassPropsFlags::ROTATION_LOCKED.into(),
        activation: RigidBodyActivation::cannot_sleep(),
        forces: RigidBodyForces {
            gravity_scale: 3.,
            ..Default::default()
        },
        ..Default::default()
    };

    let collider = ColliderBundle {
        shape: ColliderShape::round_cuboid(0.35, 0.35, 0.1),
        flags: ColliderFlags {
            active_events: ActiveEvents::CONTACT_EVENTS,
            ..Default::default()
        },
        ..Default::default()
    };

    let sprite = SpriteBundle {
        material: materials.monster_material.clone(),
        sprite: Sprite::new(Vec2::new(0.9, 0.9)),
        ..Default::default()
    };

    commands
        .spawn_bundle(sprite)
        .insert_bundle(rigid_body)
        .insert_bundle(collider)
        .insert(RigidBodyPositionSync::Discrete)
        .insert(Enemy)
        .insert(Monster);
}
```

Here, our monster gets a rigid body, a collider, a red sprite and the `Enemy` and `Monster` components.

4. Add a step to the map generation script to randomly add monsters to our world:

```rs
// In map.rs
// in the spawn_floor method
add_enemies(&mut commands, &world, &materials);

// Function to insert monsters
fn add_enemies(commands: &mut Commands, world: &Vec<usize>, materials: &Res<Materials>) {
    world.iter().enumerate().for_each(|(x, height)| {
        if should_add_enemy(x) {
            insert_monster_at(commands, x, *height + 1, materials)
        }
    })
}

// Determines whether we should add a monster or not
fn should_add_enemy(x: usize) -> bool {
    if x <= 5 {
        return false;
    }
    let mut rng = thread_rng();
    let random_number: u32 = rng.gen_range(0..100);
    match random_number {
        0..=90 => false,
        _ => true,
    }
}
```

If you run the game now, monsters should be visible:


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

## Dying to the Hand of your Enemies

Enemies should kill the player on contact. We learned how to implement something similar in a previous chapter with contact events.

Let's create a new system:

```rs
pub fn death_by_enemy(
    mut commands: Commands,
    mut players: Query<Entity, With<Player>>,
    enemies: Query<Entity, With<Enemy>>,
    mut contact_events: EventReader<ContactEvent>,
) {
    for contact_event in contact_events.iter() {
        if let ContactEvent::Started(h1, h2) = contact_event {
            for player in players.iter_mut() {
                for enemy in enemies.iter() {
                    if (h1.entity() == player && h2.entity() == enemy) || (h1.entity() == enemy && h2.entity() == player) {
                        commands.entity(player).despawn_recursive();
                    }
                }
            }
        }
    }
}
```

And let's register it:

```rs
.with_system(death_by_enemy.system())
```

If you run the game, your player should die when it touches a monster:


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

The final code is available [here](https://github.com/belzile/platformer/tree/part-10).