relationships/relationships.rs
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
14use bevy::ecs::entity::EntityHashSet;
15use bevy::ecs::system::RunSystemOnce;
16use 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)]
24struct 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)]
34struct TargetedBy(Vec<Entity>);
35
36fn 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}