Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EntityCommands::with_child and EntityMut::with_child #6716

Closed
wants to merge 11 commits into from
34 changes: 32 additions & 2 deletions crates/bevy_hierarchy/src/child_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,12 @@ impl<'w, 's, 'a> ChildBuilder<'w, 's, 'a> {

/// Trait defining how to build children
pub trait BuildChildren {
/// Spawns a new entity for the provided `bundle`, and adds it as a child entity to the parent.
///
/// Returns the original reference to the parent,
/// which can be used to spawn siblings by calling this method repeatedly.
fn with_child(&mut self, bundle: impl Bundle) -> &mut Self;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be inclined to make this return (), as this could reasonably return either Self or a version of Self for the new entity (especially as there's no way in this API to get the new child's id directly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think users would get that it's returning the child_builder cause that's a common pattern used in Bevy.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning self is actually quite useful: you can spawn multiple children under the same parent by chaining it. I should improve the docs to explain that though.

I didn't change the examples to use that pattern though, as it would be pointlessly controversial.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was a bit confused by that too. I think it makes sense as it is and overall is more useful this way, but I do think there's some potential for confusion here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I also wanted to maintain consistency with with_children


/// Creates a [`ChildBuilder`] with the given children built in the given closure
///
/// Compared to [`add_children`][BuildChildren::add_children], this method returns self
Expand Down Expand Up @@ -316,6 +322,13 @@ pub trait BuildChildren {
}

impl<'w, 's, 'a> BuildChildren for EntityCommands<'w, 's, 'a> {
fn with_child(&mut self, bundle: impl Bundle) -> &mut Self {
self.add_children(|child_builder| {
child_builder.spawn(bundle);
});
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
self
}

fn with_children(&mut self, spawn_children: impl FnOnce(&mut ChildBuilder)) -> &mut Self {
self.add_children(spawn_children);
self
Expand Down Expand Up @@ -444,6 +457,8 @@ impl<'w> WorldChildBuilder<'w> {

/// Trait that defines adding children to an entity directly through the [`World`]
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
pub trait BuildWorldChildren {
/// Spawns a new entity for the provided `bundle`, and adds it as a child entity to the parent
fn with_child(&mut self, bundle: impl Bundle) -> &mut Self;
/// Creates a [`WorldChildBuilder`] with the given children built in the given closure
fn with_children(&mut self, spawn_children: impl FnOnce(&mut WorldChildBuilder)) -> &mut Self;
/// Pushes children to the back of the builder's children
Expand All @@ -455,12 +470,20 @@ pub trait BuildWorldChildren {
}

impl<'w> BuildWorldChildren for EntityMut<'w> {
fn with_child(&mut self, bundle: impl Bundle) -> &mut Self {
// PERF: we can probably special-case this to be faster
self.with_children(|world_child_builder| {
world_child_builder.spawn(bundle);
});
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
self
}

fn with_children(&mut self, spawn_children: impl FnOnce(&mut WorldChildBuilder)) -> &mut Self {
let entity = self.id();
let parent_entity = self.id();
self.world_scope(|world| {
let mut builder = WorldChildBuilder {
current_entity: None,
parent_entities: vec![entity],
parent_entities: vec![parent_entity],
world,
};
spawn_children(&mut builder);
Expand Down Expand Up @@ -510,6 +533,13 @@ impl<'w> BuildWorldChildren for EntityMut<'w> {
}

impl<'w> BuildWorldChildren for WorldChildBuilder<'w> {
fn with_child(&mut self, bundle: impl Bundle) -> &mut Self {
self.with_children(|world_child_builder| {
world_child_builder.spawn(bundle);
});
self
}

fn with_children(
&mut self,
spawn_children: impl FnOnce(&mut WorldChildBuilder<'w>),
Expand Down
10 changes: 2 additions & 8 deletions errors/B0004.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,12 @@ fn setup_cube(
) {
commands
.spawn(TransformBundle::default())
.with_children(|parent| {
// cube
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
parent.spawn(PbrBundle {
.with_child(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
transform: Transform::from_xyz(0.0, 0.5, 0.0),
..default()
});
});

// camera
commands.spawn(Camera3dBundle {
Expand Down Expand Up @@ -77,14 +74,11 @@ fn setup_cube(
// ComputedVisibility component needed to display the cube,
// In addition to the Transform and GlobalTransform components.
.spawn(SpatialBundle::default())
.with_children(|parent| {
// cube
parent.spawn(PbrBundle {
.with_child(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
transform: Transform::from_xyz(0.0, 0.5, 0.0),
..default()
});
});

// camera
Expand Down
70 changes: 32 additions & 38 deletions examples/3d/lighting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,19 +110,17 @@ fn setup(
},
..default()
})
.with_children(|builder| {
builder.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::UVSphere {
radius: 0.1,
..default()
})),
material: materials.add(StandardMaterial {
base_color: Color::RED,
emissive: Color::rgba_linear(100.0, 0.0, 0.0, 0.0),
..default()
}),
.with_child(PbrBundle {
mesh: meshes.add(Mesh::from(shape::UVSphere {
radius: 0.1,
..default()
});
})),
material: materials.add(StandardMaterial {
base_color: Color::RED,
emissive: Color::rgba_linear(100.0, 0.0, 0.0, 0.0),
..default()
}),
..default()
});

// green spot light
Expand All @@ -140,21 +138,19 @@ fn setup(
},
..default()
})
.with_children(|builder| {
builder.spawn(PbrBundle {
transform: Transform::from_rotation(Quat::from_rotation_x(PI / 2.0)),
mesh: meshes.add(Mesh::from(shape::Capsule {
depth: 0.125,
radius: 0.1,
..default()
})),
material: materials.add(StandardMaterial {
base_color: Color::GREEN,
emissive: Color::rgba_linear(0.0, 100.0, 0.0, 0.0),
..default()
}),
.with_child(PbrBundle {
transform: Transform::from_rotation(Quat::from_rotation_x(PI / 2.0)),
mesh: meshes.add(Mesh::from(shape::Capsule {
depth: 0.125,
radius: 0.1,
..default()
})),
material: materials.add(StandardMaterial {
base_color: Color::GREEN,
emissive: Color::rgba_linear(0.0, 100.0, 0.0, 0.0),
..default()
});
}),
..default()
});

// blue point light
Expand All @@ -170,19 +166,17 @@ fn setup(
},
..default()
})
.with_children(|builder| {
builder.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::UVSphere {
radius: 0.1,
..default()
})),
material: materials.add(StandardMaterial {
base_color: Color::BLUE,
emissive: Color::rgba_linear(0.0, 0.0, 100.0, 0.0),
..default()
}),
.with_child(PbrBundle {
mesh: meshes.add(Mesh::from(shape::UVSphere {
radius: 0.1,
..default()
})),
material: materials.add(StandardMaterial {
base_color: Color::BLUE,
emissive: Color::rgba_linear(0.0, 0.0, 100.0, 0.0),
..default()
});
}),
..default()
});

// directional 'sun' light
Expand Down
14 changes: 6 additions & 8 deletions examples/3d/parenting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,12 @@ fn setup(
},
Rotator,
))
.with_children(|parent| {
// child cube
parent.spawn(PbrBundle {
mesh: cube_handle,
material: cube_material_handle,
transform: Transform::from_xyz(0.0, 0.0, 3.0),
..default()
});
// child cube
.with_child(PbrBundle {
mesh: cube_handle,
material: cube_material_handle,
transform: Transform::from_xyz(0.0, 0.0, 3.0),
..default()
});
// light
commands.spawn(PointLightBundle {
Expand Down
16 changes: 7 additions & 9 deletions examples/3d/spherical_area_lights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,14 @@ fn setup(
.with_scale(Vec3::splat(radius)),
..default()
})
.with_children(|children| {
children.spawn(PointLightBundle {
point_light: PointLight {
intensity: 1500.0,
radius,
color: Color::rgb(0.2, 0.2, 1.0),
..default()
},
.with_child(PointLightBundle {
point_light: PointLight {
intensity: 1500.0,
radius,
color: Color::rgb(0.2, 0.2, 1.0),
..default()
});
},
..default()
});
}
}
24 changes: 11 additions & 13 deletions examples/animation/animated_transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,16 @@ fn setup(
// Add the Name component
orbit_controller,
))
.with_children(|p| {
// The satellite, placed at a distance of the planet
p.spawn((
PbrBundle {
transform: Transform::from_xyz(1.5, 0.0, 0.0),
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.5 })),
material: materials.add(Color::rgb(0.3, 0.9, 0.3).into()),
..default()
},
// Add the Name component
satellite,
));
});
// The satellite, placed at a distance of the planet
.with_child((
PbrBundle {
transform: Transform::from_xyz(1.5, 0.0, 0.0),
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.5 })),
material: materials.add(Color::rgb(0.3, 0.9, 0.3).into()),
..default()
},
// Add the Name component
satellite,
));
});
}
19 changes: 8 additions & 11 deletions examples/ecs/hierarchy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,15 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
texture: texture.clone(),
..default()
})
// With that entity as a parent, run a lambda that spawns its children
.with_children(|parent| {
// parent is a ChildBuilder, which has a similar API to Commands
parent.spawn(SpriteBundle {
transform: Transform::from_xyz(250.0, 0.0, 0.0).with_scale(Vec3::splat(0.75)),
texture: texture.clone(),
sprite: Sprite {
color: Color::BLUE,
..default()
},
// Add a child entity
.with_child(SpriteBundle {
transform: Transform::from_xyz(250.0, 0.0, 0.0).with_scale(Vec3::splat(0.75)),
texture: texture.clone(),
sprite: Sprite {
color: Color::BLUE,
..default()
});
},
..default()
})
// Store parent entity for next sections
.id();
Expand Down
18 changes: 8 additions & 10 deletions examples/ecs/iter_combinations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,17 +136,15 @@ fn generate_bodies(
},
Star,
))
.with_children(|p| {
p.spawn(PointLightBundle {
point_light: PointLight {
color: Color::WHITE,
intensity: 400.0,
range: 100.0,
radius: star_radius,
..default()
},
.with_child(PointLightBundle {
point_light: PointLight {
color: Color::WHITE,
intensity: 400.0,
range: 100.0,
radius: star_radius,
..default()
});
},
..default()
});
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(0.0, 10.5, -30.0).looking_at(Vec3::ZERO, Vec3::Y),
Expand Down
18 changes: 8 additions & 10 deletions examples/ecs/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,14 @@ fn setup_menu(mut commands: Commands, asset_server: Res<AssetServer>) {
background_color: NORMAL_BUTTON.into(),
..default()
})
.with_children(|parent| {
parent.spawn(TextBundle::from_section(
"Play",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 40.0,
color: Color::rgb(0.9, 0.9, 0.9),
},
));
});
.with_child(TextBundle::from_section(
"Play",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 40.0,
color: Color::rgb(0.9, 0.9, 0.9),
},
));
})
.id();
commands.insert_resource(MenuData { button_entity });
Expand Down
Loading