event/
event.rs

1//! This example shows how to send, mutate, and receive, events. As well as showing
2//! how to you might control system ordering so that events are processed in a specific order.
3//! It does this by simulating a damage over time effect that you might find in a game.
4
5use bevy::prelude::*;
6
7// In order to send or receive events first you must define them
8// This event should be sent when something attempts to deal damage to another entity.
9#[derive(Event, Debug)]
10struct DealDamage {
11    pub amount: i32,
12}
13
14// This event should be sent when an entity receives damage.
15#[derive(Event, Debug, Default)]
16struct DamageReceived;
17
18// This event should be sent when an entity blocks damage with armor.
19#[derive(Event, Debug, Default)]
20struct ArmorBlockedDamage;
21
22// This resource represents a timer used to determine when to deal damage
23// By default it repeats once per second
24#[derive(Resource, Deref, DerefMut)]
25struct DamageTimer(pub Timer);
26
27impl Default for DamageTimer {
28    fn default() -> Self {
29        DamageTimer(Timer::from_seconds(1.0, TimerMode::Repeating))
30    }
31}
32
33// Next we define systems that send, mutate, and receive events
34// This system reads 'DamageTimer', updates it, then sends a 'DealDamage' event
35// if the timer has finished.
36//
37// Events are sent using an 'EventWriter<T>' by calling 'write' or 'write_default'.
38// The 'write_default' method will send the event with the default value if the event
39// has a 'Default' implementation.
40fn deal_damage_over_time(
41    time: Res<Time>,
42    mut state: ResMut<DamageTimer>,
43    mut events: EventWriter<DealDamage>,
44) {
45    if state.tick(time.delta()).finished() {
46        // Events can be sent with 'write' and constructed just like any other object.
47        events.write(DealDamage { amount: 10 });
48    }
49}
50
51// This system mutates the 'DealDamage' events to apply some armor value
52// It also sends an 'ArmorBlockedDamage' event if the value of 'DealDamage' is zero
53//
54// Events are mutated using an 'EventMutator<T>' by calling 'read'. This returns an iterator
55// over all the &mut T that this system has not read yet. Note, you can have multiple
56// 'EventReader', 'EventWriter', and 'EventMutator' in a given system, as long as the types (T) are different.
57fn apply_armor_to_damage(
58    mut dmg_events: EventMutator<DealDamage>,
59    mut armor_events: EventWriter<ArmorBlockedDamage>,
60) {
61    for event in dmg_events.read() {
62        event.amount -= 1;
63        if event.amount <= 0 {
64            // Zero-sized events can also be sent with 'send'
65            armor_events.write(ArmorBlockedDamage);
66        }
67    }
68}
69
70// This system reads 'DealDamage' events and sends 'DamageReceived' if the amount is non-zero
71//
72// Events are read using an 'EventReader<T>' by calling 'read'. This returns an iterator over all the &T
73// that this system has not read yet, and must be 'mut' in order to track which events have been read.
74// Again, note you can have multiple 'EventReader', 'EventWriter', and 'EventMutator' in a given system,
75// as long as the types (T) are different.
76fn apply_damage_to_health(
77    mut dmg_events: EventReader<DealDamage>,
78    mut rcvd_events: EventWriter<DamageReceived>,
79) {
80    for event in dmg_events.read() {
81        info!("Applying {} damage", event.amount);
82        if event.amount > 0 {
83            // Events with a 'Default' implementation can be written with 'write_default'
84            rcvd_events.write_default();
85        }
86    }
87}
88
89// Finally these two systems read 'DamageReceived' events.
90//
91// The first system will play a sound.
92// The second system will spawn a particle effect.
93//
94// As before, events are read using an 'EventReader' by calling 'read'. This returns an iterator over all the &T
95// that this system has not read yet.
96fn play_damage_received_sound(mut dmg_events: EventReader<DamageReceived>) {
97    for _ in dmg_events.read() {
98        info!("Playing a sound.");
99    }
100}
101
102// Note that both systems receive the same 'DamageReceived' events. Any number of systems can
103// receive the same event type.
104fn play_damage_received_particle_effect(mut dmg_events: EventReader<DamageReceived>) {
105    for _ in dmg_events.read() {
106        info!("Playing particle effect.");
107    }
108}
109
110fn main() {
111    App::new()
112        .add_plugins(DefaultPlugins)
113        // Events must be added to the app before they can be used
114        // using the 'add_event' method
115        .add_event::<DealDamage>()
116        .add_event::<ArmorBlockedDamage>()
117        .add_event::<DamageReceived>()
118        .init_resource::<DamageTimer>()
119        // As always we must add our systems to the apps schedule.
120        // Here we add our systems to the schedule using 'chain()' so that they run in order
121        // This ensures that 'apply_armor_to_damage' runs before 'apply_damage_to_health'
122        // It also ensures that 'EventWriters' are used before the associated 'EventReaders'
123        .add_systems(
124            Update,
125            (
126                deal_damage_over_time,
127                apply_armor_to_damage,
128                apply_damage_to_health,
129            )
130                .chain(),
131        )
132        // These two systems are not guaranteed to run in order, nor are they guaranteed to run
133        // after the above chain. They may even run in parallel with each other.
134        // This means they may have a one frame delay in processing events compared to the above chain
135        // In some instances this is fine. In other cases it can be an issue. See the docs for more information
136        .add_systems(
137            Update,
138            (
139                play_damage_received_sound,
140                play_damage_received_particle_effect,
141            ),
142        )
143        .run();
144}