Files
reliquary/plugins/bevy/skills/bevy-0.19/references/events-observers.md
T
g4borg 8988b16bee 🤖 Fix proofreading issues in bevy skills
- Fix `ref` keyword used as variable name in code examples
- Fix "retries next tick" → systems continue on normal schedule
- Fix `State::set()` → `NextState::set()` in upgrade skill
- Fix `SceneRoot` → `WorldAssetRoot` for 0.19 consistency
- Fix Replace lifecycle event description (overwritten, not removed)
- Fix grep patterns (quoting, trailing spaces)
- Remove third-party physics/networking content (not Bevy built-ins)
2026-07-07 17:52:20 +02:00

3.8 KiB

Events, Messages, Observers & Hooks

0.18 split the old single event system into Messages (pull, buffered) and Events (push, observed). They are different traits with different derives — using the wrong one won't compile, and porting old code mechanically to the wrong side is an architecture bug.

Messages — pull-based, double-buffered

The renamed successor of EventWriter/EventReader. Use for decoupled many-to-many data flow where several systems batch-process the same stream.

#[derive(Message)]
struct Collision { a: Entity, b: Entity }

fn produce(mut writer: MessageWriter<Collision>) {
    writer.write(Collision { a, b });          // also: write_batch(iter), write_default()
}

fn consume(mut reader: MessageReader<Collision>) {
    for c in reader.read() { /* each reader has its own cursor */ }
}
  • Double-buffered: a message survives one update() cycle and is dropped on the next. A reader that runs before the writer in the same frame sees it next frame.
  • Multiple readers run concurrently; multiple writers of the same type cannot.
  • Reader extras: len(), is_empty(), clear().
  • Run condition: on_message::<M>().

Events — push-based, immediate, observed

#[derive(Event)]
struct Speak { text: String }

// Global observer
app.add_observer(|on: On<Speak>| println!("{}", on.text));

// Entity-scoped observer — fires only for this entity
commands.entity(id).observe(|on: On<Hit>| { /* ... */ });

// Trigger
commands.trigger(Speak { text: "hi".into() });
world.trigger_ref(&mut event);   // inspect the event after observers mutate it

Observer param On<'w, 't, E, B> (B: Bundle filters by component):

  • event() / event_mut() — event data (also Deref<Target = E> for direct field access)
  • trigger() / trigger_mut() — trigger context
  • observer() — the observer entity

Observers run immediately during the trigger, not at a schedule point.

EntityEvent — entity-targeted

#[derive(EntityEvent)]
struct Hit {
    #[event_target]
    entity: Entity,
    damage: f32,
}

#[entity_event(propagate)] makes the event bubble up the ChildOf chain — useful for UI-like hit forwarding.

0.19: EntityComponentsTrigger gained archetype fields — destructure with ..: let EntityComponentsTrigger { components, .. } = e.trigger();

Lifecycle events

Built-in events observers can watch per component:

Event Fires Order
Add component added where absent first
Insert any insert (add or replace) after Add
Replace component value overwritten by a new insert over an existing value before Remove
Remove component removed after Replace
Despawn entity despawned (per component) last
world.add_observer(|on: On<Add, Health>| { /* entity just gained Health */ });

Component hooks — synchronous, inline

For callbacks that must be atomic with the ECS operation itself (not deferred, not scheduled):

#[derive(Component)]
#[component(on_add = init_health_bar)]
struct Health(f32);

fn init_health_bar(world: DeferredWorld, ctx: HookContext) { /* ... */ }

Available: on_add, on_insert, on_replace, on_remove, on_despawn.

Choosing the mechanism

Mechanism Model Reach for it when
Messages pull, buffered, scheduled several systems independently process a stream; frame-batched work
Event + observer push, immediate something must react now (gameplay reactions, structural responses)
EntityEvent push, targeted, can propagate the reaction belongs to a specific entity
Component hook synchronous, inline the response must not be separable from the ECS operation
Changed<T> / Added<T> per-tick polling cheap frame-wise checks inside a normal system