Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/ecs/observer_propagation.rs
6592 views
1
//! Demonstrates how to propagate events through the hierarchy with observers.
2
3
use std::time::Duration;
4
5
use bevy::{log::LogPlugin, prelude::*, time::common_conditions::on_timer};
6
use rand::{rng, seq::IteratorRandom, Rng};
7
8
fn 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 a global observer that will emit a line whenever an attack hits an entity.
17
.add_observer(attack_hits)
18
.run();
19
}
20
21
// In this example, we spawn a goblin wearing different pieces of armor. Each piece of armor
22
// is represented as a child entity, with an `Armor` component.
23
//
24
// We're going to model how attack damage can be partially blocked by the goblin's armor using
25
// event bubbling. Our events will target the armor, and if the armor isn't strong enough to block
26
// the attack it will continue up and hit the goblin.
27
fn 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
// This event represents an attack we want to "bubble" up from the armor to the goblin.
45
//
46
// We enable propagation by adding the event attribute and specifying two important pieces of information.
47
//
48
// - **traversal:**
49
// Which component we want to propagate along. In this case, we want to "bubble" (meaning propagate
50
// from child to parent) so we use the `ChildOf` component for propagation. The component supplied
51
// must implement the `Traversal` trait.
52
//
53
// - **auto_propagate:**
54
// We can also choose whether or not this event will propagate by default when triggered. If this is
55
// false, it will only propagate following a call to `On::propagate(true)`.
56
#[derive(Clone, Component, EntityEvent)]
57
#[entity_event(traversal = &'static ChildOf, auto_propagate)]
58
struct Attack {
59
damage: u16,
60
}
61
62
/// An entity that can take damage.
63
#[derive(Component, Deref, DerefMut)]
64
struct HitPoints(u16);
65
66
/// For damage to reach the wearer, it must exceed the armor.
67
#[derive(Component, Deref)]
68
struct Armor(u16);
69
70
/// A normal bevy system that attacks a piece of the goblin's armor on a timer.
71
fn attack_armor(entities: Query<Entity, With<Armor>>, mut commands: Commands) {
72
let mut rng = rng();
73
if let Some(target) = entities.iter().choose(&mut rng) {
74
let damage = rng.random_range(1..20);
75
commands.trigger_targets(Attack { damage }, target);
76
info!("⚔️ Attack for {} damage", damage);
77
}
78
}
79
80
fn attack_hits(event: On<Attack>, name: Query<&Name>) {
81
if let Ok(name) = name.get(event.entity()) {
82
info!("Attack hit {}", name);
83
}
84
}
85
86
/// A callback placed on [`Armor`], checking if it absorbed all the [`Attack`] damage.
87
fn block_attack(mut event: On<Attack>, armor: Query<(&Armor, &Name)>) {
88
let (armor, name) = armor.get(event.entity()).unwrap();
89
let attack = event.event_mut();
90
let damage = attack.damage.saturating_sub(**armor);
91
if damage > 0 {
92
info!("🩸 {} damage passed through {}", damage, name);
93
// The attack isn't stopped by the armor. We reduce the damage of the attack, and allow
94
// it to continue on to the goblin.
95
attack.damage = damage;
96
} else {
97
info!("🛡️ {} damage blocked by {}", attack.damage, name);
98
// Armor stopped the attack, the event stops here.
99
event.propagate(false);
100
info!("(propagation halted early)\n");
101
}
102
}
103
104
/// A callback on the armor wearer, triggered when a piece of armor is not able to block an attack,
105
/// or the wearer is attacked directly.
106
fn take_damage(
107
event: On<Attack>,
108
mut hp: Query<(&mut HitPoints, &Name)>,
109
mut commands: Commands,
110
mut app_exit: EventWriter<AppExit>,
111
) {
112
let attack = event.event();
113
let (mut hp, name) = hp.get_mut(event.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(event.entity()).despawn();
121
app_exit.write(AppExit::Success);
122
}
123
124
info!("(propagation reached root)\n");
125
}
126
127