Skip to content

Commit 59ee512

Browse files
MinerSebascart
andcommitted
Add TransformBundle (#3054)
# Objective - Bevy currently has no simple way to make an "empty" Entity work correctly in a Hierachy. - The current Solution is to insert a Tuple instead: ```rs .insert_bundle((Transform::default(), GlobalTransform::default())) ``` ## Solution * Add a `TransformBundle` that combines the Components: ```rs .insert_bundle(TransformBundle::default()) ``` * The code is based on #2331, except for missing the more controversial usage of `TransformBundle` as a Sub-bundle in preexisting Bundles. Co-authored-by: MinerSebas <[email protected]> Co-authored-by: Carter Anderson <[email protected]>
1 parent 7604665 commit 59ee512

File tree

9 files changed

+113
-96
lines changed

9 files changed

+113
-96
lines changed

crates/bevy_gltf/src/loader.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ use bevy_render::{
2626
use bevy_scene::Scene;
2727
use bevy_transform::{
2828
hierarchy::{BuildWorldChildren, WorldChildBuilder},
29-
prelude::{GlobalTransform, Transform},
29+
prelude::Transform,
30+
TransformBundle,
3031
};
3132
use bevy_utils::{HashMap, HashSet};
3233
use gltf::{
@@ -289,7 +290,7 @@ async fn load_gltf<'a, 'b>(
289290
let mut world = World::default();
290291
world
291292
.spawn()
292-
.insert_bundle((Transform::identity(), GlobalTransform::identity()))
293+
.insert_bundle(TransformBundle::identity())
293294
.with_children(|parent| {
294295
for node in scene.nodes() {
295296
let result = load_node(&node, parent, load_context, &buffer_data);
@@ -462,10 +463,9 @@ fn load_node(
462463
) -> Result<(), GltfError> {
463464
let transform = gltf_node.transform();
464465
let mut gltf_error = None;
465-
let mut node = world_builder.spawn_bundle((
466-
Transform::from_matrix(Mat4::from_cols_array_2d(&transform.matrix())),
467-
GlobalTransform::identity(),
468-
));
466+
let mut node = world_builder.spawn_bundle(TransformBundle::from(Transform::from_matrix(
467+
Mat4::from_cols_array_2d(&transform.matrix()),
468+
)));
469469

470470
if let Some(name) = gltf_node.name() {
471471
node.insert(Name::new(name.to_string()));

crates/bevy_transform/src/components/global_transform.rs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ use std::ops::Mul;
77
/// Describe the position of an entity relative to the reference frame.
88
///
99
/// * To place or move an entity, you should set its [`Transform`].
10-
/// * To be displayed, an entity must have both a [`Transform`] and a [`GlobalTransform`].
1110
/// * To get the global position of an entity, you should get its [`GlobalTransform`].
11+
/// * For transform hierarchies to work correctly, you must have both a [`Transform`] and a [`GlobalTransform`].
12+
/// * You may use the [`TransformBundle`](crate::TransformBundle) to guarantee this.
1213
///
1314
/// ## [`Transform`] and [`GlobalTransform`]
1415
///
@@ -20,16 +21,6 @@ use std::ops::Mul;
2021
/// [`GlobalTransform`] is updated from [`Transform`] in the system
2122
/// [`transform_propagate_system`](crate::transform_propagate_system::transform_propagate_system).
2223
///
23-
/// In pseudo code:
24-
/// ```ignore
25-
/// for entity in entities_without_parent:
26-
/// set entity.global_transform to entity.transform
27-
/// recursively:
28-
/// set parent to current entity
29-
/// for child in parent.children:
30-
/// set child.global_transform to parent.global_transform * child.transform
31-
/// ```
32-
///
3324
/// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you
3425
/// update the[`Transform`] of an entity in this stage or after, you will notice a 1 frame lag
3526
/// before the [`GlobalTransform`] is updated.

crates/bevy_transform/src/components/transform.rs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ use std::ops::Mul;
88
/// to its parent position.
99
///
1010
/// * To place or move an entity, you should set its [`Transform`].
11-
/// * To be displayed, an entity must have both a [`Transform`] and a [`GlobalTransform`].
1211
/// * To get the global position of an entity, you should get its [`GlobalTransform`].
12+
/// * To be displayed, an entity must have both a [`Transform`] and a [`GlobalTransform`].
13+
/// * You may use the [`TransformBundle`](crate::TransformBundle) to guarantee this.
1314
///
1415
/// ## [`Transform`] and [`GlobalTransform`]
1516
///
@@ -21,16 +22,6 @@ use std::ops::Mul;
2122
/// [`GlobalTransform`] is updated from [`Transform`] in the system
2223
/// [`transform_propagate_system`](crate::transform_propagate_system::transform_propagate_system).
2324
///
24-
/// In pseudo code:
25-
/// ```ignore
26-
/// for entity in entities_without_parent:
27-
/// set entity.global_transform to entity.transform
28-
/// recursively:
29-
/// set parent to current entity
30-
/// for child in parent.children:
31-
/// set child.global_transform to parent.global_transform * child.transform
32-
/// ```
33-
///
3425
/// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you
3526
/// update the[`Transform`] of an entity in this stage or after, you will notice a 1 frame lag
3627
/// before the [`GlobalTransform`] is updated.

crates/bevy_transform/src/lib.rs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,76 @@ pub mod transform_propagate_system;
1111
#[doc(hidden)]
1212
pub mod prelude {
1313
#[doc(hidden)]
14-
pub use crate::{components::*, hierarchy::*, TransformPlugin};
14+
pub use crate::{components::*, hierarchy::*, TransformBundle, TransformPlugin};
1515
}
1616

1717
use bevy_app::prelude::*;
18-
use bevy_ecs::schedule::{ParallelSystemDescriptorCoercion, SystemLabel};
18+
use bevy_ecs::{
19+
bundle::Bundle,
20+
schedule::{ParallelSystemDescriptorCoercion, SystemLabel},
21+
};
1922
use prelude::{parent_update_system, Children, GlobalTransform, Parent, PreviousParent, Transform};
2023

24+
/// A [`Bundle`] of the [`Transform`] and [`GlobalTransform`]
25+
/// [`Component`](bevy_ecs::component::Component)s, which describe the position of an entity.
26+
///
27+
/// * To place or move an entity, you should set its [`Transform`].
28+
/// * To get the global position of an entity, you should get its [`GlobalTransform`].
29+
/// * For transform hierarchies to work correctly, you must have both a [`Transform`] and a [`GlobalTransform`].
30+
/// * You may use the [`TransformBundle`] to guarantee this.
31+
///
32+
/// ## [`Transform`] and [`GlobalTransform`]
33+
///
34+
/// [`Transform`] is the position of an entity relative to its parent position, or the reference
35+
/// frame if it doesn't have a [`Parent`](Parent).
36+
///
37+
/// [`GlobalTransform`] is the position of an entity relative to the reference frame.
38+
///
39+
/// [`GlobalTransform`] is updated from [`Transform`] in the system
40+
/// [`transform_propagate_system`](crate::transform_propagate_system::transform_propagate_system).
41+
///
42+
/// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you
43+
/// update the[`Transform`] of an entity in this stage or after, you will notice a 1 frame lag
44+
/// before the [`GlobalTransform`] is updated.
45+
#[derive(Bundle, Clone, Copy, Debug, Default)]
46+
pub struct TransformBundle {
47+
/// The transform of the entity.
48+
pub local: Transform,
49+
/// The global transform of the entity.
50+
pub global: GlobalTransform,
51+
}
52+
53+
impl TransformBundle {
54+
/// Creates a new [`TransformBundle`] from a [`Transform`].
55+
///
56+
/// This initializes [`GlobalTransform`] as identity, to be updated later by the
57+
/// [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate) stage.
58+
#[inline]
59+
pub const fn from_transform(transform: Transform) -> Self {
60+
TransformBundle {
61+
local: transform,
62+
// Note: `..Default::default()` cannot be used here, because it isn't const
63+
..Self::identity()
64+
}
65+
}
66+
67+
/// Creates a new identity [`TransformBundle`], with no translation, rotation, and a scale of 1
68+
/// on all axes.
69+
#[inline]
70+
pub const fn identity() -> Self {
71+
TransformBundle {
72+
local: Transform::identity(),
73+
global: GlobalTransform::identity(),
74+
}
75+
}
76+
}
77+
78+
impl From<Transform> for TransformBundle {
79+
#[inline]
80+
fn from(transform: Transform) -> Self {
81+
Self::from_transform(transform)
82+
}
83+
}
2184
/// The base plugin for handling [`Transform`] components
2285
#[derive(Default)]
2386
pub struct TransformPlugin;

crates/bevy_transform/src/transform_propagate_system.rs

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ mod test {
8282
};
8383

8484
use super::*;
85-
use crate::hierarchy::{parent_update_system, BuildChildren, BuildWorldChildren};
85+
use crate::{
86+
hierarchy::{parent_update_system, BuildChildren, BuildWorldChildren},
87+
TransformBundle,
88+
};
8689

8790
#[test]
8891
fn did_propagate() {
@@ -96,33 +99,23 @@ mod test {
9699
schedule.add_stage("update", update_stage);
97100

98101
// Root entity
99-
world.spawn().insert_bundle((
100-
Transform::from_xyz(1.0, 0.0, 0.0),
101-
GlobalTransform::identity(),
102-
));
102+
world
103+
.spawn()
104+
.insert_bundle(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0)));
103105

104106
let mut children = Vec::new();
105107
world
106108
.spawn()
107-
.insert_bundle((
108-
Transform::from_xyz(1.0, 0.0, 0.0),
109-
GlobalTransform::identity(),
110-
))
109+
.insert_bundle(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0)))
111110
.with_children(|parent| {
112111
children.push(
113112
parent
114-
.spawn_bundle((
115-
Transform::from_xyz(0.0, 2.0, 0.),
116-
GlobalTransform::identity(),
117-
))
113+
.spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 2.0, 0.)))
118114
.id(),
119115
);
120116
children.push(
121117
parent
122-
.spawn_bundle((
123-
Transform::from_xyz(0.0, 0.0, 3.),
124-
GlobalTransform::identity(),
125-
))
118+
.spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 0.0, 3.)))
126119
.id(),
127120
);
128121
});
@@ -155,25 +148,16 @@ mod test {
155148
let mut commands = Commands::new(&mut queue, &world);
156149
let mut children = Vec::new();
157150
commands
158-
.spawn_bundle((
159-
Transform::from_xyz(1.0, 0.0, 0.0),
160-
GlobalTransform::identity(),
161-
))
151+
.spawn_bundle(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0)))
162152
.with_children(|parent| {
163153
children.push(
164154
parent
165-
.spawn_bundle((
166-
Transform::from_xyz(0.0, 2.0, 0.0),
167-
GlobalTransform::identity(),
168-
))
155+
.spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 2.0, 0.0)))
169156
.id(),
170157
);
171158
children.push(
172159
parent
173-
.spawn_bundle((
174-
Transform::from_xyz(0.0, 0.0, 3.0),
175-
GlobalTransform::identity(),
176-
))
160+
.spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 0.0, 3.0)))
177161
.id(),
178162
);
179163
});

