H11Y BLOG Logo

Hextorio Devlog 2 - EventReader and EventWriter

Dec 06 2022


Intro

This is the second blog post related to my game project Hextorio. It covers some topics related to Bevy.

I’ve been slowly trying to learn some of the nuances related to the core std library of Rust and Bevy. Most of my notes have been on paper: writing code samples by hand so that I might internalize the syntax of rust.

Today I’m going to write about how I went about refactoring the hover behavior of my bevy game which I tentatively call Hextorio.

The old System

Previously, I handled hover events using a hard-to-maintain (untenable) pattern. I’ve now opted for an event-based pattern.

Previously my implementation of HexCell hovering involved setting a component field called Hoverable.hover to the state of the cell’s hover. It did this as a way of determining whether or not to update the sprite.color of the entity associated with the hovered HexCell.

The old code below for hex_grid_hover was complikcated and not very straightforward. I think the new solution outlined below is a pretty good pattern that I plan to use in the future.

fn hex_grid_hover(
    mut commands: Commands,
    buttons: Res<Input<MouseButton>>,
    windows: Res<Windows>,
    mut query: Query<(
        &mut HexCell,
        &mut Handle<Image>,
        &mut Hoverable,
        &mut Sprite,
        Entity,
    )>,
    q_camera: Query<(&Camera, &GlobalTransform), With<MainCamera>>,
    asset_server: Res<AssetServer>,
    mut selected_item: ResMut<SelectedItem>,
) {
    // Games typically only have one window (the primary window).
    // For multi-window applications, you need to use a specific window ID here.
    let window = windows.get_primary().unwrap();
    let (_camera, transform) = q_camera.single();

    if let Some(_position) = window.cursor_position() {
        let norm = Vec3::new(
            _position.x - window.width() / 2.,
            _position.y - window.height() / 2.,
            0.,
        );
        let current_coords = (*transform * norm).truncate();
        let mut coords = current_coords.clone();
        if selected_item.waiting_for_release == true {
            coords = Vec2::new(
                selected_item.clicked_coords.x,
                selected_item.clicked_coords.y,
            )
        }
        // cursor is inside the window, position given
        for (cell, mut handle, mut hoverable, mut sprite, entity) in query.iter_mut() {
            if cell.intersects_coords(coords) {
                if !hoverable.hover {
                    // needs to be an overlay, not changing the underlying icon.
                    // *handle = asset_server.load("Hexagon_Hover.png");
                    sprite.color = Color::rgba(1.0, 1.0, 1.0, 0.5);
                    hoverable.hover = true
                }
                ...
            } else {
                if hoverable.hover {
                    *handle = asset_server.load("Hexagon.png");
                    hoverable.hover = false;
                    sprite.color = Color::rgba(1.0, 1.0, 1.0, 1.0);
                }
            }
        }
    }
}

The new system for handling hover: Events

Now there are two systems:

  1. A system for determining which cell is hovered that will send an EventItemHovered event using an EventWriter SystemParam.
  2. A system for modifying the tint of the sprite of the entity passed through the EventItemHovered via an EventReader SystemParam.

Event-Driven Hover Events

Video

The following video is a walkthrough through the code and the final product of event-based hovering mechanics in Rust and Bevy.

Adding an Event type

Here is the code for setting up a new event:

// An event struct
struct EventItemHovered {
    entity: Entity,
    hovered: bool,
}
// insert the event as a resource in the build step
app.insert_resource(Events::<EventItemHovered>::default());

EventWriter

// handle grid hover
fn hex_grid_hover(
    ...,
    mut event_hovered: EventWriter<EventItemHovered>,
) {
    ...
    event_hovered.send(EventItemHovered {
        entity,
        hovered: true,
    });
    ...
}

EventReader

// read events
fn paint_hovered(
    mut hovered_event: EventReader<EventItemHovered>,
    ...
) {
    ...
    // get the entity associated with the event
    if let Ok((cell, children, _)) = hex_cell_query.get(event.entity) {
        ...
        if event.hovered {
            s.color = Color::rgba(0.0, 1.0, 0.0, 0.5);
        } else {
            s.color = Color::rgba(1.0, 1.0, 1.0, 1.0);
        }
        ...
    }
    ...
}

Adding the event systems to the app

It’s important that systems using EventReader are executed after systems that create associated events using an EventWriter.

app.add_system(hex_grid_hover);
// call .after() to ensure paint_hovered will run after hex_grid_hover
app.add_system(paint_hovered.after(hex_grid_hover));

Relevant documentation