Skip to content

Commit 16dda50

Browse files
committed
Add an extension trait to EntityCommands to update hierarchy while preserving GlobalTransform (#7024)
# Objective It is often necessary to update an entity's parent while keeping its GlobalTransform static. Currently it is cumbersome and error-prone (two questions in the discord `#help` channel in the past week) - Part 2, resolves #5475 - Builds on: #7020. ## Solution - Added the `BuildChildrenTransformExt` trait, it is part of `bevy::prelude` and adds the following methods to `EntityCommands`: - `set_parent_in_place`: Change the parent of an entity and update its `Transform` in order to preserve its `GlobalTransform` after the parent change - `remove_parent_in_place`: Remove an entity from a hierarchy, while preserving its `GlobalTransform`. --- ## Changelog - Added the `BuildChildrenTransformExt` trait, it is part of `bevy::prelude` and adds the following methods to `EntityCommands`: - `set_parent_in_place`: Change the parent of an entity and update its `Transform` in order to preserve its `GlobalTransform` after the parent change - `remove_parent_in_place`: Remove an entity from a hierarchy, while preserving its `GlobalTransform`. Co-authored-by: Nicola Papale <[email protected]>
1 parent ba3069f commit 16dda50

File tree

3 files changed

+107
-2
lines changed

3 files changed

+107
-2
lines changed

crates/bevy_hierarchy/src/child_builder.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,8 @@ impl Command for RemoveChildren {
198198

199199
/// Command that removes the parent of an entity, and removes that entity from the parent's [`Children`].
200200
pub struct RemoveParent {
201-
child: Entity,
201+
/// `Entity` whose parent must be removed.
202+
pub child: Entity,
202203
}
203204

204205
impl Command for RemoveParent {

crates/bevy_transform/src/commands.rs

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
//! Extension to [`EntityCommands`] to modify [`bevy_hierarchy`] hierarchies
2+
//! while preserving [`GlobalTransform`].
3+
4+
use bevy_ecs::{prelude::Entity, system::Command, system::EntityCommands, world::World};
5+
use bevy_hierarchy::{AddChild, RemoveParent};
6+
7+
#[cfg(doc)]
8+
use bevy_hierarchy::BuildChildren;
9+
10+
use crate::{GlobalTransform, Transform};
11+
12+
/// Command similar to [`AddChild`], but updating the child transform to keep
13+
/// it at the same [`GlobalTransform`].
14+
///
15+
/// You most likely want to use [`BuildChildrenTransformExt::set_parent_in_place`]
16+
/// method on [`EntityCommands`] instead.
17+
pub struct AddChildInPlace {
18+
/// Parent entity to add the child to.
19+
pub parent: Entity,
20+
/// Child entity to add.
21+
pub child: Entity,
22+
}
23+
impl Command for AddChildInPlace {
24+
fn write(self, world: &mut World) {
25+
let hierarchy_command = AddChild {
26+
child: self.child,
27+
parent: self.parent,
28+
};
29+
hierarchy_command.write(world);
30+
// FIXME: Replace this closure with a `try` block. See: https://github.com/rust-lang/rust/issues/31436.
31+
let mut update_transform = || {
32+
let parent = *world.get_entity(self.parent)?.get::<GlobalTransform>()?;
33+
let child_global = *world.get_entity(self.child)?.get::<GlobalTransform>()?;
34+
let mut child_entity = world.get_entity_mut(self.child)?;
35+
let mut child = child_entity.get_mut::<Transform>()?;
36+
*child = child_global.reparented_to(&parent);
37+
Some(())
38+
};
39+
update_transform();
40+
}
41+
}
42+
/// Command similar to [`RemoveParent`], but updating the child transform to keep
43+
/// it at the same [`GlobalTransform`].
44+
///
45+
/// You most likely want to use [`BuildChildrenTransformExt::remove_parent_in_place`]
46+
/// method on [`EntityCommands`] instead.
47+
pub struct RemoveParentInPlace {
48+
/// `Entity` whose parent must be removed.
49+
pub child: Entity,
50+
}
51+
impl Command for RemoveParentInPlace {
52+
fn write(self, world: &mut World) {
53+
let hierarchy_command = RemoveParent { child: self.child };
54+
hierarchy_command.write(world);
55+
// FIXME: Replace this closure with a `try` block. See: https://github.com/rust-lang/rust/issues/31436.
56+
let mut update_transform = || {
57+
let child_global = *world.get_entity(self.child)?.get::<GlobalTransform>()?;
58+
let mut child_entity = world.get_entity_mut(self.child)?;
59+
let mut child = child_entity.get_mut::<Transform>()?;
60+
*child = child_global.compute_transform();
61+
Some(())
62+
};
63+
update_transform();
64+
}
65+
}
66+
/// Collection of methods similar to [`BuildChildren`], but preserving each
67+
/// entity's [`GlobalTransform`].
68+
pub trait BuildChildrenTransformExt {
69+
/// Change this entity's parent while preserving this entity's [`GlobalTransform`]
70+
/// by updating its [`Transform`].
71+
///
72+
/// See [`BuildChildren::set_parent`] for a method that doesn't update the
73+
/// [`Transform`].
74+
///
75+
/// Note that both the hierarchy and transform updates will only execute
76+
/// at the end of the current stage.
77+
fn set_parent_in_place(&mut self, parent: Entity) -> &mut Self;
78+
79+
/// Make this entity parentless while preserving this entity's [`GlobalTransform`]
80+
/// by updating its [`Transform`] to be equal to its current [`GlobalTransform`].
81+
///
82+
/// See [`BuildChildren::remove_parent`] for a method that doesn't update the
83+
/// [`Transform`].
84+
///
85+
/// Note that both the hierarchy and transform updates will only execute
86+
/// at the end of the current stage.
87+
fn remove_parent_in_place(&mut self) -> &mut Self;
88+
}
89+
impl<'w, 's, 'a> BuildChildrenTransformExt for EntityCommands<'w, 's, 'a> {
90+
fn remove_parent_in_place(&mut self) -> &mut Self {
91+
let child = self.id();
92+
self.commands().add(RemoveParentInPlace { child });
93+
self
94+
}
95+
96+
fn set_parent_in_place(&mut self, parent: Entity) -> &mut Self {
97+
let child = self.id();
98+
self.commands().add(AddChildInPlace { child, parent });
99+
self
100+
}
101+
}

crates/bevy_transform/src/lib.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22
#![warn(clippy::undocumented_unsafe_blocks)]
33
#![doc = include_str!("../README.md")]
44

5+
pub mod commands;
56
/// The basic components of the transform crate
67
pub mod components;
78
mod systems;
89

910
#[doc(hidden)]
1011
pub mod prelude {
1112
#[doc(hidden)]
12-
pub use crate::{components::*, TransformBundle, TransformPlugin};
13+
pub use crate::{
14+
commands::BuildChildrenTransformExt, components::*, TransformBundle, TransformPlugin,
15+
};
1316
}
1417

1518
use bevy_app::prelude::*;

0 commit comments

Comments
 (0)