Skip to content

Hierarchy Child Global Transforms are Not Updated When Spawned as a Child #1354

Closed

Description

@zicklag

Bevy version

af67231

Operating system & version

Pop!_OS ( Ubuntu ) 20.04

What you did

Here is a complete, simple demonstration program. We spawn a parent sprite in the setup system, then in the load_child system, we spawn a child sprite with a (-50, -50) offset translation. We also have the move_parent system, which just moves the parent sprite.

use bevy::prelude::*;

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_startup_system(setup.system())
        // Comment out the move_parent line below to witness the bugginess 😜
        .add_system(move_parent.system())
        .add_system(load_child.system())
        .run();
}

struct TheParentEnt;
struct Loaded;

fn setup(
    commands: &mut Commands,
    asset_server: Res<AssetServer>,
    mut materials: ResMut<Assets<ColorMaterial>>,
) {
    // Spawn camera
    commands.spawn(Camera2dBundle::default());

    // Load parent sprite
    let texture = asset_server.load("textures/rpg/mobs/boss_bee.png");
    // Spawn parent
    commands
        .spawn(SpriteBundle {
            material: materials.add(ColorMaterial::texture(texture)),
            ..Default::default()
        })
        .with(TheParentEnt);
}

// Move the parent slowly to the left
fn move_parent(mut query: Query<&mut Transform, With<TheParentEnt>>) {
    for mut trans in query.iter_mut() {
        trans.translation.x -= 0.1;
    }
}

// Load the child sprite
fn load_child(
    commands: &mut Commands,
    query: Query<Entity, (Without<Loaded>, With<TheParentEnt>)>,
    asset_server: Res<AssetServer>,
    mut materials: ResMut<Assets<ColorMaterial>>,
) {
    for ent in query.iter() {
        // Spawn the child sprite
        let texture = asset_server.load("textures/rpg/chars/hat-guy/hat-guy.png");
        commands
            .spawn(SpriteBundle {
                material: materials.add(ColorMaterial::texture(texture)),
                transform: Transform::from_translation(Vec3::new(-50., -50., 0.)),
                ..Default::default()
            })
            .with(Parent(ent));

        // Prevent processing parent sprite again by marking it loaded
        commands.insert_one(ent, Loaded);
    }
}

What you expected to happen

When running the example below without changes, everything goes as expected, you have the child sprite moving along with the parent, but offset to the left and down from the parent sprite.

When commenting out the move_parent system, the only difference should be that the sprites aren't moving. The child sprite should still be offset to the left and down from the parent sprite.

What actually happened

When you comment out the move_parent system, when the child sprite is spawned, instead of being offset from the parent, it actually ends up in the center of the world.

Additional information

It seems that the GlobalTransform of the child sprite is not updated when it is spawned as a child, until the parent transform is actually changed. Thus, when the move_parent system is active, the child's transform is properly updated, but if the parent doesn't move, then the child's GlobalTransform is never updated and it remains in the center of the universe!

Activity

  1. zicklag commented on Jan 30, 2021

    @zicklag
    MemberAuthor

    I verified that it is an update issue by modifying the transform propagation system to always consider a transform component as having "changed" and it fixes the issue. Now I just have to figure out why the added child is not being considdered as a Changed<Transform> or a Changed<Parent>.

  2. Davier commented on Jan 30, 2021

    @Davier
    Contributor

    I think the issue comes from adding the Parent component directly. The system that automatically adds the corresponding Children component probably runs after the system that propagates the global transform, and that system relies on Children being properly set up.
    When spawning a child entity, try to use the with_children function instead of spawn.

  3. tigregalis commented on Jan 30, 2021

    @tigregalis
    Contributor

    You could do this:

    // Load the child sprite
    fn load_child(
        commands: &mut Commands,
        query: Query<Entity, (Without<Loaded>, With<TheParentEnt>)>,
        asset_server: Res<AssetServer>,
        mut materials: ResMut<Assets<ColorMaterial>>,
    ) {
        for ent in query.iter() {
            // Spawn the child sprite
            let texture = asset_server.load("textures/rpg/chars/hat-guy/hat-guy.png");
            let child = commands
                .spawn(SpriteBundle {
                    material: materials.add(ColorMaterial::texture(texture)),
                    transform: Transform::from_translation(Vec3::new(-50., -50., 0.)),
                    ..Default::default()
                })
                .with(Parent(ent))
                .current_entity()
                .unwrap();
    
            // as you've manually added the Parent component to the child, then
            // manually add the Children component to the parent
            commands.insert_one(ent, Children::with(&[child]));
    
            // Prevent processing parent sprite again by marking it loaded
            commands.insert_one(ent, Loaded);
        }
    }

    However, I believe the canonical way is:

    // Load the child sprite
    fn load_child(
        commands: &mut Commands,
        query: Query<Entity, (Without<Loaded>, With<TheParentEnt>)>,
        asset_server: Res<AssetServer>,
        mut materials: ResMut<Assets<ColorMaterial>>,
    ) {
        for ent in query.iter() {
            // Spawn the child sprite
            let texture = asset_server.load("textures/rpg/chars/hat-guy/hat-guy.png");
            let child = commands
                .spawn(SpriteBundle {
                    material: materials.add(ColorMaterial::texture(texture)),
                    transform: Transform::from_translation(Vec3::new(-50., -50., 0.)),
                    ..Default::default()
                })
    //            .with(Parent(ent))
                .current_entity()
                .unwrap();
            
            // Pushing takes a slice of children to add:
            // where ent: Entity, child: Entity
            commands.push_children(ent, &[child]);
    
            // Prevent processing parent sprite again by marking it loaded
            commands.insert_one(ent, Loaded);
        }
    }
  4. MinerSebas commented on Jan 30, 2021

    @MinerSebas
    Contributor

    I think the issue comes from adding the Parent component directly. The system that automatically adds the corresponding Children component probably runs after the system that propagates the global transform, and that system relies on Children being properly set up.
    When spawning a child entity, try to use the with_children function instead of spawn.

    Almost, the problem is, that the two are in the same Stage: See here for further Information #1198

  5. zicklag commented on Jan 30, 2021

    @zicklag
    MemberAuthor

    @tigregalis that worked!!! Thanks a lot! I spent all day yesterday wrestling with this, so it's really nice to get it working.

    I think I'll go ahead and close this because there is a solution and the weirdness should be worked out with the new hierarchy system comming in: #1198 (comment).

  6. added
    A-ECSEntities, components, systems, and events
    S-User-ErrorThis issue was caused by a mistake in the user's approach
    on Jan 30, 2021
  7. added
    C-UsabilityA targeted quality-of-life change that makes Bevy easier to use
    on Dec 12, 2021
  8. added
    A-TransformTranslations, rotations and scales
    and removed
    A-ECSEntities, components, systems, and events
    on Apr 4, 2022
  9. SUPERCILEX commented on May 9, 2022

    @SUPERCILEX
    Contributor

    @alice-i-cecile This can probably be closed in favor of #1807 or #4197

  10. james7132 commented on Oct 19, 2022

    @james7132
    Member

    Is this still an issue? #4197 has been merged. @zicklag if this is still happening and easy to run into despite these changes, please comment, otherwise please close this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-TransformTranslations, rotations and scalesC-UsabilityA targeted quality-of-life change that makes Bevy easier to useS-User-ErrorThis issue was caused by a mistake in the user's approach

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions