How to read Bevy events without consuming them?
Asked Answered
E

2

8

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;
        }
    }
}
Erastes answered 31/8, 2020 at 17:4 Comment(0)
E
15

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.

Erastes answered 31/8, 2020 at 17:25 Comment(0)
P
1

For posteriority: this is now Bevy's default

Peraza answered 12/3, 2023 at 18:49 Comment(2)
I believe that patch note simply refers to how you read events. Before you needed a reader+ the events themselves. Then they changed it so you have a combined type that does both. The question was asking how to read without consuming the eventsInefficient
I understood the question in the way that OP had an issue with two systems reading events and thus only one of them actually accessing them. The default way in Bevy now ensures that this isn't actually possible.Peraza

© 2022 - 2024 — McMap. All rights reserved.