docs/bevy/examples/ecs/message.rs 6.6 K · 166 lines · raw · history
| 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 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. |
| 56 | fn 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. |
| 78 | fn 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. |
| 97 | fn 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. |
| 117 | fn 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. |
| 125 | fn 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 | |
| 131 | fn 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 | } |