Skip to content

Commit 570b13a

Browse files
Add EntityCommands::with_child and EntityMut::with_child (#6716)
# Objective - Adding a single child is very cumbersome, and requires the use of a complex closure. - Fixes #6712. ## Solution Add a `with_child(b: impl Bundle)` API. ## Changelog Added `EntityCommands::with_child(b: impl Bundle)` and an equivelent method on `EntityMut` (for direct world access). ## Migration Guide When spawning a single child with no children of its own, consider using `commands.entity(parent).with_child(child_bundle)` to make your code clearer.
1 parent 64642fb commit 570b13a

22 files changed

+320
-285
lines changed

crates/bevy_hierarchy/src/child_builder.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,12 @@ impl<'w, 's, 'a> ChildBuilder<'w, 's, 'a> {
252252

253253
/// Trait defining how to build children
254254
pub trait BuildChildren {
255+
/// Spawns a new entity for the provided `bundle`, and adds it as a child entity to the parent.
256+
///
257+
/// Returns the original reference to the parent,
258+
/// which can be used to spawn siblings by calling this method repeatedly.
259+
fn with_child(&mut self, bundle: impl Bundle) -> &mut Self;
260+
255261
/// Creates a [`ChildBuilder`] with the given children built in the given closure
256262
///
257263
/// Compared to [`add_children`][BuildChildren::add_children], this method returns self
@@ -316,6 +322,14 @@ pub trait BuildChildren {
316322
}
317323

318324
impl<'w, 's, 'a> BuildChildren for EntityCommands<'w, 's, 'a> {
325+
fn with_child(&mut self, bundle: impl Bundle) -> &mut Self {
326+
let parent_entity = self.id();
327+
self.commands().add(move |world| {
328+
world.entity_mut(parent_entity).with_child(bundle);
329+
});
330+
self
331+
}
332+
319333
fn with_children(&mut self, spawn_children: impl FnOnce(&mut ChildBuilder)) -> &mut Self {
320334
self.add_children(spawn_children);
321335
self
@@ -436,6 +450,8 @@ impl<'w> WorldChildBuilder<'w> {
436450

437451
/// Trait that defines adding children to an entity directly through the [`World`]
438452
pub trait BuildWorldChildren {
453+
/// Spawns a new entity for the provided `bundle`, and adds it as a child entity to the parent
454+
fn with_child(&mut self, bundle: impl Bundle) -> &mut Self;
439455
/// Creates a [`WorldChildBuilder`] with the given children built in the given closure
440456
fn with_children(&mut self, spawn_children: impl FnOnce(&mut WorldChildBuilder)) -> &mut Self;
441457
/// Pushes children to the back of the builder's children
@@ -447,6 +463,14 @@ pub trait BuildWorldChildren {
447463
}
448464

449465
impl<'w> BuildWorldChildren for EntityMut<'w> {
466+
fn with_child(&mut self, bundle: impl Bundle) -> &mut Self {
467+
let parent_entity = self.id();
468+
self.world_scope(|world| {
469+
world.spawn(bundle).set_parent(parent_entity);
470+
});
471+
self
472+
}
473+
450474
fn with_children(&mut self, spawn_children: impl FnOnce(&mut WorldChildBuilder)) -> &mut Self {
451475
let parent = self.id();
452476
self.world_scope(|world| {
@@ -496,6 +520,76 @@ impl<'w> BuildWorldChildren for EntityMut<'w> {
496520
}
497521
}
498522

523+
impl<'w> BuildWorldChildren for WorldChildBuilder<'w> {
524+
fn with_child(&mut self, bundle: impl Bundle) -> &mut Self {
525+
self.with_children(|world_child_builder| {
526+
world_child_builder.spawn(bundle);
527+
});
528+
self
529+
}
530+
531+
fn with_children(
532+
&mut self,
533+
spawn_children: impl FnOnce(&mut WorldChildBuilder<'w>),
534+
) -> &mut Self {
535+
let current_entity = self
536+
.current_entity
537+
.expect("Cannot add children without a parent. Try creating an entity first.");
538+
self.parent_entities.push(current_entity);
539+
self.current_entity = None;
540+
541+
spawn_children(self);
542+
543+
self.current_entity = self.parent_entities.pop();
544+
self
545+
}
546+
547+
fn push_children(&mut self, children: &[Entity]) -> &mut Self {
548+
let parent = self
549+
.current_entity
550+
.expect("Cannot add children without a parent. Try creating an entity first.");
551+
update_old_parents(self.world, parent, children);
552+
if let Some(mut children_component) = self.world.get_mut::<Children>(parent) {
553+
children_component
554+
.0
555+
.retain(|value| !children.contains(value));
556+
children_component.0.extend(children.iter().cloned());
557+
} else {
558+
self.world
559+
.entity_mut(parent)
560+
.insert(Children::from_entities(children));
561+
}
562+
self
563+
}
564+
565+
fn insert_children(&mut self, index: usize, children: &[Entity]) -> &mut Self {
566+
let parent = self
567+
.current_entity
568+
.expect("Cannot add children without a parent. Try creating an entity first.");
569+
update_old_parents(self.world, parent, children);
570+
if let Some(mut children_component) = self.world.get_mut::<Children>(parent) {
571+
children_component
572+
.0
573+
.retain(|value| !children.contains(value));
574+
children_component.0.insert_from_slice(index, children);
575+
} else {
576+
self.world
577+
.entity_mut(parent)
578+
.insert(Children::from_entities(children));
579+
}
580+
self
581+
}
582+
583+
fn remove_children(&mut self, children: &[Entity]) -> &mut Self {
584+
let parent = self
585+
.current_entity
586+
.expect("Cannot remove children without a parent. Try creating an entity first.");
587+
588+
remove_children(parent, children, self.world);
589+
self
590+
}
591+
}
592+
499593
#[cfg(test)]
500594
mod tests {
501595
use super::{BuildChildren, BuildWorldChildren};

errors/B0004.md

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,15 @@ fn setup_cube(
3030
) {
3131
commands
3232
.spawn(TransformBundle::default())
33-
.with_children(|parent| {
34-
// cube
35-
parent.spawn(PbrBundle {
33+
// Cube
34+
.with_child(PbrBundle {
3635
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
3736
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
3837
transform: Transform::from_xyz(0.0, 0.5, 0.0),
3938
..default()
4039
});
41-
});
4240
43-
// camera
41+
// Camera
4442
commands.spawn(Camera3dBundle {
4543
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
4644
..default()
@@ -77,14 +75,12 @@ fn setup_cube(
7775
// ComputedVisibility component needed to display the cube,
7876
// In addition to the Transform and GlobalTransform components.
7977
.spawn(SpatialBundle::default())
80-
.with_children(|parent| {
81-
// cube
82-
parent.spawn(PbrBundle {
78+
// Cube
79+
.with_child(PbrBundle {
8380
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
8481
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
8582
transform: Transform::from_xyz(0.0, 0.5, 0.0),
8683
..default()
87-
});
8884
});
8985
9086
// camera

examples/3d/lighting.rs

Lines changed: 32 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -110,19 +110,17 @@ fn setup(
110110
},
111111
..default()
112112
})
113-
.with_children(|builder| {
114-
builder.spawn(PbrBundle {
115-
mesh: meshes.add(Mesh::from(shape::UVSphere {
116-
radius: 0.1,
117-
..default()
118-
})),
119-
material: materials.add(StandardMaterial {
120-
base_color: Color::RED,
121-
emissive: Color::rgba_linear(100.0, 0.0, 0.0, 0.0),
122-
..default()
123-
}),
113+
.with_child(PbrBundle {
114+
mesh: meshes.add(Mesh::from(shape::UVSphere {
115+
radius: 0.1,
124116
..default()
125-
});
117+
})),
118+
material: materials.add(StandardMaterial {
119+
base_color: Color::RED,
120+
emissive: Color::rgba_linear(100.0, 0.0, 0.0, 0.0),
121+
..default()
122+
}),
123+
..default()
126124
});
127125

128126
// green spot light
@@ -140,21 +138,19 @@ fn setup(
140138
},
141139
..default()
142140
})
143-
.with_children(|builder| {
144-
builder.spawn(PbrBundle {
145-
transform: Transform::from_rotation(Quat::from_rotation_x(PI / 2.0)),
146-
mesh: meshes.add(Mesh::from(shape::Capsule {
147-
depth: 0.125,
148-
radius: 0.1,
149-
..default()
150-
})),
151-
material: materials.add(StandardMaterial {
152-
base_color: Color::GREEN,
153-
emissive: Color::rgba_linear(0.0, 100.0, 0.0, 0.0),
154-
..default()
155-
}),
141+
.with_child(PbrBundle {
142+
transform: Transform::from_rotation(Quat::from_rotation_x(PI / 2.0)),
143+
mesh: meshes.add(Mesh::from(shape::Capsule {
144+
depth: 0.125,
145+
radius: 0.1,
146+
..default()
147+
})),
148+
material: materials.add(StandardMaterial {
149+
base_color: Color::GREEN,
150+
emissive: Color::rgba_linear(0.0, 100.0, 0.0, 0.0),
156151
..default()
157-
});
152+
}),
153+
..default()
158154
});
159155

160156
// blue point light
@@ -170,19 +166,17 @@ fn setup(
170166
},
171167
..default()
172168
})
173-
.with_children(|builder| {
174-
builder.spawn(PbrBundle {
175-
mesh: meshes.add(Mesh::from(shape::UVSphere {
176-
radius: 0.1,
177-
..default()
178-
})),
179-
material: materials.add(StandardMaterial {
180-
base_color: Color::BLUE,
181-
emissive: Color::rgba_linear(0.0, 0.0, 100.0, 0.0),
182-
..default()
183-
}),
169+
.with_child(PbrBundle {
170+
mesh: meshes.add(Mesh::from(shape::UVSphere {
171+
radius: 0.1,
172+
..default()
173+
})),
174+
material: materials.add(StandardMaterial {
175+
base_color: Color::BLUE,
176+
emissive: Color::rgba_linear(0.0, 0.0, 100.0, 0.0),
184177
..default()
185-
});
178+
}),
179+
..default()
186180
});
187181

188182
// directional 'sun' light

examples/3d/parenting.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,12 @@ fn setup(
4545
},
4646
Rotator,
4747
))
48-
.with_children(|parent| {
49-
// child cube
50-
parent.spawn(PbrBundle {
51-
mesh: cube_handle,
52-
material: cube_material_handle,
53-
transform: Transform::from_xyz(0.0, 0.0, 3.0),
54-
..default()
55-
});
48+
// child cube
49+
.with_child(PbrBundle {
50+
mesh: cube_handle,
51+
material: cube_material_handle,
52+
transform: Transform::from_xyz(0.0, 0.0, 3.0),
53+
..default()
5654
});
5755
// light
5856
commands.spawn(PointLightBundle {

examples/3d/spherical_area_lights.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,14 @@ fn setup(
6060
.with_scale(Vec3::splat(radius)),
6161
..default()
6262
})
63-
.with_children(|children| {
64-
children.spawn(PointLightBundle {
65-
point_light: PointLight {
66-
intensity: 1500.0,
67-
radius,
68-
color: Color::rgb(0.2, 0.2, 1.0),
69-
..default()
70-
},
63+
.with_child(PointLightBundle {
64+
point_light: PointLight {
65+
intensity: 1500.0,
66+
radius,
67+
color: Color::rgb(0.2, 0.2, 1.0),
7168
..default()
72-
});
69+
},
70+
..default()
7371
});
7472
}
7573
}

examples/animation/animated_transform.rs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -133,18 +133,16 @@ fn setup(
133133
// Add the Name component
134134
orbit_controller,
135135
))
136-
.with_children(|p| {
137-
// The satellite, placed at a distance of the planet
138-
p.spawn((
139-
PbrBundle {
140-
transform: Transform::from_xyz(1.5, 0.0, 0.0),
141-
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.5 })),
142-
material: materials.add(Color::rgb(0.3, 0.9, 0.3).into()),
143-
..default()
144-
},
145-
// Add the Name component
146-
satellite,
147-
));
148-
});
136+
// The satellite, placed at a distance of the planet
137+
.with_child((
138+
PbrBundle {
139+
transform: Transform::from_xyz(1.5, 0.0, 0.0),
140+
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.5 })),
141+
material: materials.add(Color::rgb(0.3, 0.9, 0.3).into()),
142+
..default()
143+
},
144+
// Add the Name component
145+
satellite,
146+
));
149147
});
150148
}

examples/ecs/hierarchy.rs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,15 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
2323
texture: texture.clone(),
2424
..default()
2525
})
26-
// With that entity as a parent, run a lambda that spawns its children
27-
.with_children(|parent| {
28-
// parent is a ChildBuilder, which has a similar API to Commands
29-
parent.spawn(SpriteBundle {
30-
transform: Transform::from_xyz(250.0, 0.0, 0.0).with_scale(Vec3::splat(0.75)),
31-
texture: texture.clone(),
32-
sprite: Sprite {
33-
color: Color::BLUE,
34-
..default()
35-
},
26+
// Add a child entity
27+
.with_child(SpriteBundle {
28+
transform: Transform::from_xyz(250.0, 0.0, 0.0).with_scale(Vec3::splat(0.75)),
29+
texture: texture.clone(),
30+
sprite: Sprite {
31+
color: Color::BLUE,
3632
..default()
37-
});
33+
},
34+
..default()
3835
})
3936
// Store parent entity for next sections
4037
.id();

examples/ecs/iter_combinations.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -136,17 +136,15 @@ fn generate_bodies(
136136
},
137137
Star,
138138
))
139-
.with_children(|p| {
140-
p.spawn(PointLightBundle {
141-
point_light: PointLight {
142-
color: Color::WHITE,
143-
intensity: 400.0,
144-
range: 100.0,
145-
radius: star_radius,
146-
..default()
147-
},
139+
.with_child(PointLightBundle {
140+
point_light: PointLight {
141+
color: Color::WHITE,
142+
intensity: 400.0,
143+
range: 100.0,
144+
radius: star_radius,
148145
..default()
149-
});
146+
},
147+
..default()
150148
});
151149
commands.spawn(Camera3dBundle {
152150
transform: Transform::from_xyz(0.0, 10.5, -30.0).looking_at(Vec3::ZERO, Vec3::Y),

0 commit comments

Comments
 (0)