observers/
observers.rs

1//! Demonstrates how to observe events: both component lifecycle events and custom events.
2
3use bevy::{
4    platform::collections::{HashMap, HashSet},
5    prelude::*,
6};
7use rand::{Rng, SeedableRng};
8use rand_chacha::ChaCha8Rng;
9
10fn main() {
11    App::new()
12        .add_plugins(DefaultPlugins)
13        .init_resource::<SpatialIndex>()
14        .add_systems(Startup, setup)
15        .add_systems(Update, (draw_shapes, handle_click))
16        // Observers are systems that run when an event is "triggered". This observer runs whenever
17        // `ExplodeMines` is triggered.
18        .add_observer(
19            |explode_mines: On<ExplodeMines>,
20             mines: Query<&Mine>,
21             index: Res<SpatialIndex>,
22             mut commands: Commands| {
23                // Access resources
24                for entity in index.get_nearby(explode_mines.pos) {
25                    // Run queries
26                    let mine = mines.get(entity).unwrap();
27                    if mine.pos.distance(explode_mines.pos) < mine.size + explode_mines.radius {
28                        // And queue commands, including triggering additional events
29                        // Here we trigger the `Explode` event for entity `e`
30                        commands.trigger(Explode { entity });
31                    }
32                }
33            },
34        )
35        // This observer runs whenever the `Mine` component is added to an entity, and places it in a simple spatial index.
36        .add_observer(on_add_mine)
37        // This observer runs whenever the `Mine` component is removed from an entity (including despawning it)
38        // and removes it from the spatial index.
39        .add_observer(on_remove_mine)
40        .run();
41}
42
43#[derive(Component)]
44struct Mine {
45    pos: Vec2,
46    size: f32,
47}
48
49impl Mine {
50    fn random(rand: &mut ChaCha8Rng) -> Self {
51        Mine {
52            pos: Vec2::new(
53                (rand.random::<f32>() - 0.5) * 1200.0,
54                (rand.random::<f32>() - 0.5) * 600.0,
55            ),
56            size: 4.0 + rand.random::<f32>() * 16.0,
57        }
58    }
59}
60
61/// This is a normal [`Event`]. Any observer that watches for it will run when it is triggered.
62#[derive(Event)]
63struct ExplodeMines {
64    pos: Vec2,
65    radius: f32,
66}
67
68/// An [`EntityEvent`] is a specialized type of [`Event`] that can target a specific entity. In addition to
69/// running normal "top level" observers when it is triggered (which target _any_ entity that Explodes), it will
70/// also run any observers that target the _specific_ entity for that event.
71#[derive(EntityEvent)]
72struct Explode {
73    entity: Entity,
74}
75
76fn setup(mut commands: Commands) {
77    commands.spawn(Camera2d);
78    commands.spawn((
79        Text::new(
80            "Click on a \"Mine\" to trigger it.\n\
81            When it explodes it will trigger all overlapping mines.",
82        ),
83        Node {
84            position_type: PositionType::Absolute,
85            top: px(12),
86            left: px(12),
87            ..default()
88        },
89    ));
90
91    let mut rng = ChaCha8Rng::seed_from_u64(19878367467713);
92
93    commands
94        .spawn(Mine::random(&mut rng))
95        // Observers can watch for events targeting a specific entity.
96        // This will create a new observer that runs whenever the Explode event
97        // is triggered for this spawned entity.
98        .observe(explode_mine);
99
100    // We want to spawn a bunch of mines. We could just call the code above for each of them.
101    // That would create a new observer instance for every Mine entity. Having duplicate observers
102    // generally isn't worth worrying about as the overhead is low. But if you want to be maximally efficient,
103    // you can reuse observers across entities.
104    //
105    // First, observers are actually just entities with the Observer component! The `observe()` functions
106    // you've seen so far in this example are just shorthand for manually spawning an observer.
107    let mut observer = Observer::new(explode_mine);
108
109    // As we spawn entities, we can make this observer watch each of them:
110    for _ in 0..1000 {
111        let entity = commands.spawn(Mine::random(&mut rng)).id();
112        observer.watch_entity(entity);
113    }
114
115    // By spawning the Observer component, it becomes active!
116    commands.spawn(observer);
117}
118
119fn on_add_mine(add: On<Add, Mine>, query: Query<&Mine>, mut index: ResMut<SpatialIndex>) {
120    let mine = query.get(add.entity).unwrap();
121    let tile = (
122        (mine.pos.x / CELL_SIZE).floor() as i32,
123        (mine.pos.y / CELL_SIZE).floor() as i32,
124    );
125    index.map.entry(tile).or_default().insert(add.entity);
126}
127
128// Remove despawned mines from our index
129fn on_remove_mine(remove: On<Remove, Mine>, query: Query<&Mine>, mut index: ResMut<SpatialIndex>) {
130    let mine = query.get(remove.entity).unwrap();
131    let tile = (
132        (mine.pos.x / CELL_SIZE).floor() as i32,
133        (mine.pos.y / CELL_SIZE).floor() as i32,
134    );
135    index.map.entry(tile).and_modify(|set| {
136        set.remove(&remove.entity);
137    });
138}
139
140fn explode_mine(explode: On<Explode>, query: Query<&Mine>, mut commands: Commands) {
141    // Explode is an EntityEvent. `explode.entity` is the entity that Explode was triggered for.
142    let Ok(mut entity) = commands.get_entity(explode.entity) else {
143        return;
144    };
145    info!("Boom! {} exploded.", explode.entity);
146    entity.despawn();
147    let mine = query.get(explode.entity).unwrap();
148    // Trigger another explosion cascade.
149    commands.trigger(ExplodeMines {
150        pos: mine.pos,
151        radius: mine.size,
152    });
153}
154
155// Draw a circle for each mine using `Gizmos`
156fn draw_shapes(mut gizmos: Gizmos, mines: Query<&Mine>) {
157    for mine in &mines {
158        gizmos.circle_2d(
159            mine.pos,
160            mine.size,
161            Color::hsl((mine.size - 4.0) / 16.0 * 360.0, 1.0, 0.8),
162        );
163    }
164}
165
166// Trigger `ExplodeMines` at the position of a given click
167fn handle_click(
168    mouse_button_input: Res<ButtonInput<MouseButton>>,
169    camera: Single<(&Camera, &GlobalTransform)>,
170    windows: Query<&Window>,
171    mut commands: Commands,
172) {
173    let Ok(windows) = windows.single() else {
174        return;
175    };
176
177    let (camera, camera_transform) = *camera;
178    if let Some(pos) = windows
179        .cursor_position()
180        .and_then(|cursor| camera.viewport_to_world(camera_transform, cursor).ok())
181        .map(|ray| ray.origin.truncate())
182        && mouse_button_input.just_pressed(MouseButton::Left)
183    {
184        commands.trigger(ExplodeMines { pos, radius: 1.0 });
185    }
186}
187
188#[derive(Resource, Default)]
189struct SpatialIndex {
190    map: HashMap<(i32, i32), HashSet<Entity>>,
191}
192
193/// Cell size has to be bigger than any `TriggerMine::radius`
194const CELL_SIZE: f32 = 64.0;
195
196impl SpatialIndex {
197    // Lookup all entities within adjacent cells of our spatial index
198    fn get_nearby(&self, pos: Vec2) -> Vec<Entity> {
199        let tile = (
200            (pos.x / CELL_SIZE).floor() as i32,
201            (pos.y / CELL_SIZE).floor() as i32,
202        );
203        let mut nearby = Vec::new();
204        for x in -1..2 {
205            for y in -1..2 {
206                if let Some(mines) = self.map.get(&(tile.0 + x, tile.1 + y)) {
207                    nearby.extend(mines.iter());
208                }
209            }
210        }
211        nearby
212    }
213}