message/message.rs
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
5use 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)]
10struct DealDamage {
11 pub amount: i32,
12}
13
14// This message should be sent when an entity receives damage.
15#[derive(Message, Debug, Default)]
16struct DamageReceived;
17
18// This message should be sent when an entity blocks damage with armor.
19#[derive(Message, 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 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.
40fn 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 both reads and writes `DealDamage` messages.
52//
53// Trying to do this with a `MessageReader` and a `MessageWriter` will fail with conflicts,
54// since they both use the same underlying resource.
55// Instead, you may use a `MessageMutator`, which allows both reading and writing.
56fn apply_extra_damage(mut dmg_messages: MessageMutator<DealDamage>) {
57 let mut extra = Vec::new();
58 for message in dmg_messages.read() {
59 if message.amount >= 10 {
60 extra.push(DealDamage {
61 amount: message.amount / 10,
62 });
63 }
64 }
65 // Note that this system will read messages it wrote itself!
66 // These are written after the `read()`, so they will be read the next time it runs.
67 for message in extra {
68 dmg_messages.write(message);
69 }
70}
71
72// This system mutates the 'DealDamage' messages to apply some armor value
73// It also sends an 'ArmorBlockedDamage' message if the value of 'DealDamage' is zero
74//
75// Messages are mutated using an 'MessageMutator<T>' by calling 'read'. This returns an iterator
76// over all the &mut T that this system has not read yet. Note, you can have multiple
77// 'MessageReader', 'MessageWriter', and 'MessageMutator' in a given system, as long as the types (T) are different.
78fn apply_armor_to_damage(
79 mut dmg_messages: MessageMutator<DealDamage>,
80 mut armor_messages: MessageWriter<ArmorBlockedDamage>,
81) {
82 for message in dmg_messages.read() {
83 message.amount -= 1;
84 if message.amount <= 0 {
85 // Zero-sized messages can also be sent with 'send'
86 armor_messages.write(ArmorBlockedDamage);
87 }
88 }
89}
90
91// This system reads 'DealDamage' messages and sends 'DamageReceived' if the amount is non-zero
92//
93// Messages are read using an 'MessageReader<T>' by calling 'read'. This returns an iterator over all the &T
94// that this system has not read yet, and must be 'mut' in order to track which messages have been read.
95// Again, note you can have multiple 'MessageReader', 'MessageWriter', and 'MessageMutator' in a given system,
96// as long as the types (T) are different.
97fn apply_damage_to_health(
98 mut deal_damage_reader: MessageReader<DealDamage>,
99 mut damaged_received_writer: MessageWriter<DamageReceived>,
100) {
101 for deal_damage in deal_damage_reader.read() {
102 info!("Applying {} damage", deal_damage.amount);
103 if deal_damage.amount > 0 {
104 // Messages with a 'Default' implementation can be written with 'write_default'
105 damaged_received_writer.write_default();
106 }
107 }
108}
109
110// Finally these two systems read 'DamageReceived' messages.
111//
112// The first system will play a sound.
113// The second system will spawn a particle effect.
114//
115// As before, messages are read using an 'MessageReader' by calling 'read'. This returns an iterator over all the &T
116// that this system has not read yet.
117fn play_damage_received_sound(mut damage_received_reader: MessageReader<DamageReceived>) {
118 for _ in damage_received_reader.read() {
119 info!("Playing a sound.");
120 }
121}
122
123// Note that both systems receive the same 'DamageReceived' messages. Any number of systems can
124// receive the same message type.
125fn play_damage_received_particle_effect(mut damage_received_reader: MessageReader<DamageReceived>) {
126 for _ in damage_received_reader.read() {
127 info!("Playing particle effect.");
128 }
129}
130
131fn main() {
132 App::new()
133 .add_plugins(DefaultPlugins)
134 // Messages must be added to the app before they can be used
135 // using the 'add_message' method
136 .add_message::<DealDamage>()
137 .add_message::<ArmorBlockedDamage>()
138 .add_message::<DamageReceived>()
139 .init_resource::<DamageTimer>()
140 // As always we must add our systems to the apps schedule.
141 // Here we add our systems to the schedule using 'chain()' so that they run in order
142 // This ensures that 'apply_armor_to_damage' runs before 'apply_damage_to_health'
143 // It also ensures that 'MessageWriters' are used before the associated 'MessageReaders'
144 .add_systems(
145 Update,
146 (
147 deal_damage_over_time,
148 apply_extra_damage,
149 apply_armor_to_damage,
150 apply_damage_to_health,
151 )
152 .chain(),
153 )
154 // These two systems are not guaranteed to run in order, nor are they guaranteed to run
155 // after the above chain. They may even run in parallel with each other.
156 // This means they may have a one frame delay in processing messages compared to the above chain
157 // In some instances this is fine. In other cases it can be an issue. See the docs for more information
158 .add_systems(
159 Update,
160 (
161 play_damage_received_sound,
162 play_damage_received_particle_effect,
163 ),
164 )
165 .run();
166}