Making Games in Rust - Part 11 - Killing Monsters with Bullets
Tags: bevy, rust, programming, tutorial
Published: 2022-01-24 12:06:31

[Getting killed](https://dev.to/sbelzile/making-games-in-rust-part-10-death-enemies-cfg) is not cool. On the other hand, killing monsters is. This article will implement shooting bullets and make them kill monsters on contact.

## Components

Let's start by adding the required components:

```rs
#[derive(Copy, Clone)]
pub enum GameDirection {
    Left,
    Right
}

pub struct Bullet;
```

The `Bullet` component will be used to identify our bullets entities.

The `GameDirection` will be used to fire our bullets in the right direction.

## Drawing Bullets

Let's create a file called `bullets.rs` that will contain the required code to create a new bullet:

```rs
pub struct BulletOptions {
    pub x: f32,
    pub y: f32,
    pub direction: GameDirection,
}

pub fn insert_bullet_at(
    commands: &mut Commands,
    materials: &Res<Materials>,
    options: BulletOptions,
) {
    let speed = match options.direction {
        GameDirection::Left => -14.0,
        _ => 14.0,
    };

    let x = match options.direction {
        GameDirection::Left => options.x - 1.,
        _ => options.x + 1.,
    };
    let rigid_body = RigidBodyBundle {
        position: Vec2::new(x, options.y).into(),
        velocity: RigidBodyVelocity {
            linvel: Vec2::new(speed, 0.0).into(),
            ..Default::default()
        },
        mass_properties: RigidBodyMassPropsFlags::ROTATION_LOCKED.into(),
        activation: RigidBodyActivation::cannot_sleep(),
        forces: RigidBodyForces {
            gravity_scale: 0.,
            ..Default::default()
        },
        ..Default::default()
    };

    let collider = ColliderBundle {
        shape: ColliderShape::cuboid(0.25, 0.05),
        flags: ColliderFlags {
            active_events: ActiveEvents::CONTACT_EVENTS,
            ..Default::default()
        },
        ..Default::default()
    };

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

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

This code contains the usual boilerplate to create a new entity. It creates a rigid body, a collider and a sprite. The collider must enable the `CONTACT_EVENTS` flag. The color of our bullets will be yellow. Here is the code to declare the bullet material:

```rs
// src/game/mod.rs
bullet_material: materials.add(Color::rgb(0.8, 0.8, 0.).into()),

// src/game/components.rs
pub struct Materials {
    pub player_material: Handle<ColorMaterial>,
    pub floor_material: Handle<ColorMaterial>,
    pub monster_material: Handle<ColorMaterial>,
    pub bullet_material: Handle<ColorMaterial>,
}
```

## Shooting Bullets

In our `player.rs` file, we can add a system that will create a new bullet whenever the space bar is pressed:

```rs
// in build method of PlayerPlugin
SystemSet::on_update(AppState::InGame)
    .with_system(fire_controller.system())

// system implementation
pub fn fire_controller(
    keyboard_input: Res<Input<KeyCode>>,
    mut commands: Commands,
    materials: Res<Materials>,
    players: Query<(&Player, &RigidBodyPosition), With<Player>>,
) {
    if keyboard_input.just_pressed(KeyCode::Space) {
        for (player, position) in players.iter() {
            let options = BulletOptions {
                x: position.position.translation.x,
                y: position.position.translation.y,
                direction: GameDirection::Right,
            };
            insert_bullet_at(&mut commands, &materials, options)
        }
    }
}
```

Compiling and running the game should show that pressing space shoots useless bullets. Useless since they do not kill monsters...

## Killing Monsters

To kill monsters, we can add a system that detects contacts between monsters and bullets:

```rs
// in build method of PlayerPlugin
SystemSet::on_update(AppState::InGame)
    .with_system(kill_on_contact.system())

// system implementation
pub fn kill_on_contact(
    mut commands: Commands,
    bullets: Query<Entity, With<Bullet>>,
    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 bullet in bullets.iter() {
                for enemy in enemies.iter() {
                    if (h1.entity() == bullet && h2.entity() == enemy)
                        || (h1.entity() == enemy && h2.entity() == bullet)
                    {
                        commands.entity(enemy).despawn_recursive();
                    }
                }
            }
        }
    }
}
```

Try running the game and try shooting at monsters. Monsters should now disappear when they are hit by a bullet.

## Destroying Bullets

Our bullets are currently eternal. They do not disappear after hitting a monster, and they get stuck on walls. We should despawn them whenever they hit something:

```rs
// in build method of PlayerPlugin
SystemSet::on_update(AppState::InGame)
    .with_system(destroy_bullet_on_contact.system())

// system implementation
pub fn destroy_bullet_on_contact(
    mut commands: Commands,
    bullets: Query<Entity, With<Bullet>>,
    mut contact_events: EventReader<ContactEvent>,
) {
    for contact_event in contact_events.iter() {
        if let ContactEvent::Started(h1, h2) = contact_event {
            for bullet in bullets.iter() {
                if h1.entity() == bullet || h2.entity() == bullet {
                    commands.entity(bullet).despawn_recursive();
                }
            }
        }
    }
}
```

## Shooting Left

Our player always shoots in a single direction... It would be better if we could select the direction we shoot to. To do so, we will add a facing direction to our player:

```rs
// src/game/components.rs
pub struct Player {
    pub speed: f32,
    pub facing_direction: GameDirection,
}
```

And initialize it on our player:

```rs
.insert(Player {
    speed: 7.,
    facing_direction: GameDirection::Right,
})
```

Then, we will update our `player_controller` system to update this facing direction when our player moves:

```rs
pub fn player_controller(
    keyboard_input: Res<Input<KeyCode>>,
    mut players: Query<(&mut Player, &mut RigidBodyVelocity)>,
) {
    for (mut player, mut velocity) in players.iter_mut() {
        if keyboard_input.pressed(KeyCode::Left) {
            velocity.linvel = Vec2::new(-player.speed, velocity.linvel.y).into();
            player.facing_direction = GameDirection::Left
        }
        if keyboard_input.pressed(KeyCode::Right) {
            velocity.linvel = Vec2::new(player.speed, velocity.linvel.y).into();
            player.facing_direction = GameDirection::Right
        }
    }
}
```

Finally, we will pass this direction to our bullet creation function to fire the bullet in the right direction:

```rs
pub fn fire_controller(
    keyboard_input: Res<Input<KeyCode>>,
    mut commands: Commands,
    materials: Res<Materials>,
    players: Query<(&Player, &RigidBodyPosition), With<Player>>,
) {
    if keyboard_input.just_pressed(KeyCode::Space) {
        for (player, position) in players.iter() {
            let options = BulletOptions {
                x: position.position.translation.x,
                y: position.position.translation.y,
                direction: player.facing_direction,
            };
            insert_bullet_at(&mut commands, &materials, options)
        }
    }
}
```

Running the game should confirm that you can shoot bullets left and right.

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