Hextorio Devlog 2 - EventReader and EventWriter
12/6/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:
- A system for determining which cell is hovered that will send an EventItemHovered event using an
EventWriter
SystemParam
. - A system for modifying the tint of the sprite of the entity passed through the
EventItemHovered
via anEventReader
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
- Example of using EventWriter and EventReader with two systems.
- How to run a system after another one using .after.
- EventWriter and EventReader.
- A handy list of special SystemParams such as
EventReader
andEventWriter
.