observer_propagation/
observer_propagation.rs1use std::time::Duration;
4
5use bevy::{log::LogPlugin, prelude::*, time::common_conditions::on_timer};
6use rand::{seq::IteratorRandom, thread_rng, Rng};
7
8fn main() {
9 App::new()
10 .add_plugins((MinimalPlugins, LogPlugin::default()))
11 .add_systems(Startup, setup)
12 .add_systems(
13 Update,
14 attack_armor.run_if(on_timer(Duration::from_millis(200))),
15 )
16 .add_observer(attack_hits)
18 .run();
19}
20
21fn setup(mut commands: Commands) {
28 commands
29 .spawn((Name::new("Goblin"), HitPoints(50)))
30 .observe(take_damage)
31 .with_children(|parent| {
32 parent
33 .spawn((Name::new("Helmet"), Armor(5)))
34 .observe(block_attack);
35 parent
36 .spawn((Name::new("Socks"), Armor(10)))
37 .observe(block_attack);
38 parent
39 .spawn((Name::new("Shirt"), Armor(15)))
40 .observe(block_attack);
41 });
42}
43
44#[derive(Clone, Component)]
46struct Attack {
47 damage: u16,
48}
49
50impl Event for Attack {
53 type Traversal = &'static Parent;
57 const AUTO_PROPAGATE: bool = true;
60}
61
62#[derive(Component, Deref, DerefMut)]
64struct HitPoints(u16);
65
66#[derive(Component, Deref)]
68struct Armor(u16);
69
70fn attack_armor(entities: Query<Entity, With<Armor>>, mut commands: Commands) {
72 let mut rng = thread_rng();
73 if let Some(target) = entities.iter().choose(&mut rng) {
74 let damage = rng.gen_range(1..20);
75 commands.trigger_targets(Attack { damage }, target);
76 info!("⚔️ Attack for {} damage", damage);
77 }
78}
79
80fn attack_hits(trigger: Trigger<Attack>, name: Query<&Name>) {
81 if let Ok(name) = name.get(trigger.entity()) {
82 info!("Attack hit {}", name);
83 }
84}
85
86fn block_attack(mut trigger: Trigger<Attack>, armor: Query<(&Armor, &Name)>) {
88 let (armor, name) = armor.get(trigger.entity()).unwrap();
89 let attack = trigger.event_mut();
90 let damage = attack.damage.saturating_sub(**armor);
91 if damage > 0 {
92 info!("🩸 {} damage passed through {}", damage, name);
93 attack.damage = damage;
96 } else {
97 info!("🛡️ {} damage blocked by {}", attack.damage, name);
98 trigger.propagate(false);
100 info!("(propagation halted early)\n");
101 }
102}
103
104fn take_damage(
107 trigger: Trigger<Attack>,
108 mut hp: Query<(&mut HitPoints, &Name)>,
109 mut commands: Commands,
110 mut app_exit: EventWriter<AppExit>,
111) {
112 let attack = trigger.event();
113 let (mut hp, name) = hp.get_mut(trigger.entity()).unwrap();
114 **hp = hp.saturating_sub(attack.damage);
115
116 if **hp > 0 {
117 info!("{} has {:.1} HP", name, hp.0);
118 } else {
119 warn!("💀 {} has died a gruesome death", name);
120 commands.entity(trigger.entity()).despawn_recursive();
121 app_exit.send(AppExit::Success);
122 }
123
124 info!("(propagation reached root)\n");
125}