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}