Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/ecs/event.rs
6592 views
1
//! This example shows how to send, mutate, and receive, events. It also demonstrates
2
//! how to 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
5
use 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(BufferedEvent, Debug)]
10
struct DealDamage {
11
pub amount: i32,
12
}
13
14
// This event should be sent when an entity receives damage.
15
#[derive(BufferedEvent, Debug, Default)]
16
struct DamageReceived;
17
18
// This event should be sent when an entity blocks damage with armor.
19
#[derive(BufferedEvent, Debug, Default)]
20
struct 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)]
25
struct DamageTimer(pub Timer);
26
27
impl 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.
40
fn 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()).is_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.
57
fn 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.
76
fn 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.
96
fn 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.
104
fn play_damage_received_particle_effect(mut dmg_events: EventReader<DamageReceived>) {
105
for _ in dmg_events.read() {
106
info!("Playing particle effect.");
107
}
108
}
109
110
fn 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
}
145
146