Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
269 changes: 262 additions & 7 deletions examples/ecs/hierarchy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,102 @@
//! [`Transform`] and [`Visibility`] from parents to children down the hierarchy,
//! resulting in a final [`GlobalTransform`] and [`InheritedVisibility`] component for each entity.

use std::f32::consts::*;
use std::{f32::consts::*, time::Duration};

use bevy::{color::palettes::css::*, prelude::*};
use bevy::{color::palettes::css::*, ecs::relationship::RelatedSpawner, prelude::*};

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, rotate)
.init_state::<Showcase>()
.insert_resource(Delta(Duration::ZERO))
.add_systems(OnEnter(Showcase::WithChildren), setup_with_children)
.add_systems(OnEnter(Showcase::ChildrenSpawn), setup_children_spawn)
.add_systems(OnEnter(Showcase::ChildrenMacro), spawn_children_macro)
.add_systems(OnEnter(Showcase::ChildrenIter), setup_children_iter)
.add_systems(OnEnter(Showcase::Related), setup_children_related)
.add_systems(Update, (rotate, switch_scene))
.run();
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
#[derive(Debug, Clone, Eq, PartialEq, Hash, States, Default)]
enum Showcase {
#[default]
WithChildren,
ChildrenSpawn,
ChildrenMacro,
ChildrenIter,
Related,
}

impl Showcase {
fn next(&self) -> Self {
match self {
Showcase::WithChildren => Showcase::ChildrenSpawn,
Showcase::ChildrenSpawn => Showcase::ChildrenMacro,
Showcase::ChildrenMacro => Showcase::ChildrenIter,
Showcase::ChildrenIter => Showcase::Related,
Showcase::Related => Showcase::WithChildren,
}
}
}

fn switch_scene(
keyboard: Res<ButtonInput<KeyCode>>,
scene: Res<State<Showcase>>,
mut next_scene: ResMut<NextState<Showcase>>,
) {
if keyboard.just_pressed(KeyCode::Space) {
info!("Switching scene");
next_scene.set(scene.get().next());
}
}

fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
}

#[derive(Resource)]
struct Delta(Duration);

fn setup_common(
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a fan of this function. I would rather copy paste its body.

commands: &mut Commands,
time: &Res<Time>,
delta: &mut ResMut<Delta>,
title: &str,
stage: Showcase,
) {
delta.0 = time.elapsed();
commands.spawn((
Text::new(title),
TextFont::from_font_size(36.),
DespawnOnExit(stage),
));
}

