1

In a Bevy project with the following scene file called my_scene.scn:

[
  (
    entity: 0,
    components: [
      {
        "type": "Transform",
        "map": {
          "translation": {
            "type": "Vec3", 
            "value": (150.0, 200.0, 0.0),
          },
          "rotation": {
            "type": "Quat",
            "value": (0.0, 0.0, 0.0, 1.0),
          },
          "scale": {
            "type": "Vec3",
            "value": (0.75, 0.75, 0.0),
          },
        },
      },
    ]
  )
]

And with this source code following this official Bevy example:

use bevy::prelude::*;

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_startup_system(load_scene_system.system())
        .run();
}

fn load_scene_system(asset_server: Res<AssetServer>, mut scene_spawner: ResMut<SceneSpawner>) {
    let scene_handle: Handle<Scene> = asset_server.load("my_scene.scn");

    scene_spawner.spawn_dynamic(scene_handle);
    // ^ How to add components to entities created here ?

    asset_server.watch_for_changes().unwrap();
}

I would like to add other components to this entity 0, for example a SpriteComponents. How do I achieve this?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Didi Bear
  • 356
  • 3
  • 13
  • Scenes shouldn't be edited. In that scenes should be defined, then exported to disk, and if a certain scene needs something new (like a new entity, or another component) that should be reflected on disk, not done when loading the scene. Also, Bevy uses `hecs` as it's ECS implementation, which is an archetypal implementation. Meaning that mutating an entity is expensive and should be avoided if possible – MindSwipe Nov 12 '20 at 13:52
  • Do you mean that scene should be self-contained ? – Didi Bear Nov 13 '20 at 03:55
  • Just not edited at runtime. Say you're creating a scene that represents the players house, you got a chair, a desk, and a bed in it. But now, after some testing, you want to add a dresser to the house as well. You wouldn't add that dresser dynamically at runtime, you'd add it inside the editor once and then save the edited scene to disk. – MindSwipe Nov 13 '20 at 07:15
  • Ah ok I understand :+1: Basically what I wanted is something for creating components that cannot be expressed in the scene file, like asset handles. I have found a solution by having an intermediate component as you can see in the answer I write below. – Didi Bear Nov 13 '20 at 15:50
  • 1
    Ah I see now. I didn't quite understand *why* you needed it, and I see now that your use case us warranted. It seems like the Bevy team is actively working on the scene system as being tracked [here](https://github.com/bevyengine/bevy/issues/255) – MindSwipe Nov 16 '20 at 07:16

1 Answers1

3

Here is a solution I came up with. The idea is to store a component that contains the information to creation a more dynamic component.

For instance, to dynamically load a sprite image referred in the scene file, we can define a SpriteLoader component and then add a system that will replaces this loader component with the components we want.

use bevy::prelude::*;

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .register_component::<SpriteLoader>()
        .add_startup_system(load_scene_system.system())
        .add_system(load_sprite_system.system())
        .run();
}

fn load_scene_system(asset_server: Res<AssetServer>, mut scene_spawner: ResMut<SceneSpawner>) {
    let scene_handle: Handle<DynamicScene> = asset_server.load("my_scene.scn");
    scene_spawner.spawn_dynamic(scene_handle);
    asset_server.watch_for_changes().unwrap();
}

/// Component indicating that a sprite will be loaded for the entity.
#[derive(Properties, Default)]
pub struct SpriteLoader {
    /// Path of the sprite to load
    pub path: String
}

/// Replaces the `SpritLoader` component with the corresponding `SpriteComponents`.
pub fn load_sprite_system(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    mut materials: ResMut<Assets<ColorMaterial>>,
    query: Query<(Entity, Added<SpriteLoader>)>,
) {
    for (entity, sprite_loader) in query.iter() {
        commands.remove_one::<SpriteLoader>(entity);

        let path = PathBuf::from(sprite_loader.path.clone());

        commands.insert(
            entity,
            SpriteComponents {
                material: materials.add(asset_server.load(path).into()),
                ..SpriteComponents::default()
            },
        );
    }
}

Here, once a SpriteLoader is added to the entity, the load_sprite_system system will remove it and insert a SpriteComponents instead. As per the doc of insert, this will replace the previous SpriteComponents.

The corresponding scene file is:

[
  (
    entity: 0,
    components: [
      {
        "type": "SpriteLoader",
        "map": { 
          "path": "my_sprite.png",
        },
      },
    ]
  )
]
Didi Bear
  • 356
  • 3
  • 13