Skip to content

ffmulks/bevy_enum_event

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

bevy_enum_event

General-purpose enum to Bevy event conversion macro.

Overview

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 entity
  • EntityEvent: Events that target a specific entity and can trigger entity-specific observers

This crate provides corresponding derive macros for both:

  • #[derive(EnumEvent)] - Generates Event types
  • #[derive(EnumEntityEvent)] - Generates EntityEvent types

Bevy Compatibility

Bevy bevy_enum_event
0.17 0.2
0.16 0.1

Features

  • 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: PlayerStateplayer_state module
  • 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 where clauses
  • Bevy integration: Generated events work seamlessly with Bevy's observer system
  • Entity event support: Generate EntityEvent types with entity targeting and propagation
  • Deref support (optional, enabled by default): Automatic Deref and DerefMut for ergonomic field access

Installation

[dependencies]
bevy_enum_event = "0.2"

Part 1: EnumEvent - Global Events

Quick Start - Unit Variants

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;
}

Variants with Data

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;
}

Using Events with Bevy Observers

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!");
}

Accessing Event Data

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);
}

Deref Feature (enabled by default)

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

Example - Automatic Deref

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);
}

Example - Multi-Field with Deref Annotation

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);
}

Disabling Deref

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);
}

Advanced: Generics & Lifetimes

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.


Part 2: EnumEntityEvent - Entity-Targeted Events

EntityEvent types target specific entities and can trigger entity-specific observers, enabling fine-grained control over event handling.

Important Requirements

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)]
  • Triggering: Use commands.trigger(event) or world.trigger(event)

Basic EntityEvent Usage

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);
        });
}

Custom Target Field

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

Event propagation allows events to "bubble up" through entity hierarchies, similar to DOM event propagation in web browsers.

Basic Propagation

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();
}

Auto Propagation

For events that should always bubble up without manual control:

#[derive(EnumEntityEvent, Clone, Copy)]
#[enum_event(auto_propagate, propagate)]
enum SystemEvent {
    Update { entity: Entity },
}

Custom Propagation Relationships

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.

Variant-Level Propagation

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.

Use Cases

  • 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

AI Disclaimer

  • 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

License

Licensed under either of:

at your option.

Contribution

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.

About

No description, website, or topics provided.

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Packages

No packages published

Languages