fn setup_with_children(
mut commands: Commands,
asset_server: Res<AssetServer>,
time: Res<Time>,
mut delta: ResMut<Delta>,
) {
let texture = asset_server.load("branding/icon.png");

setup_common(
&mut commands,
&time,
&mut delta,
"with_children()\nPress Space to continue",
Showcase::WithChildren,
);

// Spawn a root entity with no parent
let parent = commands
.spawn((
Sprite::from_image(texture.clone()),
Transform::from_scale(Vec3::splat(0.75)),
DespawnOnExit(Showcase::WithChildren),
))
// With that entity as a parent, run a lambda that spawns its children
.with_children(|parent| {
Expand All @@ -46,7 +121,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let child = commands
.spawn((
Sprite {
image: texture,
image: texture.clone(),
color: LIME.into(),
..default()
},
Expand All @@ -58,10 +133,189 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.entity(parent).add_child(child);
}

fn setup_children_spawn(
mut commands: Commands,
asset_server: Res<AssetServer>,
time: Res<Time>,
mut delta: ResMut<Delta>,
) {
let texture = asset_server.load("branding/icon.png");

setup_common(
&mut commands,
&time,
&mut delta,
"Children::spawn() \nPress Space to continue",
Showcase::ChildrenSpawn,
);

// Children can also be spawned using the `Children` component as part of the parent's bundle.
commands.spawn((
Sprite::from_image(texture.clone()),
Transform::from_scale(Vec3::splat(0.75)),
DespawnOnExit(Showcase::ChildrenSpawn),
Children::spawn((
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure I would showcase the use of Children::spawn because I think the children! macro should always be preferred over it. On the other hand I use Children::spawn_one because it emphasizes that a single child is being spawned.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Children::spawn can mix Spawn and other implementers of SpawnableList which I think is worth showcasing, so maybe replacing one of these Spawns with a SpawnWith would be a good idea.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh yeah I forgot about that. That's a good point.

Spawn((
Transform::from_xyz(250.0, 0.0, 0.0).with_scale(Vec3::splat(0.75)),
Sprite {
image: texture.clone(),
color: BLUE.into(),
..default()
},
)),
// since they have to be explicitly created, `Children::spawn` can
// mix implementers of `SpawnableList` while `children!` cannot.
SpawnWith(|spawner: &mut RelatedSpawner<'_, ChildOf>| {
spawner.spawn((
Transform::from_xyz(0.0, 250.0, 0.0).with_scale(Vec3::splat(0.75)),
Sprite {
image: texture,
color: LIME.into(),
..default()
},
));
}),
)),
));
}

fn spawn_children_macro(
mut commands: Commands,
asset_server: Res<AssetServer>,
time: Res<Time>,
mut delta: ResMut<Delta>,
) {
let texture = asset_server.load("branding/icon.png");

setup_common(
&mut commands,
&time,
&mut delta,
"children!() \nPress Space to continue",
Showcase::ChildrenMacro,
);

// The `children!` macro provides a convenient way to define children inline with their parent.
commands.spawn((
Sprite::from_image(texture.clone()),
Transform::from_scale(Vec3::splat(0.75)),
DespawnOnExit(Showcase::ChildrenMacro),
children![
(
Transform::from_xyz(250.0, 0.0, 0.0).with_scale(Vec3::splat(0.75)),
Sprite {
image: texture.clone(),
color: BLUE.into(),
..default()
},
),
(
Transform::from_xyz(0.0, 250.0, 0.0).with_scale(Vec3::splat(0.75)),
Sprite {
image: texture,
color: LIME.into(),
..default()
},
)
],
));
}

fn setup_children_iter(
mut commands: Commands,
asset_server: Res<AssetServer>,
time: Res<Time>,
mut delta: ResMut<Delta>,
) {
let texture = asset_server.load("branding/icon.png");

setup_common(
&mut commands,
&time,
&mut delta,
"SpawnIter() \nPress Space to continue",
Showcase::ChildrenIter,
);

// You can also spawn children from an iterator yielding bundles.
let child_components = [
(
Transform::from_xyz(250.0, 0.0, 0.0).with_scale(Vec3::splat(0.75)),
BLUE,
),
(
Transform::from_xyz(0.0, 250.0, 0.0).with_scale(Vec3::splat(0.75)),
LIME,
),
];

commands.spawn((
Sprite::from_image(texture.clone()),
Transform::from_scale(Vec3::splat(0.75)),
DespawnOnExit(Showcase::ChildrenIter),
Children::spawn(SpawnIter(child_components.into_iter().map(
move |(transform, color)| {
(
transform,
Sprite {
image: texture.clone(),
color: color.into(),
..default()
},
)
},
))),
));
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe showcase SpawnWith as well ?

fn setup_children_related(
mut commands: Commands,
asset_server: Res<AssetServer>,
time: Res<Time>,
mut delta: ResMut<Delta>,
) {
let texture = asset_server.load("branding/icon.png");

setup_common(
&mut commands,
&time,
&mut delta,
"related!() \nPress Space to continue",
Showcase::Related,
);

// You can also spawn entities with relationships other than parent/child.
commands.spawn((
Sprite::from_image(texture.clone()),
Transform::from_scale(Vec3::splat(0.75)),
DespawnOnExit(Showcase::Related),
// the `related!` macro will spawn entities according to the `Children: RelationshipTarget` trait, but other types implementing `RelationshipTarget` can be used as well.
related!(Children[
(
Transform::from_xyz(250.0, 0.0, 0.0).with_scale(Vec3::splat(0.75)),
Sprite {
image: texture.clone(),
color: BLUE.into(),
..default()
},
),
(
Transform::from_xyz(0.0, 250.0, 0.0).with_scale(Vec3::splat(0.75)),
Sprite {
image: texture,
color: LIME.into(),
..default()
},
)
]),
));
}

// A simple system to rotate the root entity, and rotate all its children separately
fn rotate(
mut commands: Commands,
time: Res<Time>,
delta: Res<Delta>,
mut parents_query: Query<(Entity, &Children), With<Sprite>>,
mut transform_query: Query<&mut Transform, With<Sprite>>,
) {
Expand All @@ -79,12 +333,13 @@ fn rotate(
}

// To demonstrate removing children, we'll remove a child after a couple of seconds.
if time.elapsed_secs() >= 2.0 && children.len() == 2 {
let elapsed = time.elapsed_secs() - delta.0.as_secs_f32();
if elapsed >= 2.0 && children.len() == 2 {
let child = children.last().unwrap();
commands.entity(*child).despawn();
}

if time.elapsed_secs() >= 4.0 {
if elapsed >= 4.0 {
// This will remove the entity from its parent's list of children, as well as despawn
// any children the entity has.
commands.entity(parent).despawn();
Expand Down
Loading