Skip to main content

hierarchy/
hierarchy.rs

1//! Demonstrates techniques for creating a hierarchy of parent and child entities.
2//!
3//! When [`DefaultPlugins`] are added to your app, systems are automatically added to propagate
4//! [`Transform`] and [`Visibility`] from parents to children down the hierarchy,
5//! resulting in a final [`GlobalTransform`] and [`InheritedVisibility`] component for each entity.
6
7use std::{f32::consts::*, time::Duration};
8
9use bevy::{color::palettes::css::*, ecs::relationship::RelatedSpawner, prelude::*};
10
11fn main() {
12    App::new()
13        .add_plugins(DefaultPlugins)
14        .add_systems(Startup, setup)
15        .init_state::<Showcase>()
16        .insert_resource(Delta(Duration::ZERO))
17        .add_systems(OnEnter(Showcase::WithChildren), setup_with_children)
18        .add_systems(OnEnter(Showcase::ChildrenSpawn), setup_children_spawn)
19        .add_systems(OnEnter(Showcase::ChildrenMacro), spawn_children_macro)
20        .add_systems(OnEnter(Showcase::ChildrenIter), setup_children_iter)
21        .add_systems(OnEnter(Showcase::Related), setup_children_related)
22        .add_systems(OnEnter(Showcase::Bsn), setup_bsn)
23        .add_systems(Update, (rotate, switch_scene))
24        .run();
25}
26
27#[derive(Debug, Clone, Eq, PartialEq, Hash, States, Default)]
28enum Showcase {
29    #[default]
30    WithChildren,
31    ChildrenSpawn,
32    ChildrenMacro,
33    ChildrenIter,
34    Related,
35    Bsn,
36}
37
38impl Showcase {
39    fn next(&self) -> Self {
40        match self {
41            Showcase::WithChildren => Showcase::ChildrenSpawn,
42            Showcase::ChildrenSpawn => Showcase::ChildrenMacro,
43            Showcase::ChildrenMacro => Showcase::ChildrenIter,
44            Showcase::ChildrenIter => Showcase::Related,
45            Showcase::Related => Showcase::Bsn,
46            Showcase::Bsn => Showcase::WithChildren,
47        }
48    }
49}
50
51fn switch_scene(
52    keyboard: Res<ButtonInput<KeyCode>>,
53    scene: Res<State<Showcase>>,
54    mut next_scene: ResMut<NextState<Showcase>>,
55) {
56    if keyboard.just_pressed(KeyCode::Space) {
57        info!("Switching scene");
58        next_scene.set(scene.get().next());
59    }
60}
61
62fn setup(mut commands: Commands) {
63    commands.spawn(Camera2d);
64}
65
66#[derive(Resource)]
67struct Delta(Duration);
68
69fn setup_common(
70    commands: &mut Commands,
71    time: &Res<Time>,
72    delta: &mut ResMut<Delta>,
73    title: &str,
74    stage: Showcase,
75) {
76    delta.0 = time.elapsed();
77    commands.spawn((
78        Text::new(title),
79        TextFont::from_font_size(36.),
80        DespawnOnExit(stage),
81    ));
82}
83
84fn setup_with_children(
85    mut commands: Commands,
86    asset_server: Res<AssetServer>,
87    time: Res<Time>,
88    mut delta: ResMut<Delta>,
89) {
90    let texture = asset_server.load("branding/icon.png");
91
92    setup_common(
93        &mut commands,
94        &time,
95        &mut delta,
96        "with_children()\nPress Space to continue",
97        Showcase::WithChildren,
98    );
99
100    // Spawn a root entity with no parent
101    let parent = commands
102        .spawn((
103            Sprite::from_image(texture.clone()),
104            Transform::from_scale(Vec3::splat(0.75)),
105            DespawnOnExit(Showcase::WithChildren),
106        ))
107        // With that entity as a parent, run a lambda that spawns its children
108        .with_children(|parent| {
109            // parent is a ChildSpawnerCommands, which has a similar API to Commands
110            parent.spawn((
111                Transform::from_xyz(250.0, 0.0, 0.0).with_scale(Vec3::splat(0.75)),
112                Sprite {
113                    image: texture.clone(),
114                    color: BLUE.into(),
115                    ..default()
116                },
117            ));
118        })
119        // Store parent entity for next sections
120        .id();
121
122    // Another way is to use the add_child function to add children after the parent
123    // entity has already been spawned.
124    let child = commands
125        .spawn((
126            Sprite {
127                image: texture.clone(),
128                color: LIME.into(),
129                ..default()
130            },
131            Transform::from_xyz(0.0, 250.0, 0.0).with_scale(Vec3::splat(0.75)),
132        ))
133        .id();
134
135    // Add child to the parent.
136    commands.entity(parent).add_child(child);
137}
138
139fn setup_children_spawn(
140    mut commands: Commands,
141    asset_server: Res<AssetServer>,
142    time: Res<Time>,
143    mut delta: ResMut<Delta>,
144) {
145    let texture = asset_server.load("branding/icon.png");
146
147    setup_common(
148        &mut commands,
149        &time,
150        &mut delta,
151        "Children::spawn() \nPress Space to continue",
152        Showcase::ChildrenSpawn,
153    );
154
155    // Children can also be spawned using the `Children` component as part of the parent's bundle.
156    commands.spawn((
157        Sprite::from_image(texture.clone()),
158        Transform::from_scale(Vec3::splat(0.75)),
159        DespawnOnExit(Showcase::ChildrenSpawn),
160        Children::spawn((
161            Spawn((
162                Transform::from_xyz(250.0, 0.0, 0.0).with_scale(Vec3::splat(0.75)),
163                Sprite {
164                    image: texture.clone(),
165                    color: BLUE.into(),
166                    ..default()
167                },
168            )),
169            // since they have to be explicitly created, `Children::spawn` can
170            // mix implementers of `SpawnableList` while `children!` cannot.
171            SpawnWith(|spawner: &mut RelatedSpawner<'_, ChildOf>| {
172                spawner.spawn((
173                    Transform::from_xyz(0.0, 250.0, 0.0).with_scale(Vec3::splat(0.75)),
174                    Sprite {
175                        image: texture,
176                        color: LIME.into(),
177                        ..default()
178                    },
179                ));
180            }),
181        )),
182    ));
183}
184
185fn spawn_children_macro(
186    mut commands: Commands,
187    asset_server: Res<AssetServer>,
188    time: Res<Time>,
189    mut delta: ResMut<Delta>,
190) {
191    let texture = asset_server.load("branding/icon.png");
192
193    setup_common(
194        &mut commands,
195        &time,
196        &mut delta,
197        "children!() \nPress Space to continue",
198        Showcase::ChildrenMacro,
199    );
200
201    // The `children!` macro provides a convenient way to define children inline with their parent.
202    commands.spawn((
203        Sprite::from_image(texture.clone()),
204        Transform::from_scale(Vec3::splat(0.75)),
205        DespawnOnExit(Showcase::ChildrenMacro),
206        children![
207            (
208                Transform::from_xyz(250.0, 0.0, 0.0).with_scale(Vec3::splat(0.75)),
209                Sprite {
210                    image: texture.clone(),
211                    color: BLUE.into(),
212                    ..default()
213                },
214            ),
215            (
216                Transform::from_xyz(0.0, 250.0, 0.0).with_scale(Vec3::splat(0.75)),
217                Sprite {
218                    image: texture,
219                    color: LIME.into(),
220                    ..default()
221                },
222            )
223        ],
224    ));
225}
226
227fn setup_children_iter(
228    mut commands: Commands,
229    asset_server: Res<AssetServer>,
230    time: Res<Time>,
231    mut delta: ResMut<Delta>,
232) {
233    let texture = asset_server.load("branding/icon.png");
234
235    setup_common(
236        &mut commands,
237        &time,
238        &mut delta,
239        "SpawnIter() \nPress Space to continue",
240        Showcase::ChildrenIter,
241    );
242
243    // You can also spawn children from an iterator yielding bundles.
244    let child_components = [
245        (
246            Transform::from_xyz(250.0, 0.0, 0.0).with_scale(Vec3::splat(0.75)),
247            BLUE,
248        ),
249        (
250            Transform::from_xyz(0.0, 250.0, 0.0).with_scale(Vec3::splat(0.75)),
251            LIME,
252        ),
253    ];
254
255    commands.spawn((
256        Sprite::from_image(texture.clone()),
257        Transform::from_scale(Vec3::splat(0.75)),
258        DespawnOnExit(Showcase::ChildrenIter),
259        Children::spawn(SpawnIter(child_components.into_iter().map(
260            move |(transform, color)| {
261                (
262                    transform,
263                    Sprite {
264                        image: texture.clone(),
265                        color: color.into(),
266                        ..default()
267                    },
268                )
269            },
270        ))),
271    ));
272}
273
274fn setup_children_related(
275    mut commands: Commands,
276    asset_server: Res<AssetServer>,
277    time: Res<Time>,
278    mut delta: ResMut<Delta>,
279) {
280    let texture = asset_server.load("branding/icon.png");
281
282    setup_common(
283        &mut commands,
284        &time,
285        &mut delta,
286        "related!() \nPress Space to continue",
287        Showcase::Related,
288    );
289
290    // You can also spawn entities with relationships other than parent/child.
291    commands.spawn((
292        Sprite::from_image(texture.clone()),
293        Transform::from_scale(Vec3::splat(0.75)),
294        DespawnOnExit(Showcase::Related),
295        // the `related!` macro will spawn entities according to the `Children: RelationshipTarget` trait, but other types implementing `RelationshipTarget` can be used as well.
296        related!(Children[
297            (
298                Transform::from_xyz(250.0, 0.0, 0.0).with_scale(Vec3::splat(0.75)),
299                Sprite {
300                    image: texture.clone(),
301                    color: BLUE.into(),
302                    ..default()
303                },
304            ),
305            (
306                Transform::from_xyz(0.0, 250.0, 0.0).with_scale(Vec3::splat(0.75)),
307                Sprite {
308                    image: texture,
309                    color: LIME.into(),
310                    ..default()
311                },
312            )
313        ]),
314    ));
315}
316
317fn setup_bsn(mut commands: Commands, time: Res<Time>, mut delta: ResMut<Delta>) {
318    setup_common(
319        &mut commands,
320        &time,
321        &mut delta,
322        "BSN\nPress Space to continue",
323        Showcase::Bsn,
324    );
325
326    // BSN can also define related entities inline. Asset paths are loaded when the scene is spawned.
327    commands.spawn_scene(bsn! {
328        Sprite { image: "branding/icon.png" }
329        Transform::from_scale(Vec3::splat(0.75))
330        DespawnOnExit::<Showcase>(Showcase::Bsn)
331        Children [
332            Transform::from_xyz(250.0, 0.0, 0.0).with_scale(Vec3::splat(0.75))
333            Sprite {
334                image: "branding/icon.png",
335                color: BLUE,
336            }
337            --
338            Transform::from_xyz(0.0, 250.0, 0.0).with_scale(Vec3::splat(0.75))
339            Sprite {
340                image: "branding/icon.png",
341                color: LIME,
342            }
343        ]
344    });
345}
346
347// A simple system to rotate the root entity, and rotate all its children separately
348fn rotate(
349    mut commands: Commands,
350    time: Res<Time>,
351    delta: Res<Delta>,
352    mut parents_query: Query<(Entity, &Children), With<Sprite>>,
353    mut transform_query: Query<&mut Transform, With<Sprite>>,
354) {
355    for (parent, children) in &mut parents_query {
356        if let Ok(mut transform) = transform_query.get_mut(parent) {
357            transform.rotate_z(-PI / 2. * time.delta_secs());
358        }
359
360        // To iterate through the entities children, just treat the Children component as a Vec
361        // Alternatively, you could query entities that have a ChildOf component
362        for child in children {
363            if let Ok(mut transform) = transform_query.get_mut(*child) {
364                transform.rotate_z(PI * time.delta_secs());
365            }
366        }
367
368        // To demonstrate removing children, we'll remove a child after a couple of seconds.
369        let elapsed = time.elapsed_secs() - delta.0.as_secs_f32();
370        if elapsed >= 2.0 && children.len() == 2 {
371            let child = children.last().unwrap();
372            commands.entity(*child).despawn();
373        }
374
375        if elapsed >= 4.0 {
376            // This will remove the entity from its parent's list of children, as well as despawn
377            // any children the entity has.
378            commands.entity(parent).despawn();
379        }
380    }
381}