Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to read Bevy events without consuming them?

Tags:

rust

bevy

I am currently trying to use events to signal when the character jumps in my Bevy game. I want the system that handles player inputs to send a JumpedEvent which can then be received by other systems to perform the appropriate actions (set the correct player animations, velocity, sound, etc.) but the first system to read the event consumes it.

Does Bevy provide a way to read events without consuming them?

Here is my current code:

// Sends a JumpedEvent when the jump key is pressed
fn player_jump_event_system(
    keyboard_input: Res<Input<KeyCode>>,
    mut jump_events: ResMut<Events<JumpedEvent>>,
    mut query: Query<(&Controlled, &mut Jumps)>,
) {
    for (controlled, mut jumps) in &mut query.iter() {
        if keyboard_input.just_pressed(controlled.jump)
            && jumps.jumps_remaining > 0
        {
            jumps.jumps_remaining -= 1;
            jump_events.send(JumpedEvent {
                jump_number: jumps.max_jumps - jumps.jumps_remaining,
            });
        }
    }
}

// This system consumes the JumpedEvent that was sent
fn control_player_jump_system(
    jump_events: ResMut<Events<JumpedEvent>>,
    mut jump_event_listener: ResMut<JumpedEventListener>,
    mut query: Query<(&Controlled, &mut Jumps, &mut Velocity)>,
) {
    const PLAYER_JUMP_SPEED: f32 = 450.0;
    const MULTI_JUMP_MODIFIER: f32 = 0.9;

    // For every jump event, make player jump slightly less high
    for jump_event in jump_event_listener.jumped_event_reader.iter(&jump_events)
    {
        for (_, _, mut velocity) in &mut query.iter() {
            *velocity.0.y_mut() = PLAYER_JUMP_SPEED
                * MULTI_JUMP_MODIFIER.powi(i32::from(jump_event.jump_number));
        }
    }
}

// This is the system that cannot receive the event because the above system consumes it
fn select_animation_system(
    texture_atlases: Res<Assets<TextureAtlas>>,
    jump_events: ResMut<Events<JumpedEvent>>,
    mut jump_event_listener: ResMut<JumpedEventListener>,
    mut query: Query<(
        &Jumps,
        &Velocity,
        &AnimatedPlayer,
        &mut TextureAtlasSprite,
        &mut Handle<TextureAtlas>,
    )>,
) {
    for (_, velocity, animations, mut sprite, mut texture_atlas) in
        &mut query.iter()
    {

        // Check if the player just jumped
        let just_jumped = jump_event_listener
            .jumped_event_reader
            .iter(&jump_events)
            .next()
            .is_some();

        // Omitting irrelevant details...

        if just_jumped {

            sprite.index = 0;
        }
    }
}
like image 628
EtTuBrute Avatar asked Aug 31 '20 17:08

EtTuBrute


1 Answers

I just realized what I was doing wrong. I was using a single global resource EventReader to listen to the JumpedEvent instances being sent. Each EventReader only reads each event a single time. What I needed to do instead was to have an individual EventReader for each system that needed to listen for the event. I did this by using Local EventReaders for each system that needed to listen for the event. See below the modified code:

// Sends a JumpedEvent when the jump key is pressed
fn player_jump_event_system(
    keyboard_input: Res<Input<KeyCode>>,
    mut jump_events: ResMut<Events<JumpedEvent>>,
    mut query: Query<(&Controlled, &mut Jumps)>,
) {
    for (controlled, mut jumps) in &mut query.iter() {
        if keyboard_input.just_pressed(controlled.jump)
            && jumps.jumps_remaining > 0
        {
            jumps.jumps_remaining -= 1;
            jump_events.send(JumpedEvent {
                jump_number: jumps.max_jumps - jumps.jumps_remaining,
            });
        }
    }
}

// This system consumes the JumpedEvent that was sent
fn control_player_jump_system(
    jump_events: ResMut<Events<JumpedEvent>>,

    // See that this line now specifies that the resource is local to the system
    mut jump_event_listener: Local<JumpedEventListener>,

    mut query: Query<(&Controlled, &mut Jumps, &mut Velocity)>,
) {
    const PLAYER_JUMP_SPEED: f32 = 450.0;
    const MULTI_JUMP_MODIFIER: f32 = 0.9;

    // For every jump event, make player jump slightly less high
    for jump_event in jump_event_listener.jumped_event_reader.iter(&jump_events)
    {
        for (_, _, mut velocity) in &mut query.iter() {
            *velocity.0.y_mut() = PLAYER_JUMP_SPEED
                * MULTI_JUMP_MODIFIER.powi(i32::from(jump_event.jump_number));
        }
    }
}

// This is the system that cannot receive the event because the above system consumes it
fn select_animation_system(
    texture_atlases: Res<Assets<TextureAtlas>>,
    jump_events: ResMut<Events<JumpedEvent>>,
   
    // See that this line now specifies that the resource is local to the system
    mut jump_event_listener: Local<JumpedEventListener>,

    mut query: Query<(
        &Jumps,
        &Velocity,
        &AnimatedPlayer,
        &mut TextureAtlasSprite,
        &mut Handle<TextureAtlas>,
    )>,
) {
    for (_, velocity, animations, mut sprite, mut texture_atlas) in
        &mut query.iter()
    {

        // Check if the player just jumped
        let just_jumped = jump_event_listener
            .jumped_event_reader
            .iter(&jump_events)
            .next()
            .is_some();

        // Omitting irrelevant details...

        if just_jumped {

            sprite.index = 0;
        }
    }
}

So, each EventReader consumes each event upon reading it. Thus, to have multiple consuming systems, each system needs a Local reader.

like image 128
EtTuBrute Avatar answered Oct 19 '22 05:10

EtTuBrute