General-purpose enum to Bevy event conversion macro.
bevy_enum_event provides derive macros that automatically generate Bevy event types from enum variants. For each variant in your enum, it creates a corresponding event struct organized in a snake_case module. Supports unit variants, tuple variants, and named field variants.
Starting with Bevy 0.17, there are two types of events:
Event: Global events that are not associated with any specific entityEntityEvent: Events that target a specific entity and can trigger entity-specific observers
This crate provides corresponding derive macros for both:
#[derive(EnumEvent)]- GeneratesEventtypes#[derive(EnumEntityEvent)]- GeneratesEntityEventtypes
| Bevy | bevy_enum_event |
|---|---|
| 0.17 | 0.2 |
| 0.16 | 0.1 |
- Automatic event generation: One macro generates all variant events
- Support for data-carrying variants: Enum variants can contain data (tuple or named fields)
- Snake case module:
PlayerState→player_statemodule - Zero boilerplate: No manual event struct definitions needed
- Type-safe: Each variant gets its own distinct event type
- Generic-friendly: Works with lifetimes, generic parameters, and
whereclauses - Bevy integration: Generated events work seamlessly with Bevy's observer system
- Entity event support: Generate
EntityEventtypes with entity targeting and propagation - Deref support (optional, enabled by default): Automatic
DerefandDerefMutfor ergonomic field access
[dependencies]
bevy_enum_event = "0.2"use bevy::prelude::*;
use bevy_enum_event::EnumEvent;
#[derive(EnumEvent, Clone, Copy, Debug)]
enum PlayerState {
Idle,
Running,
Jumping,
}This automatically generates:
pub mod player_state {
use bevy::prelude::Event;
#[derive(Event, Clone, Copy, Debug)]
pub struct Idle;
#[derive(Event, Clone, Copy, Debug)]
pub struct Running;
#[derive(Event, Clone, Copy, Debug)]
pub struct Jumping;
}Enum variants can carry data using tuple or named field syntax:
use bevy::prelude::*;
use bevy_enum_event::EnumEvent;
#[derive(EnumEvent, Clone)]
enum GameEvent {
PlayerSpawned(Entity),
ScoreChanged { player: Entity, score: i32 },
GameOver,
}This generates:
pub mod game_event {
use bevy::prelude::Event;
#[derive(Event, Clone, Debug)]
pub struct PlayerSpawned(pub Entity);
#[derive(Event, Clone, Debug)]
pub struct ScoreChanged {
pub player: Entity,
pub score: i32,
}
#[derive(Event, Clone, Debug)]
pub struct GameOver;
}use bevy::prelude::*;
use bevy_enum_event::EnumEvent;
#[derive(EnumEvent, Clone, Copy)]
enum GameState {
MainMenu,
Playing,
Paused,
}
fn setup(app: &mut App) {
app.observe(on_paused);
}
fn on_paused(paused: On<game_state::Paused>) {
println!("Game paused!");
}use bevy::prelude::*;
use bevy_enum_event::EnumEvent;
#[derive(EnumEvent, Clone)]
enum GameEvent {
Victory(String),
ScoreChanged { team: u32, score: i32 },
GameOver,
}
fn on_score_changed(score: On<game_event::ScoreChanged>) {
let event = score.event();
println!("Team {} scored {} points", event.team, event.score);
}The deref feature provides ergonomic access to event data by automatically implementing Deref and DerefMut:
- Single-field variants: Automatically get deref to the inner value
- Multi-field variants: Mark one field with
#[enum_event(deref)]for deref access - No annotation: Access fields directly by name
use bevy::prelude::*;
use bevy_enum_event::EnumEvent;
#[derive(EnumEvent, Clone)]
enum NetworkEvent {
MessageReceived(String), // Single field - automatic deref
HealthChanged { value: f32 }, // Single field - automatic deref
}
fn on_message(msg: On<network_event::MessageReceived>) {
// Direct access to the String via deref
let content: &String = &*msg.event();
println!("Received: {}", content);
}
fn on_health(health: On<network_event::HealthChanged>) {
// Direct access to the f32 via deref
let value: f32 = *health.event();
println!("Health: {}", value);
}use bevy::prelude::*;
use bevy_enum_event::EnumEvent;
#[derive(EnumEvent, Clone)]
enum GameEvent {
// Mark the primary field for deref access
PlayerScored { #[enum_event(deref)] player: Entity, points: u32 },
// No annotation - access fields directly
TeamScore { team: u32, points: u32 },
}
fn on_player_scored(scored: On<game_event::PlayerScored>) {
// Deref gives you the player entity
let player: Entity = *scored.event();
// Other fields still accessible by name
println!("Player {:?} scored {} points", player, scored.event().points);
}
fn on_team_score(scored: On<game_event::TeamScore>) {
// No deref - access fields directly
let event = scored.event();
println!("Team {} scored {} points", event.team, event.points);
}If you prefer not to have Deref and DerefMut automatically implemented:
[dependencies]
bevy_enum_event = { version = "0.2", default-features = false }When disabled, access fields directly:
fn on_message(msg: On<network_event::MessageReceived>) {
let content: &String = &msg.event().0; // Access via .0 for tuple variants
println!("Received: {}", content);
}The macro preserves generic parameters, lifetimes, and where clauses from your enum:
#[derive(EnumEvent, Clone)]
enum GenericEvent<'a, T>
where
T: Clone + 'a,
{
Borrowed(&'a T),
Owned(T),
Done,
}Generated types: generic_event::Borrowed<'a, T>, generic_event::Owned<'a, T>, generic_event::Done<'a, T>.
Unit variants automatically implement Default and get a new() helper when phantom markers are needed. Tuple and named variants with phantom markers also receive new(...) helpers that accept only the original fields.
EntityEvent types target specific entities and can trigger entity-specific observers, enabling fine-grained control over event handling.
Before diving into examples, note these requirements for EnumEntityEvent:
- Named fields only: All variants must use struct-style
{ field: Type }syntax - Entity field required: Each variant must have either:
- A field named
entity: Entity, OR - A field marked with
#[enum_event(target)]
- A field named
- Triggering: Use
commands.trigger(event)orworld.trigger(event)
use bevy::prelude::*;
use bevy_enum_event::EnumEntityEvent;
#[derive(EnumEntityEvent, Clone)]
enum PlayerEvent {
Spawned { entity: Entity },
Damaged { entity: Entity, amount: f32 },
Destroyed { entity: Entity },
}
// Global observer - runs for ALL player damage events
fn on_any_player_damaged(damaged: On<player_event::Damaged>) {
println!("A player took {} damage", damaged.amount);
}
// Entity-specific observer - only runs for events targeting this specific entity
fn setup_player(mut commands: Commands) {
commands.spawn_empty()
.observe(|damaged: On<player_event::Damaged>| {
println!("This specific player took {} damage", damaged.amount);
});
}By default, EnumEntityEvent looks for a field named entity. You can use a different field with #[enum_event(target)]:
use bevy::prelude::*;
use bevy_enum_event::EnumEntityEvent;
#[derive(EnumEntityEvent, Clone, Copy)]
enum CombatEvent {
Attack {
#[enum_event(target)]
attacker: Entity,
defender: Entity,
},
}
// This event will trigger observers on the attacker entity
fn trigger_attack(mut commands: Commands, attacker: Entity, defender: Entity) {
commands.trigger(combat_event::Attack { attacker, defender });
}Event propagation allows events to "bubble up" through entity hierarchies, similar to DOM event propagation in web browsers.
Enable propagation with #[enum_event(propagate)]:
use bevy::prelude::*;
use bevy_enum_event::EnumEntityEvent;
// Default propagation uses the ChildOf relationship
#[derive(EnumEntityEvent, Clone, Copy)]
#[enum_event(propagate)]
enum UiEvent {
Click { entity: Entity },
Hover { entity: Entity },
}
fn on_click(mut click: On<ui_event::Click>) {
println!("Clicked on: {:?}", click.entity);
// Cause the event to bubbling up to parent entities
click.propagate(true);
// Access the original target that triggered the event
let original = click.original_event_target();
}For events that should always bubble up without manual control:
#[derive(EnumEntityEvent, Clone, Copy)]
#[enum_event(auto_propagate, propagate)]
enum SystemEvent {
Update { entity: Entity },
}Specify a custom relationship type for propagation instead of the default ChildOf:
use bevy::prelude::*;
use bevy_enum_event::EnumEntityEvent;
#[derive(Component)]
#[relationship(relationship_target = CustomRelationshipTarget)]
pub struct CustomRelationship(pub Entity);
#[derive(Component)]
#[relationship_target(relationship = CustomRelationship, linked_spawn)]
pub struct CustomRelationshipTarget(Vec<Entity>);
// Propagate along CustomRelationship instead of ChildOf
// Note: Use absolute paths (::) or make the relationship public
#[derive(EnumEntityEvent, Clone, Copy)]
#[enum_event(propagate = &'static CustomRelationship)]
enum NetworkEvent {
DataReceived { entity: Entity },
}Important: Custom relationship types must be pub or referenced via absolute paths (::bevy::, crate::, etc.) because they're accessed from the generated module.
Override enum-level propagation settings for specific variants:
#[derive(EnumEntityEvent, Clone, Copy)]
#[enum_event(propagate)] // Default for all variants
enum MixedEvent {
Normal { entity: Entity }, // Uses enum-level propagate
#[enum_event(auto_propagate, propagate)] // Override with auto_propagate
AutoEvent { entity: Entity },
#[enum_event(propagate = &'static ::bevy::prelude::ChildOf)] // Custom relationship
CustomEvent { entity: Entity },
}
## Snake Case Conversion
The macro intelligently converts enum names to snake_case module names:
- `LifeFSM` → `life_fsm`
- `PlayerState` → `player_state`
- `HTTPServer` → `http_server`
- `MyHTTPSConnection` → `my_https_connection`
## Generics & Lifetimes
All derives mirror the generic parameters, lifetimes, and `where` clauses from your enum onto the generated
event structs. This makes it straightforward to use `EnumEvent` with enums such as:
```rust
#[derive(EnumEvent, Clone)]
enum GenericEvent<'a, T>
where
T: Clone + 'a,
{
Borrowed(&'a T),
Owned(T),
Done,
}The generated module exposes generic_event::Borrowed<'a, T>, generic_event::Owned<'a, T>, and
generic_event::Done<'a, T> types with identical bounds.
Unit event structs expose ergonomic constructors so you never have to juggle hidden PhantomData
markers by hand. Every unit variant implements Default, and when a phantom marker is required the
derive also emits a new() helper that seeds it for you. Tuple and named variants that require
phantom markers likewise receive new(...) helpers that accept only the original fields.
- State machines (see bevy_fsm)
- Game state transitions
- Entity lifecycle events
- Animation states
- Input modes
- Any enum-based event system
- Network message events
- Input/Output events with associated data
- Refactoring and documentation supported by Claude Code
- Minor editing supported by ChatGPT Codex
- The process and final releases are thoroughly supervised and checked by the author
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.