examples/3d/pbr.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ fn setup(
5454
});
5555
// light
5656
commands.spawn_bundle(PointLightBundle {
57-
transform: Transform::from_translation(Vec3::new(50.0, 50.0, 50.0)),
57+
transform: Transform::from_xyz(50.0, 50.0, 50.0),
5858
point_light: PointLight {
5959
intensity: 600000.,
6060
range: 100.,
@@ -64,8 +64,7 @@ fn setup(
6464
});
6565
// camera
6666
commands.spawn_bundle(OrthographicCameraBundle {
67-
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 8.0))
68-
.looking_at(Vec3::default(), Vec3::Y),
67+
transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::default(), Vec3::Y),
6968
orthographic_projection: OrthographicProjection {
7069
scale: 0.01,
7170
..Default::default()

examples/3d/update_gltf_scene.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,7 @@ fn setup(
3838
// Spawn the scene as a child of another entity. This first scene will be translated backward
3939
// with its parent
4040
commands
41-
.spawn_bundle((
42-
Transform::from_xyz(0.0, 0.0, -1.0),
43-
GlobalTransform::identity(),
44-
))
41+
.spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 0.0, -1.0)))
4542
.with_children(|parent| {
4643
parent.spawn_scene(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"));
4744
});

examples/async_tasks/async_compute.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ fn spawn_tasks(mut commands: Commands, thread_pool: Res<AsyncComputeTaskPool>) {
6060
}
6161

6262
// Such hard work, all done!
63-
Transform::from_translation(Vec3::new(x as f32, y as f32, z as f32))
63+
Transform::from_xyz(x as f32, y as f32, z as f32)
6464
});
6565

