Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/ecs/observer_propagation.rs
6849 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
// - **propagate:**
49
// Enables the default propagation behavior ("bubbling" up from child to parent using the ChildOf component).
50
//
51
// - **auto_propagate:**
52
// We can also choose whether or not this event will propagate by default when triggered. If this is
53
// false, it will only propagate following a call to `On::propagate(true)`.
54
#[derive(Clone, Component, EntityEvent)]
55
#[entity_event(propagate, auto_propagate)]
56
struct Attack {
57
entity: Entity,
58
damage: u16,
59
}
60
61
/// An entity that can take damage.
62
#[derive(Component, Deref, DerefMut)]
63
struct HitPoints(u16);
64
65
/// For damage to reach the wearer, it must exceed the armor.
66
#[derive(Component, Deref)]
67
struct Armor(u16);
68
69
/// A normal bevy system that attacks a piece of the goblin's armor on a timer.
70
fn attack_armor(entities: Query<Entity, With<Armor>>, mut commands: Commands) {
71
let mut rng = rng();
72
if let Some(entity) = entities.iter().choose(&mut rng) {
73
let damage = rng.random_range(1..20);
74
commands.trigger(Attack { damage, entity });
75
info!("⚔️ Attack for {} damage", damage);
76
}
77
}
78
79
fn attack_hits(attack: On<Attack>, name: Query<&Name>) {
80
if let Ok(name) = name.get(attack.entity) {
81
info!("Attack hit {}", name);
82
}
83
}
84
85
/// A callback placed on [`Armor`], checking if it absorbed all the [`Attack`] damage.
86
fn block_attack(mut attack: On<Attack>, armor: Query<(&Armor, &Name)>) {
87
let (armor, name) = armor.get(attack.entity).unwrap();
88
let damage = attack.damage.saturating_sub(**armor);
89
if damage > 0 {
90
info!("🩸 {} damage passed through {}", damage, name);
91
// The attack isn't stopped by the armor. We reduce the damage of the attack, and allow
92
// it to continue on to the goblin.
93
attack.damage = damage;
94
} else {
95
info!("🛡️ {} damage blocked by {}", attack.damage, name);
96
// Armor stopped the attack, the event stops here.
97
attack.propagate(false);
98
info!("(propagation halted early)\n");
99
}
100
}
101
102
/// A callback on the armor wearer, triggered when a piece of armor is not able to block an attack,
103
/// or the wearer is attacked directly.
104
fn take_damage(
105
attack: On<Attack>,
106
mut hp: Query<(&mut HitPoints, &Name)>,
107
mut commands: Commands,
108
mut app_exit: MessageWriter<AppExit>,
109
) {
110
let (mut hp, name) = hp.get_mut(attack.entity).unwrap();
111
**hp = hp.saturating_sub(attack.damage);
112
113
if **hp > 0 {
114
info!("{} has {:.1} HP", name, hp.0);
115
} else {
116
warn!("💀 {} has died a gruesome death", name);
117
commands.entity(attack.entity).despawn();
118
app_exit.write(AppExit::Success);
119
}
120
121
info!("(propagation reached root)\n");
122
}
123
124