Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/ecs/relationships.rs
6592 views
1
//! Entities generally don't exist in isolation. Instead, they are related to other entities in various ways.
2
//! While Bevy comes with a built-in [`ChildOf`]/[`Children`] relationship
3
//! (which enables transform and visibility propagation),
4
//! you can define your own relationships using components.
5
//!
6
//! We can define a custom relationship by creating two components:
7
//! one to store the relationship itself, and another to keep track of the reverse relationship.
8
//! Bevy's [`ChildOf`] component implements the [`Relationship`] trait, serving as the source of truth,
9
//! while the [`Children`] component implements the [`RelationshipTarget`] trait and is used to accelerate traversals down the hierarchy.
10
//!
11
//! In this example we're creating a [`Targeting`]/[`TargetedBy`] relationship,
12
//! demonstrating how you might model units which target a single unit in combat.
13
14
use bevy::ecs::entity::EntityHashSet;
15
use bevy::ecs::system::RunSystemOnce;
16
use bevy::prelude::*;
17
18
/// The entity that this entity is targeting.
19
///
20
/// This is the source of truth for the relationship,
21
/// and can be modified directly to change the target.
22
#[derive(Component, Debug)]
23
#[relationship(relationship_target = TargetedBy)]
24
struct Targeting(Entity);
25
26
/// All entities that are targeting this entity.
27
///
28
/// This component is updated reactively using the component hooks introduced by deriving
29
/// the [`Relationship`] trait. We should not modify this component directly,
30
/// but can safely read its field. In a larger project, we could enforce this through the use of
31
/// private fields and public getters.
32
#[derive(Component, Debug)]
33
#[relationship_target(relationship = Targeting)]
34
struct TargetedBy(Vec<Entity>);
35
36
fn main() {
37
// Operating on a raw `World` and running systems one at a time
38
// is great for writing tests and teaching abstract concepts!
39
let mut world = World::new();
40
41
// We're going to spawn a few entities and relate them to each other in a complex way.
42
// To start, Bob will target Alice, Charlie will target Bob,
43
// and Alice will target Charlie. This creates a loop in the relationship graph.
44
//
45
// Then, we'll spawn Devon, who will target Charlie,
46
// creating a more complex graph with a branching structure.
47
fn spawning_entities_with_relationships(mut commands: Commands) {
48
// Calling .id() after spawning an entity will return the `Entity` identifier of the spawned entity,
49
// even though the entity itself is not yet instantiated in the world.
50
// This works because Commands will reserve the entity ID before actually spawning the entity,
51
// through the use of atomic counters.
52
let alice = commands.spawn(Name::new("Alice")).id();
53
// Relations are just components, so we can add them into the bundle that we're spawning.
54
let bob = commands.spawn((Name::new("Bob"), Targeting(alice))).id();
55
56
// The `with_related` and `with_related_entities` helper methods on `EntityCommands` can be used to add relations in a more ergonomic way.
57
let charlie = commands
58
.spawn((Name::new("Charlie"), Targeting(bob)))
59
// The `with_related` method will spawn a bundle with `Targeting` relationship
60
.with_related::<Targeting>(Name::new("James"))
61
// The `with_related_entities` method will automatically add the `Targeting` component to any entities spawned within the closure,
62
// targeting the entity that we're calling `with_related` on.
63
.with_related_entities::<Targeting>(|related_spawner_commands| {
64
// We could spawn multiple entities here, and they would all target `charlie`.
65
related_spawner_commands.spawn(Name::new("Devon"));
66
})
67
.id();
68
69
// Simply inserting the `Targeting` component will automatically create and update the `TargetedBy` component on the target entity.
70
// We can do this at any point; not just when the entity is spawned.
71
commands.entity(alice).insert(Targeting(charlie));
72
}
73
74
world
75
.run_system_once(spawning_entities_with_relationships)
76
.unwrap();
77
78
fn debug_relationships(
79
// Not all of our entities are targeted by something, so we use `Option` in our query to handle this case.
80
relations_query: Query<(&Name, &Targeting, Option<&TargetedBy>)>,
81
name_query: Query<&Name>,
82
) {
83
let mut relationships = String::new();
84
85
for (name, targeting, maybe_targeted_by) in relations_query.iter() {
86
let targeting_name = name_query.get(targeting.0).unwrap();
87
let targeted_by_string = if let Some(targeted_by) = maybe_targeted_by {
88
let mut vec_of_names = Vec::<&Name>::new();
89
90
for entity in targeted_by.iter() {
91
let name = name_query.get(entity).unwrap();
92
vec_of_names.push(name);
93
}
94
95
// Convert this to a nice string for printing.
96
let vec_of_str: Vec<&str> = vec_of_names.iter().map(|name| name.as_str()).collect();
97
vec_of_str.join(", ")
98
} else {
99
"nobody".to_string()
100
};
101
102
relationships.push_str(&format!(
103
"{name} is targeting {targeting_name}, and is targeted by {targeted_by_string}\n",
104
));
105
}
106
107
println!("{relationships}");
108
}
109
110
world.run_system_once(debug_relationships).unwrap();
111
112
// Demonstrates how to correctly mutate relationships.
113
// Relationship components are immutable! We can't query for the `Targeting` component mutably and modify it directly,
114
// but we can insert a new `Targeting` component to replace the old one.
115
// This allows the hooks on the `Targeting` component to update the `TargetedBy` component correctly.
116
// The `TargetedBy` component will be updated automatically!
117
fn mutate_relationships(name_query: Query<(Entity, &Name)>, mut commands: Commands) {
118
// Let's find Devon by doing a linear scan of the entity names.
119
let devon = name_query
120
.iter()
121
.find(|(_entity, name)| name.as_str() == "Devon")
122
.unwrap()
123
.0;
124
125
let alice = name_query
126
.iter()
127
.find(|(_entity, name)| name.as_str() == "Alice")
128
.unwrap()
129
.0;
130
131
println!("Making Devon target Alice.\n");
132
commands.entity(devon).insert(Targeting(alice));
133
}
134
135
world.run_system_once(mutate_relationships).unwrap();
136
world.run_system_once(debug_relationships).unwrap();
137
138
// Systems can return errors,
139
// which can be used to signal that something went wrong during the system's execution.
140
#[derive(Debug)]
141
#[expect(
142
dead_code,
143
reason = "Rust considers types that are only used by their debug trait as dead code."
144
)]
145
struct TargetingCycle {
146
initial_entity: Entity,
147
visited: EntityHashSet,
148
}
149
150
/// Bevy's relationships come with all sorts of useful methods for traversal.
151
/// Here, we're going to look for cycles using a depth-first search.
152
fn check_for_cycles(
153
// We want to check every entity for cycles
154
query_to_check: Query<Entity, With<Targeting>>,
155
// Fetch the names for easier debugging.
156
name_query: Query<&Name>,
157
// The targeting_query allows us to traverse the relationship graph.
158
targeting_query: Query<&Targeting>,
159
) -> Result<(), TargetingCycle> {
160
for initial_entity in query_to_check.iter() {
161
let mut visited = EntityHashSet::new();
162
let mut targeting_name = name_query.get(initial_entity).unwrap().clone();
163
println!("Checking for cycles starting at {targeting_name}",);
164
165
// There's all sorts of methods like this; check the `Query` docs for more!
166
// This would also be easy to do by just manually checking the `Targeting` component,
167
// and calling `query.get(targeted_entity)` on the entity that it targets in a loop.
168
for targeting in targeting_query.iter_ancestors(initial_entity) {
169
let target_name = name_query.get(targeting).unwrap();
170
println!("{targeting_name} is targeting {target_name}",);
171
targeting_name = target_name.clone();
172
173
if !visited.insert(targeting) {
174
return Err(TargetingCycle {
175
initial_entity,
176
visited,
177
});
178
}
179
}
180
}
181
182
// If we've checked all the entities and haven't found a cycle, we're good!
183
Ok(())
184
}
185
186
// Calling `world.run_system_once` on systems which return Results gives us two layers of errors:
187
// the first checks if running the system failed, and the second checks if the system itself returned an error.
188
// We're unwrapping the first, but checking the output of the system itself.
189
let cycle_result = world.run_system_once(check_for_cycles).unwrap();
190
println!("{cycle_result:?} \n");
191
// We deliberately introduced a cycle during spawning!
192
assert!(cycle_result.is_err());
193
194
// Now, let's demonstrate removing relationships and break the cycle.
195
fn untarget(mut commands: Commands, name_query: Query<(Entity, &Name)>) {
196
// Let's find Charlie by doing a linear scan of the entity names.
197
let charlie = name_query
198
.iter()
199
.find(|(_entity, name)| name.as_str() == "Charlie")
200
.unwrap()
201
.0;
202
203
// We can remove the `Targeting` component to remove the relationship
204
// and break the cycle we saw earlier.
205
println!("Removing Charlie's targeting relationship.\n");
206
commands.entity(charlie).remove::<Targeting>();
207
}
208
209
world.run_system_once(untarget).unwrap();
210
world.run_system_once(debug_relationships).unwrap();
211
// Cycle free!
212
let cycle_result = world.run_system_once(check_for_cycles).unwrap();
213
println!("{cycle_result:?} \n");
214
assert!(cycle_result.is_ok());
215
}
216
217