Making Games in Rust – Part 10 – Death & Enemies

If you did not read the previous articles, you can start here.

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 B…


This content originally appeared on DEV Community and was authored by Sébastien Belzile

If you did not read the previous articles, you can start here.

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.

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:

// 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:

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

Monsters

To add monsters:

  1. Add an Enemy and a Monster component:
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.

  1. Add a monster material to the materials:
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()),
    });
}
  1. In monster.rs, we will add a function to add our monsters to the scene:
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.

  1. Add a step to the map generation script to randomly add monsters to our world:
// 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

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:

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:

.with_system(death_by_enemy.system())

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

Image description

The final code is available here.


This content originally appeared on DEV Community and was authored by Sébastien Belzile


Print Share Comment Cite Upload Translate Updates
APA

Sébastien Belzile | Sciencx (2022-01-17T12:57:57+00:00) Making Games in Rust – Part 10 – Death & Enemies. Retrieved from https://www.scien.cx/2022/01/17/making-games-in-rust-part-10-death-enemies/

MLA
" » Making Games in Rust – Part 10 – Death & Enemies." Sébastien Belzile | Sciencx - Monday January 17, 2022, https://www.scien.cx/2022/01/17/making-games-in-rust-part-10-death-enemies/
HARVARD
Sébastien Belzile | Sciencx Monday January 17, 2022 » Making Games in Rust – Part 10 – Death & Enemies., viewed ,<https://www.scien.cx/2022/01/17/making-games-in-rust-part-10-death-enemies/>
VANCOUVER
Sébastien Belzile | Sciencx - » Making Games in Rust – Part 10 – Death & Enemies. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2022/01/17/making-games-in-rust-part-10-death-enemies/
CHICAGO
" » Making Games in Rust – Part 10 – Death & Enemies." Sébastien Belzile | Sciencx - Accessed . https://www.scien.cx/2022/01/17/making-games-in-rust-part-10-death-enemies/
IEEE
" » Making Games in Rust – Part 10 – Death & Enemies." Sébastien Belzile | Sciencx [Online]. Available: https://www.scien.cx/2022/01/17/making-games-in-rust-part-10-death-enemies/. [Accessed: ]
rf:citation
» Making Games in Rust – Part 10 – Death & Enemies | Sébastien Belzile | Sciencx | https://www.scien.cx/2022/01/17/making-games-in-rust-part-10-death-enemies/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.