Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/ecs/message.rs
6849 views
1
//! This example shows how to send, mutate, and receive, messages. It also demonstrates
2
//! how to control system ordering so that messages 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 messages first you must define them
8
// This message should be sent when something attempts to deal damage to another entity.
9
#[derive(Message, Debug)]
10
struct DealDamage {
11
pub amount: i32,
12
}
13
14
// This message should be sent when an entity receives damage.
15
#[derive(Message, Debug, Default)]
16
struct DamageReceived;
17
18
// This message should be sent when an entity blocks damage with armor.
19
#[derive(Message, 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 messages
34
// This system reads 'DamageTimer', updates it, then sends a 'DealDamage' message
35
// if the timer has finished.
36
//
37
// Messages are sent using an 'MessageWriter<T>' by calling 'write' or 'write_default'.
38
// The 'write_default' method will send the message with the default value if the message
39
// has a 'Default' implementation.
40
fn deal_damage_over_time(
41
time: Res<Time>,
42
mut state: ResMut<DamageTimer>,
43
mut deal_damage_writer: MessageWriter<DealDamage>,
44
) {
45
if state.tick(time.delta()).is_finished() {
46
// Messages can be sent with 'write' and constructed just like any other object.
47
deal_damage_writer.write(DealDamage { amount: 10 });
48
}
49
}
50
51
// This system mutates the 'DealDamage' messages to apply some armor value
52
// It also sends an 'ArmorBlockedDamage' message if the value of 'DealDamage' is zero
53
//
54
// Messages are mutated using an 'MessageMutator<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
// 'MessageReader', 'MessageWriter', and 'MessageMutator' in a given system, as long as the types (T) are different.
57
fn apply_armor_to_damage(
58
mut dmg_messages: MessageMutator<DealDamage>,
59
mut armor_messages: MessageWriter<ArmorBlockedDamage>,
60
) {
61
for message in dmg_messages.read() {
62
message.amount -= 1;
63
if message.amount <= 0 {
64
// Zero-sized messages can also be sent with 'send'
65
armor_messages.write(ArmorBlockedDamage);
66
}
67
}
68
}
69
70
// This system reads 'DealDamage' messages and sends 'DamageReceived' if the amount is non-zero
71
//
72
// Messages are read using an 'MessageReader<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 messages have been read.
74
// Again, note you can have multiple 'MessageReader', 'MessageWriter', and 'MessageMutator' in a given system,
75
// as long as the types (T) are different.
76
fn apply_damage_to_health(
77
mut deal_damage_reader: MessageReader<DealDamage>,
78
mut damaged_received_writer: MessageWriter<DamageReceived>,
79
) {
80
for deal_damage in deal_damage_reader.read() {
81
info!("Applying {} damage", deal_damage.amount);
82
if deal_damage.amount > 0 {
83
// Messages with a 'Default' implementation can be written with 'write_default'
84
damaged_received_writer.write_default();
85
}
86
}
87
}
88
89
// Finally these two systems read 'DamageReceived' messages.
90
//
91
// The first system will play a sound.
92
// The second system will spawn a particle effect.
93
//
94
// As before, messages are read using an 'MessageReader' 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 damage_received_reader: MessageReader<DamageReceived>) {
97
for _ in damage_received_reader.read() {
98
info!("Playing a sound.");
99
}
100
}
101
102
// Note that both systems receive the same 'DamageReceived' messages. Any number of systems can
103
// receive the same message type.
104
fn play_damage_received_particle_effect(mut damage_received_reader: MessageReader<DamageReceived>) {
105
for _ in damage_received_reader.read() {
106
info!("Playing particle effect.");
107
}
108
}
109
110
fn main() {
111
App::new()
112
.add_plugins(DefaultPlugins)
113
// Messages must be added to the app before they can be used
114
// using the 'add_message' method
115
.add_message::<DealDamage>()
116
.add_message::<ArmorBlockedDamage>()
117
.add_message::<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 'MessageWriters' are used before the associated 'MessageReaders'
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 messages 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