Skip to main content

entity_graph/
entity_graph.rs

1//! Entity graph example demonstrating non-tree relationships.
2//!
3//! This example shows how to:
4//! - Model inventory systems with entity references
5//! - Handle optional relationships (targets, equipped items)
6//! - Navigate complex entity graphs
7//! - Use follow_opt for optional references
8//! - Use `WorldExt` to create `EntityPtr` without unsafe blocks
9//!
10//! Run with: `cargo run --example entity_graph`
11
12use bevy_ecs::prelude::*;
13use bevy_entity_ptr::{EntityHandle, EntityPtr, WorldExt};
14
15// Character components
16
17#[derive(Component)]
18struct Name(&'static str);
19
20#[derive(Component)]
21struct Health(i32);
22
23#[derive(Component)]
24struct Target(Option<EntityHandle>);
25
26#[derive(Component)]
27struct Inventory(Vec<EntityHandle>);
28
29#[derive(Component)]
30struct Equipped {
31    weapon: Option<EntityHandle>,
32    armor: Option<EntityHandle>,
33}
34
35// Item components
36
37#[derive(Component)]
38struct Weight(f32);
39
40#[derive(Component)]
41struct Damage(i32);
42
43#[derive(Component)]
44struct Defense(i32);
45
46// Calculate total inventory weight
47fn total_weight(character: EntityPtr) -> f32 {
48    character
49        .get::<Inventory>()
50        .map(|inv| {
51            inv.0
52                .iter()
53                .filter_map(|h| character.follow_handle(*h).get::<Weight>())
54                .map(|w| w.0)
55                .sum()
56        })
57        .unwrap_or(0.0)
58}
59
60// Get equipped weapon damage (if any)
61fn equipped_damage(character: EntityPtr) -> Option<i32> {
62    character
63        .get::<Equipped>()
64        .and_then(|e| e.weapon)
65        .and_then(|h| character.follow_handle(h).get::<Damage>())
66        .map(|d| d.0)
67}
68
69// Get equipped armor defense (if any)
70fn equipped_defense(character: EntityPtr) -> Option<i32> {
71    character
72        .get::<Equipped>()
73        .and_then(|e| e.armor)
74        .and_then(|h| character.follow_handle(h).get::<Defense>())
75        .map(|d| d.0)
76}
77
78// Get target's name and health (if targeting someone)
79fn target_info(character: EntityPtr) -> Option<(&'static str, i32)> {
80    character
81        .follow_opt::<Target, _>(|t| t.0)
82        .and_then(|target| {
83            let name = target.get::<Name>()?.0;
84            let health = target.get::<Health>()?.0;
85            Some((name, health))
86        })
87}
88
89// Find all characters targeting a specific entity
90fn find_attackers(world: &World, target_entity: Entity) -> Vec<EntityPtr> {
91    world
92        .iter_entities()
93        .filter_map(|entity_ref| {
94            let entity = entity_ref.id();
95            let ptr = world.entity_ptr(entity);
96
97            // Check if this entity has a Target component pointing to our target
98            ptr.get::<Target>().and_then(|t| {
99                t.0.filter(|h| h.entity() == target_entity)
100                    .map(|_| world.entity_ptr(entity))
101            })
102        })
103        .collect()
104}
105
106fn main() {
107    let mut world = World::new();
108
109    println!("Setting up game entities...\n");
110
111    // Create some items
112    let sword = world
113        .spawn((Name("Iron Sword"), Weight(5.0), Damage(15)))
114        .id();
115    let shield = world
116        .spawn((Name("Wooden Shield"), Weight(8.0), Defense(10)))
117        .id();
118    let potion = world.spawn((Name("Health Potion"), Weight(0.5))).id();
119    let gold = world.spawn((Name("Gold Coins"), Weight(1.0))).id();
120    let bow = world
121        .spawn((Name("Short Bow"), Weight(3.0), Damage(10)))
122        .id();
123
124    // Create the hero
125    let hero = world
126        .spawn((
127            Name("Hero"),
128            Health(100),
129            Target(None), // Not targeting anyone yet
130            Inventory(vec![
131                EntityHandle::new(sword),
132                EntityHandle::new(shield),
133                EntityHandle::new(potion),
134                EntityHandle::new(gold),
135            ]),
136            Equipped {
137                weapon: Some(EntityHandle::new(sword)),
138                armor: Some(EntityHandle::new(shield)),
139            },
140        ))
141        .id();
142
143    // Create an enemy targeting the hero
144    let goblin = world
145        .spawn((
146            Name("Goblin"),
147            Health(30),
148            Target(Some(EntityHandle::new(hero))),
149            Inventory(vec![EntityHandle::new(bow)]),
150            Equipped {
151                weapon: Some(EntityHandle::new(bow)),
152                armor: None,
153            },
154        ))
155        .id();
156
157    // Create another enemy also targeting the hero
158    let _orc = world
159        .spawn((
160            Name("Orc"),
161            Health(50),
162            Target(Some(EntityHandle::new(hero))),
163        ))
164        .id();
165
166    // Demonstrate entity graph queries
167    let hero_ptr = world.entity_ptr(hero);
168    let goblin_ptr = world.entity_ptr(goblin);
169
170    // 1. Calculate inventory weight
171    let weight = total_weight(hero_ptr);
172    println!("Hero's inventory weight: {} lbs", weight);
173    println!("  Expected: 5.0 + 8.0 + 0.5 + 1.0 = 14.5");
174    assert!((weight - 14.5).abs() < 0.01);
175
176    // 2. Check equipped items
177    let damage = equipped_damage(hero_ptr);
178    println!("\nHero's equipped weapon damage: {:?}", damage);
179    assert_eq!(damage, Some(15));
180
181    let defense = equipped_defense(hero_ptr);
182    println!("Hero's equipped armor defense: {:?}", defense);
183    assert_eq!(defense, Some(10));
184
185    // 3. Goblin's equipment
186    let goblin_damage = equipped_damage(goblin_ptr);
187    println!("\nGoblin's equipped weapon damage: {:?}", goblin_damage);
188    assert_eq!(goblin_damage, Some(10));
189
190    let goblin_defense = equipped_defense(goblin_ptr);
191    println!("Goblin's equipped armor defense: {:?}", goblin_defense);
192    assert_eq!(goblin_defense, None); // No armor equipped
193
194    // 4. Check targeting
195    let hero_target = target_info(hero_ptr);
196    println!("\nHero's target: {:?}", hero_target);
197    assert_eq!(hero_target, None);
198
199    let goblin_target = target_info(goblin_ptr);
200    println!("Goblin's target: {:?}", goblin_target);
201    assert_eq!(goblin_target, Some(("Hero", 100)));
202
203    // 5. Find all entities targeting the hero
204    let attackers = find_attackers(&world, hero);
205    let attacker_names: Vec<_> = attackers
206        .iter()
207        .filter_map(|p| p.get::<Name>())
208        .map(|n| n.0)
209        .collect();
210    println!("\nEntities targeting the hero: {:?}", attacker_names);
211    assert_eq!(attacker_names.len(), 2);
212    assert!(attacker_names.contains(&"Goblin"));
213    assert!(attacker_names.contains(&"Orc"));
214
215    // 6. Update hero's target to the goblin
216    world
217        .entity_mut(hero)
218        .insert(Target(Some(EntityHandle::new(goblin))));
219
220    // Re-query with fresh EntityPtr
221    let hero_ptr = world.entity_ptr(hero);
222
223    let hero_target = target_info(hero_ptr);
224    println!("\nAfter targeting goblin:");
225    println!("Hero's target: {:?}", hero_target);
226    assert_eq!(hero_target, Some(("Goblin", 30)));
227
228    println!("\nAll assertions passed!");
229}