docs/bevy/examples/ecs/hierarchy.rs 11.6 K · 381 lines · raw · history

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
7 use std::{f32::consts::*, time::Duration};
8
9 use bevy::{color::palettes::css::*, ecs::relationship::RelatedSpawner, prelude::*};
10
11 fn 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)]
28 enum Showcase {
29 #[default]
30 WithChildren,
31 ChildrenSpawn,
32 ChildrenMacro,
33 ChildrenIter,
34 Related,
35 Bsn,
36 }
37
38 impl 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
51 fn 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
62 fn setup(mut commands: Commands) {
63 commands.spawn(Camera2d);
64 }
65
66 #[derive(Resource)]
67 struct Delta(Duration);
68
69 fn 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
84 fn 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
139 fn 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
185 fn 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
227 fn 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
274 fn 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
317 fn 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
348 fn 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 }