6666
// Spawn new entity and add our new task as a component
@@ -107,13 +107,13 @@ fn setup_env(mut commands: Commands) {
107107

108108
// lights
109109
commands.spawn_bundle(PointLightBundle {
110-
transform: Transform::from_translation(Vec3::new(4.0, 12.0, 15.0)),
110+
transform: Transform::from_xyz(4.0, 12.0, 15.0),
111111
..Default::default()
112112
});
113113

114114
// camera
115115
commands.spawn_bundle(PerspectiveCameraBundle {
116-
transform: Transform::from_translation(Vec3::new(offset, offset, 15.0))
116+
transform: Transform::from_xyz(offset, offset, 15.0)
117117
.looking_at(Vec3::new(offset, offset, 0.0), Vec3::Y),
118118
..Default::default()
119119
});

examples/game/alien_cake_addict.rs

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,11 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
117117
.map(|i| {
118118
let height = rand::thread_rng().gen_range(-0.1..0.1);
119119
commands
120-
.spawn_bundle((
121-
Transform::from_xyz(i as f32, height - 0.2, j as f32),
122-
GlobalTransform::identity(),
123-
))
120+
.spawn_bundle(TransformBundle::from(Transform::from_xyz(
121+
i as f32,
122+
height - 0.2,
123+
j as f32,
124+
)))
124125
.with_children(|cell| {
125126
cell.spawn_scene(cell_scene.clone());
126127
});
@@ -133,18 +134,15 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
133134
// spawn the game character
134135
game.player.entity = Some(
135136
commands
136-
.spawn_bundle((
137-
Transform {
138-
translation: Vec3::new(
139-
game.player.i as f32,
140-
game.board[game.player.j][game.player.i].height,
141-
game.player.j as f32,
142-
),
143-
rotation: Quat::from_rotation_y(-std::f32::consts::FRAC_PI_2),
144-
..Default::default()
145-
},
146-
GlobalTransform::identity(),
147-
))
137+
.spawn_bundle(TransformBundle::from(Transform {
138+
translation: Vec3::new(
139+
game.player.i as f32,
140+
game.board[game.player.j][game.player.i].height,
141+
game.player.j as f32,
142+
),
143+
rotation: Quat::from_rotation_y(-std::f32::consts::FRAC_PI_2),
144+
..Default::default()
145+
}))
148146
.with_children(|cell| {
149147
cell.spawn_scene(asset_server.load("models/AlienCake/alien.glb#Scene0"));
150148
})
@@ -324,17 +322,11 @@ fn spawn_bonus(
324322
}
325323
game.bonus.entity = Some(
326324
commands
327-
.spawn_bundle((
328-
Transform {
329-
translation: Vec3::new(
330-
game.bonus.i as f32,
331-
game.board[game.bonus.j][game.bonus.i].height + 0.2,
332-
game.bonus.j as f32,
333-
),
334-
..Default::default()
335-
},
336-
GlobalTransform::identity(),
337-
))
325+
.spawn_bundle(TransformBundle::from(Transform::from_xyz(
326+
game.bonus.i as f32,
327+
game.board[game.bonus.j][game.bonus.i].height + 0.2,
328+
game.bonus.j as f32,
329+
)))
338330
.with_children(|children| {
339331
children.spawn_bundle(PointLightBundle {
340332
point_light: PointLight {

0 commit comments

Comments
 (0)