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 <66798382+MinerSebas@users.noreply.github.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
		
							parent
							
								
									7604665880
								
							
						
					
					
						commit
						59ee512292
					
				@ -26,7 +26,8 @@ use bevy_render::{
 | 
			
		||||
use bevy_scene::Scene;
 | 
			
		||||
use bevy_transform::{
 | 
			
		||||
    hierarchy::{BuildWorldChildren, WorldChildBuilder},
 | 
			
		||||
    prelude::{GlobalTransform, Transform},
 | 
			
		||||
    prelude::Transform,
 | 
			
		||||
    TransformBundle,
 | 
			
		||||
};
 | 
			
		||||
use bevy_utils::{HashMap, HashSet};
 | 
			
		||||
use gltf::{
 | 
			
		||||
@ -289,7 +290,7 @@ async fn load_gltf<'a, 'b>(
 | 
			
		||||
        let mut world = World::default();
 | 
			
		||||
        world
 | 
			
		||||
            .spawn()
 | 
			
		||||
            .insert_bundle((Transform::identity(), GlobalTransform::identity()))
 | 
			
		||||
            .insert_bundle(TransformBundle::identity())
 | 
			
		||||
            .with_children(|parent| {
 | 
			
		||||
                for node in scene.nodes() {
 | 
			
		||||
                    let result = load_node(&node, parent, load_context, &buffer_data);
 | 
			
		||||
@ -462,10 +463,9 @@ fn load_node(
 | 
			
		||||
) -> Result<(), GltfError> {
 | 
			
		||||
    let transform = gltf_node.transform();
 | 
			
		||||
    let mut gltf_error = None;
 | 
			
		||||
    let mut node = world_builder.spawn_bundle((
 | 
			
		||||
        Transform::from_matrix(Mat4::from_cols_array_2d(&transform.matrix())),
 | 
			
		||||
        GlobalTransform::identity(),
 | 
			
		||||
    ));
 | 
			
		||||
    let mut node = world_builder.spawn_bundle(TransformBundle::from(Transform::from_matrix(
 | 
			
		||||
        Mat4::from_cols_array_2d(&transform.matrix()),
 | 
			
		||||
    )));
 | 
			
		||||
 | 
			
		||||
    if let Some(name) = gltf_node.name() {
 | 
			
		||||
        node.insert(Name::new(name.to_string()));
 | 
			
		||||
 | 
			
		||||
@ -7,8 +7,9 @@ use std::ops::Mul;
 | 
			
		||||
/// Describe the position of an entity relative to the reference frame.
 | 
			
		||||
///
 | 
			
		||||
/// * To place or move an entity, you should set its [`Transform`].
 | 
			
		||||
/// * To be displayed, an entity must have both a [`Transform`] and a [`GlobalTransform`].
 | 
			
		||||
/// * To get the global position of an entity, you should get its [`GlobalTransform`].
 | 
			
		||||
/// * For transform hierarchies to work correctly, you must have both a [`Transform`] and a [`GlobalTransform`].
 | 
			
		||||
///   * You may use the [`TransformBundle`](crate::TransformBundle) to guarantee this.
 | 
			
		||||
///
 | 
			
		||||
/// ## [`Transform`] and [`GlobalTransform`]
 | 
			
		||||
///
 | 
			
		||||
@ -20,16 +21,6 @@ use std::ops::Mul;
 | 
			
		||||
/// [`GlobalTransform`] is updated from [`Transform`] in the system
 | 
			
		||||
/// [`transform_propagate_system`](crate::transform_propagate_system::transform_propagate_system).
 | 
			
		||||
///
 | 
			
		||||
/// In pseudo code:
 | 
			
		||||
/// ```ignore
 | 
			
		||||
/// for entity in entities_without_parent:
 | 
			
		||||
///     set entity.global_transform to entity.transform
 | 
			
		||||
///     recursively:
 | 
			
		||||
///         set parent to current entity
 | 
			
		||||
///         for child in parent.children:
 | 
			
		||||
///             set child.global_transform to parent.global_transform * child.transform
 | 
			
		||||
/// ```
 | 
			
		||||
///
 | 
			
		||||
/// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you
 | 
			
		||||
/// update the[`Transform`] of an entity in this stage or after, you will notice a 1 frame lag
 | 
			
		||||
/// before the [`GlobalTransform`] is updated.
 | 
			
		||||
 | 
			
		||||
@ -8,8 +8,9 @@ use std::ops::Mul;
 | 
			
		||||
/// to its parent position.
 | 
			
		||||
///
 | 
			
		||||
/// * To place or move an entity, you should set its [`Transform`].
 | 
			
		||||
/// * To be displayed, an entity must have both a [`Transform`] and a [`GlobalTransform`].
 | 
			
		||||
/// * To get the global position of an entity, you should get its [`GlobalTransform`].
 | 
			
		||||
/// * To be displayed, an entity must have both a [`Transform`] and a [`GlobalTransform`].
 | 
			
		||||
///   * You may use the [`TransformBundle`](crate::TransformBundle) to guarantee this.
 | 
			
		||||
///
 | 
			
		||||
/// ## [`Transform`] and [`GlobalTransform`]
 | 
			
		||||
///
 | 
			
		||||
@ -21,16 +22,6 @@ use std::ops::Mul;
 | 
			
		||||
/// [`GlobalTransform`] is updated from [`Transform`] in the system
 | 
			
		||||
/// [`transform_propagate_system`](crate::transform_propagate_system::transform_propagate_system).
 | 
			
		||||
///
 | 
			
		||||
/// In pseudo code:
 | 
			
		||||
/// ```ignore
 | 
			
		||||
/// for entity in entities_without_parent:
 | 
			
		||||
///     set entity.global_transform to entity.transform
 | 
			
		||||
///     recursively:
 | 
			
		||||
///         set parent to current entity
 | 
			
		||||
///         for child in parent.children:
 | 
			
		||||
///             set child.global_transform to parent.global_transform * child.transform
 | 
			
		||||
/// ```
 | 
			
		||||
///
 | 
			
		||||
/// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you
 | 
			
		||||
/// update the[`Transform`] of an entity in this stage or after, you will notice a 1 frame lag
 | 
			
		||||
/// before the [`GlobalTransform`] is updated.
 | 
			
		||||
 | 
			
		||||
@ -11,13 +11,76 @@ pub mod transform_propagate_system;
 | 
			
		||||
#[doc(hidden)]
 | 
			
		||||
pub mod prelude {
 | 
			
		||||
    #[doc(hidden)]
 | 
			
		||||
    pub use crate::{components::*, hierarchy::*, TransformPlugin};
 | 
			
		||||
    pub use crate::{components::*, hierarchy::*, TransformBundle, TransformPlugin};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
use bevy_app::prelude::*;
 | 
			
		||||
use bevy_ecs::schedule::{ParallelSystemDescriptorCoercion, SystemLabel};
 | 
			
		||||
use bevy_ecs::{
 | 
			
		||||
    bundle::Bundle,
 | 
			
		||||
    schedule::{ParallelSystemDescriptorCoercion, SystemLabel},
 | 
			
		||||
};
 | 
			
		||||
use prelude::{parent_update_system, Children, GlobalTransform, Parent, PreviousParent, Transform};
 | 
			
		||||
 | 
			
		||||
/// A [`Bundle`] of the [`Transform`] and [`GlobalTransform`]
 | 
			
		||||
/// [`Component`](bevy_ecs::component::Component)s, which describe the position of an entity.
 | 
			
		||||
///
 | 
			
		||||
/// * To place or move an entity, you should set its [`Transform`].
 | 
			
		||||
/// * To get the global position of an entity, you should get its [`GlobalTransform`].
 | 
			
		||||
/// * For transform hierarchies to work correctly, you must have both a [`Transform`] and a [`GlobalTransform`].
 | 
			
		||||
///   * You may use the [`TransformBundle`] to guarantee this.
 | 
			
		||||
///
 | 
			
		||||
/// ## [`Transform`] and [`GlobalTransform`]
 | 
			
		||||
///
 | 
			
		||||
/// [`Transform`] is the position of an entity relative to its parent position, or the reference
 | 
			
		||||
/// frame if it doesn't have a [`Parent`](Parent).
 | 
			
		||||
///
 | 
			
		||||
/// [`GlobalTransform`] is the position of an entity relative to the reference frame.
 | 
			
		||||
///
 | 
			
		||||
/// [`GlobalTransform`] is updated from [`Transform`] in the system
 | 
			
		||||
/// [`transform_propagate_system`](crate::transform_propagate_system::transform_propagate_system).
 | 
			
		||||
///
 | 
			
		||||
/// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you
 | 
			
		||||
/// update the[`Transform`] of an entity in this stage or after, you will notice a 1 frame lag
 | 
			
		||||
/// before the [`GlobalTransform`] is updated.
 | 
			
		||||
#[derive(Bundle, Clone, Copy, Debug, Default)]
 | 
			
		||||
pub struct TransformBundle {
 | 
			
		||||
    /// The transform of the entity.
 | 
			
		||||
    pub local: Transform,
 | 
			
		||||
    /// The global transform of the entity.
 | 
			
		||||
    pub global: GlobalTransform,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl TransformBundle {
 | 
			
		||||
    /// Creates a new [`TransformBundle`] from a [`Transform`].
 | 
			
		||||
    ///
 | 
			
		||||
    /// This initializes [`GlobalTransform`] as identity, to be updated later by the
 | 
			
		||||
    /// [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate) stage.
 | 
			
		||||
    #[inline]
 | 
			
		||||
    pub const fn from_transform(transform: Transform) -> Self {
 | 
			
		||||
        TransformBundle {
 | 
			
		||||
            local: transform,
 | 
			
		||||
            // Note: `..Default::default()` cannot be used here, because it isn't const
 | 
			
		||||
            ..Self::identity()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Creates a new identity [`TransformBundle`], with no translation, rotation, and a scale of 1
 | 
			
		||||
    /// on all axes.
 | 
			
		||||
    #[inline]
 | 
			
		||||
    pub const fn identity() -> Self {
 | 
			
		||||
        TransformBundle {
 | 
			
		||||
            local: Transform::identity(),
 | 
			
		||||
            global: GlobalTransform::identity(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<Transform> for TransformBundle {
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn from(transform: Transform) -> Self {
 | 
			
		||||
        Self::from_transform(transform)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
/// The base plugin for handling [`Transform`] components
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
pub struct TransformPlugin;
 | 
			
		||||
 | 
			
		||||
@ -82,7 +82,10 @@ mod test {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::hierarchy::{parent_update_system, BuildChildren, BuildWorldChildren};
 | 
			
		||||
    use crate::{
 | 
			
		||||
        hierarchy::{parent_update_system, BuildChildren, BuildWorldChildren},
 | 
			
		||||
        TransformBundle,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn did_propagate() {
 | 
			
		||||
@ -96,33 +99,23 @@ mod test {
 | 
			
		||||
        schedule.add_stage("update", update_stage);
 | 
			
		||||
 | 
			
		||||
        // Root entity
 | 
			
		||||
        world.spawn().insert_bundle((
 | 
			
		||||
            Transform::from_xyz(1.0, 0.0, 0.0),
 | 
			
		||||
            GlobalTransform::identity(),
 | 
			
		||||
        ));
 | 
			
		||||
        world
 | 
			
		||||
            .spawn()
 | 
			
		||||
            .insert_bundle(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0)));
 | 
			
		||||
 | 
			
		||||
        let mut children = Vec::new();
 | 
			
		||||
        world
 | 
			
		||||
            .spawn()
 | 
			
		||||
            .insert_bundle((
 | 
			
		||||
                Transform::from_xyz(1.0, 0.0, 0.0),
 | 
			
		||||
                GlobalTransform::identity(),
 | 
			
		||||
            ))
 | 
			
		||||
            .insert_bundle(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0)))
 | 
			
		||||
            .with_children(|parent| {
 | 
			
		||||
                children.push(
 | 
			
		||||
                    parent
 | 
			
		||||
                        .spawn_bundle((
 | 
			
		||||
                            Transform::from_xyz(0.0, 2.0, 0.),
 | 
			
		||||
                            GlobalTransform::identity(),
 | 
			
		||||
                        ))
 | 
			
		||||
                        .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 2.0, 0.)))
 | 
			
		||||
                        .id(),
 | 
			
		||||
                );
 | 
			
		||||
                children.push(
 | 
			
		||||
                    parent
 | 
			
		||||
                        .spawn_bundle((
 | 
			
		||||
                            Transform::from_xyz(0.0, 0.0, 3.),
 | 
			
		||||
                            GlobalTransform::identity(),
 | 
			
		||||
                        ))
 | 
			
		||||
                        .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 0.0, 3.)))
 | 
			
		||||
                        .id(),
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
@ -155,25 +148,16 @@ mod test {
 | 
			
		||||
        let mut commands = Commands::new(&mut queue, &world);
 | 
			
		||||
        let mut children = Vec::new();
 | 
			
		||||
        commands
 | 
			
		||||
            .spawn_bundle((
 | 
			
		||||
                Transform::from_xyz(1.0, 0.0, 0.0),
 | 
			
		||||
                GlobalTransform::identity(),
 | 
			
		||||
            ))
 | 
			
		||||
            .spawn_bundle(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0)))
 | 
			
		||||
            .with_children(|parent| {
 | 
			
		||||
                children.push(
 | 
			
		||||
                    parent
 | 
			
		||||
                        .spawn_bundle((
 | 
			
		||||
                            Transform::from_xyz(0.0, 2.0, 0.0),
 | 
			
		||||
                            GlobalTransform::identity(),
 | 
			
		||||
                        ))
 | 
			
		||||
                        .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 2.0, 0.0)))
 | 
			
		||||
                        .id(),
 | 
			
		||||
                );
 | 
			
		||||
                children.push(
 | 
			
		||||
                    parent
 | 
			
		||||
                        .spawn_bundle((
 | 
			
		||||
                            Transform::from_xyz(0.0, 0.0, 3.0),
 | 
			
		||||
                            GlobalTransform::identity(),
 | 
			
		||||
                        ))
 | 
			
		||||
                        .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 0.0, 3.0)))
 | 
			
		||||
                        .id(),
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
@ -54,7 +54,7 @@ fn setup(
 | 
			
		||||
    });
 | 
			
		||||
    // light
 | 
			
		||||
    commands.spawn_bundle(PointLightBundle {
 | 
			
		||||
        transform: Transform::from_translation(Vec3::new(50.0, 50.0, 50.0)),
 | 
			
		||||
        transform: Transform::from_xyz(50.0, 50.0, 50.0),
 | 
			
		||||
        point_light: PointLight {
 | 
			
		||||
            intensity: 600000.,
 | 
			
		||||
            range: 100.,
 | 
			
		||||
@ -64,8 +64,7 @@ fn setup(
 | 
			
		||||
    });
 | 
			
		||||
    // camera
 | 
			
		||||
    commands.spawn_bundle(OrthographicCameraBundle {
 | 
			
		||||
        transform: Transform::from_translation(Vec3::new(0.0, 0.0, 8.0))
 | 
			
		||||
            .looking_at(Vec3::default(), Vec3::Y),
 | 
			
		||||
        transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::default(), Vec3::Y),
 | 
			
		||||
        orthographic_projection: OrthographicProjection {
 | 
			
		||||
            scale: 0.01,
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
 | 
			
		||||
@ -38,10 +38,7 @@ fn setup(
 | 
			
		||||
    // Spawn the scene as a child of another entity. This first scene will be translated backward
 | 
			
		||||
    // with its parent
 | 
			
		||||
    commands
 | 
			
		||||
        .spawn_bundle((
 | 
			
		||||
            Transform::from_xyz(0.0, 0.0, -1.0),
 | 
			
		||||
            GlobalTransform::identity(),
 | 
			
		||||
        ))
 | 
			
		||||
        .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 0.0, -1.0)))
 | 
			
		||||
        .with_children(|parent| {
 | 
			
		||||
            parent.spawn_scene(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@ -60,7 +60,7 @@ fn spawn_tasks(mut commands: Commands, thread_pool: Res<AsyncComputeTaskPool>) {
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Such hard work, all done!
 | 
			
		||||
                    Transform::from_translation(Vec3::new(x as f32, y as f32, z as f32))
 | 
			
		||||
                    Transform::from_xyz(x as f32, y as f32, z as f32)
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // Spawn new entity and add our new task as a component
 | 
			
		||||
@ -107,13 +107,13 @@ fn setup_env(mut commands: Commands) {
 | 
			
		||||
 | 
			
		||||
    // lights
 | 
			
		||||
    commands.spawn_bundle(PointLightBundle {
 | 
			
		||||
        transform: Transform::from_translation(Vec3::new(4.0, 12.0, 15.0)),
 | 
			
		||||
        transform: Transform::from_xyz(4.0, 12.0, 15.0),
 | 
			
		||||
        ..Default::default()
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // camera
 | 
			
		||||
    commands.spawn_bundle(PerspectiveCameraBundle {
 | 
			
		||||
        transform: Transform::from_translation(Vec3::new(offset, offset, 15.0))
 | 
			
		||||
        transform: Transform::from_xyz(offset, offset, 15.0)
 | 
			
		||||
            .looking_at(Vec3::new(offset, offset, 0.0), Vec3::Y),
 | 
			
		||||
        ..Default::default()
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -117,10 +117,11 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
 | 
			
		||||
                .map(|i| {
 | 
			
		||||
                    let height = rand::thread_rng().gen_range(-0.1..0.1);
 | 
			
		||||
                    commands
 | 
			
		||||
                        .spawn_bundle((
 | 
			
		||||
                            Transform::from_xyz(i as f32, height - 0.2, j as f32),
 | 
			
		||||
                            GlobalTransform::identity(),
 | 
			
		||||
                        ))
 | 
			
		||||
                        .spawn_bundle(TransformBundle::from(Transform::from_xyz(
 | 
			
		||||
                            i as f32,
 | 
			
		||||
                            height - 0.2,
 | 
			
		||||
                            j as f32,
 | 
			
		||||
                        )))
 | 
			
		||||
                        .with_children(|cell| {
 | 
			
		||||
                            cell.spawn_scene(cell_scene.clone());
 | 
			
		||||
                        });
 | 
			
		||||
@ -133,8 +134,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
 | 
			
		||||
    // spawn the game character
 | 
			
		||||
    game.player.entity = Some(
 | 
			
		||||
        commands
 | 
			
		||||
            .spawn_bundle((
 | 
			
		||||
                Transform {
 | 
			
		||||
            .spawn_bundle(TransformBundle::from(Transform {
 | 
			
		||||
                translation: Vec3::new(
 | 
			
		||||
                    game.player.i as f32,
 | 
			
		||||
                    game.board[game.player.j][game.player.i].height,
 | 
			
		||||
@ -142,9 +142,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
 | 
			
		||||
                ),
 | 
			
		||||
                rotation: Quat::from_rotation_y(-std::f32::consts::FRAC_PI_2),
 | 
			
		||||
                ..Default::default()
 | 
			
		||||
                },
 | 
			
		||||
                GlobalTransform::identity(),
 | 
			
		||||
            ))
 | 
			
		||||
            }))
 | 
			
		||||
            .with_children(|cell| {
 | 
			
		||||
                cell.spawn_scene(asset_server.load("models/AlienCake/alien.glb#Scene0"));
 | 
			
		||||
            })
 | 
			
		||||
@ -324,17 +322,11 @@ fn spawn_bonus(
 | 
			
		||||
    }
 | 
			
		||||
    game.bonus.entity = Some(
 | 
			
		||||
        commands
 | 
			
		||||
            .spawn_bundle((
 | 
			
		||||
                Transform {
 | 
			
		||||
                    translation: Vec3::new(
 | 
			
		||||
            .spawn_bundle(TransformBundle::from(Transform::from_xyz(
 | 
			
		||||
                game.bonus.i as f32,
 | 
			
		||||
                game.board[game.bonus.j][game.bonus.i].height + 0.2,
 | 
			
		||||
                game.bonus.j as f32,
 | 
			
		||||
                    ),
 | 
			
		||||
                    ..Default::default()
 | 
			
		||||
                },
 | 
			
		||||
                GlobalTransform::identity(),
 | 
			
		||||
            ))
 | 
			
		||||
            )))
 | 
			
		||||
            .with_children(|children| {
 | 
			
		||||
                children.spawn_bundle(PointLightBundle {
 | 
			
		||||
                    point_light: PointLight {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user