//! This example shows how to send, mutate, and receive, messages. It also demonstrates1//! how to control system ordering so that messages are processed in a specific order.2//! It does this by simulating a damage over time effect that you might find in a game.34use bevy::prelude::*;56// In order to send or receive messages first you must define them7// This message should be sent when something attempts to deal damage to another entity.8#[derive(Message, Debug)]9struct DealDamage {10pub amount: i32,11}1213// This message should be sent when an entity receives damage.14#[derive(Message, Debug, Default)]15struct DamageReceived;1617// This message should be sent when an entity blocks damage with armor.18#[derive(Message, Debug, Default)]19struct ArmorBlockedDamage;2021// This resource represents a timer used to determine when to deal damage22// By default it repeats once per second23#[derive(Resource, Deref, DerefMut)]24struct DamageTimer(pub Timer);2526impl Default for DamageTimer {27fn default() -> Self {28DamageTimer(Timer::from_seconds(1.0, TimerMode::Repeating))29}30}3132// Next we define systems that send, mutate, and receive messages33// This system reads 'DamageTimer', updates it, then sends a 'DealDamage' message34// if the timer has finished.35//36// Messages are sent using an 'MessageWriter<T>' by calling 'write' or 'write_default'.37// The 'write_default' method will send the message with the default value if the message38// has a 'Default' implementation.39fn deal_damage_over_time(40time: Res<Time>,41mut state: ResMut<DamageTimer>,42mut deal_damage_writer: MessageWriter<DealDamage>,43) {44if state.tick(time.delta()).is_finished() {45// Messages can be sent with 'write' and constructed just like any other object.46deal_damage_writer.write(DealDamage { amount: 10 });47}48}4950// This system mutates the 'DealDamage' messages to apply some armor value51// It also sends an 'ArmorBlockedDamage' message if the value of 'DealDamage' is zero52//53// Messages are mutated using an 'MessageMutator<T>' by calling 'read'. This returns an iterator54// over all the &mut T that this system has not read yet. Note, you can have multiple55// 'MessageReader', 'MessageWriter', and 'MessageMutator' in a given system, as long as the types (T) are different.56fn apply_armor_to_damage(57mut dmg_messages: MessageMutator<DealDamage>,58mut armor_messages: MessageWriter<ArmorBlockedDamage>,59) {60for message in dmg_messages.read() {61message.amount -= 1;62if message.amount <= 0 {63// Zero-sized messages can also be sent with 'send'64armor_messages.write(ArmorBlockedDamage);65}66}67}6869// This system reads 'DealDamage' messages and sends 'DamageReceived' if the amount is non-zero70//71// Messages are read using an 'MessageReader<T>' by calling 'read'. This returns an iterator over all the &T72// that this system has not read yet, and must be 'mut' in order to track which messages have been read.73// Again, note you can have multiple 'MessageReader', 'MessageWriter', and 'MessageMutator' in a given system,74// as long as the types (T) are different.75fn apply_damage_to_health(76mut deal_damage_reader: MessageReader<DealDamage>,77mut damaged_received_writer: MessageWriter<DamageReceived>,78) {79for deal_damage in deal_damage_reader.read() {80info!("Applying {} damage", deal_damage.amount);81if deal_damage.amount > 0 {82// Messages with a 'Default' implementation can be written with 'write_default'83damaged_received_writer.write_default();84}85}86}8788// Finally these two systems read 'DamageReceived' messages.89//90// The first system will play a sound.91// The second system will spawn a particle effect.92//93// As before, messages are read using an 'MessageReader' by calling 'read'. This returns an iterator over all the &T94// that this system has not read yet.95fn play_damage_received_sound(mut damage_received_reader: MessageReader<DamageReceived>) {96for _ in damage_received_reader.read() {97info!("Playing a sound.");98}99}100101// Note that both systems receive the same 'DamageReceived' messages. Any number of systems can102// receive the same message type.103fn play_damage_received_particle_effect(mut damage_received_reader: MessageReader<DamageReceived>) {104for _ in damage_received_reader.read() {105info!("Playing particle effect.");106}107}108109fn main() {110App::new()111.add_plugins(DefaultPlugins)112// Messages must be added to the app before they can be used113// using the 'add_message' method114.add_message::<DealDamage>()115.add_message::<ArmorBlockedDamage>()116.add_message::<DamageReceived>()117.init_resource::<DamageTimer>()118// As always we must add our systems to the apps schedule.119// Here we add our systems to the schedule using 'chain()' so that they run in order120// This ensures that 'apply_armor_to_damage' runs before 'apply_damage_to_health'121// It also ensures that 'MessageWriters' are used before the associated 'MessageReaders'122.add_systems(123Update,124(125deal_damage_over_time,126apply_armor_to_damage,127apply_damage_to_health,128)129.chain(),130)131// These two systems are not guaranteed to run in order, nor are they guaranteed to run132// after the above chain. They may even run in parallel with each other.133// This means they may have a one frame delay in processing messages compared to the above chain134// In some instances this is fine. In other cases it can be an issue. See the docs for more information135.add_systems(136Update,137(138play_damage_received_sound,139play_damage_received_particle_effect,140),141)142.run();143}144145146