bevyengine/bevy
Observer
An observer is a function that runs in response to an ECS event. Observers are how Bevy implements reactive component lifecycle handling and custom event dispatch in a way that interoperates with the schedule.
The shape
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(MinimalPlugins)
.add_observer(|trigger: Trigger<OnAdd, Player>, mut commands: Commands| {
println!("Player {:?} spawned", trigger.target());
commands.entity(trigger.target()).insert(Health(100));
})
.run();
}Trigger<E, B> is the system parameter. E is the event type; B is the bundle the event applies to (often a single component).
Built-in events
| Event | Fires when |
|---|---|
OnAdd |
A component is added to an entity. |
OnInsert |
A component is added or replaced. |
OnRemove |
A component is removed. |
OnReplace |
A component is replaced (re-inserted). |
OnDespawn |
An entity is despawned (fires for each component before removal). |
These are defined in crates/bevy_ecs/src/lifecycle.rs.
Custom events
#[derive(Event)]
struct DamageDealt { amount: u32 }
world.observe(|trigger: Trigger<DamageDealt>| {
println!("hit for {}", trigger.event().amount);
});
world.trigger(DamageDealt { amount: 10 });Observers are scoped to the world they're registered against. They can also be registered per-entity:
commands.spawn(Health(100)).observe(|trigger: Trigger<DamageDealt>, mut health: Query<&mut Health>| {
if let Ok(mut h) = health.get_mut(trigger.target()) {
h.0 = h.0.saturating_sub(trigger.event().amount);
}
});When a per-entity observer fires, only that entity's handlers receive the event.
Bubbling
Events can propagate up the entity hierarchy via the Traversal trait. The picking events (Pointer<Click>, Pointer<Drag>, …) use this so a click on a deeply-nested UI node can be handled by a parent.
Observers vs systems
| Use | When |
|---|---|
| System | Runs every frame. Good for "all entities matching X get processed." |
| Observer | Runs in response to a specific event. Good for "when X happens, do Y." |
Observers are deferred — they don't run inline at the trigger site. Their effects appear at the next sync point. For inline reactions, use hooks on a component (fn on_add(...)).
Performance
Observers are lighter than the older EventReader/EventWriter pattern when only a handful of entities care, because there's no per-frame scan of an event queue. They are heavier when many entities care, because each handler runs separately. As a rule of thumb: use observers when reaction is rare; use EventReader for high-frequency events.
See also
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.