Tainted \\ Coders

Bevy Scenes

Last updated:

Scenes are file based representations of a particular world setup. Components and Resources are serialized into a file and then reinitialized when the scene is loaded.

// The TypeRegistry resource contains information about all registered types (including
// components). This is used to construct scenes.
let type_registry = world.resource::<AppTypeRegistry>();
let scene = DynamicScene::from_world(&scene_world, type_registry);

// Scenes can be serialized like this:
let serialized_scene = scene.serialize_ron(type_registry).unwrap();

Components registered with register_type in scenes must implement the Reflect and FromWorld traits.

The FromWorld trait determines how your component is constructed when it loads.

For simple use cases you can just implement the Default trait (which automatically implements FromResources).

The #[reflect(skip_serializing)] attribute can be used for fields which should not be serialized in the scene.

Implementing FromWorld on a component will let you customize initialization using the current Worlds resources:

impl FromWorld for ComponentB {
    fn from_world(world: &mut World) -> Self {
        let time = world.resource::<Time>();
        ComponentB {
            _time_since_startup: time.elapsed(),
            value: "Default Value".to_string(),
        }
    }
}

We can load from a scene file:

fn load_scene_system(mut commands: Commands, asset_server: Res<AssetServer>) {
    // "Spawning" a scene bundle creates a new entity and spawns new instances
    // of the given scene's entities as children of that entity.
    commands.spawn(DynamicSceneBundle {
        // Scenes are loaded just like any other asset.
        scene: asset_server.load(SCENE_FILE_PATH),
        ..default()
    });
}

When we load a scene Bevy is creating a new World parallel to your own and copies the data into your main world. You can never access this parallel world but it stays around.

We can save scenes to a file:

fn save_scene_system(world: &mut World) {
    // Scenes can be created from any ECS World. You can either create a new one for the scene or
    // use the current World.
    let mut scene_world = World::new();
    let mut component_b = ComponentB::from_world(world);
    component_b.value = "hello".to_string();
    scene_world.spawn((
        component_b,
        ComponentA { x: 1.0, y: 2.0 },
        Transform::IDENTITY,
    ));
    scene_world.spawn(ComponentA { x: 3.0, y: 4.0 });
    scene_world.insert_resource(ResourceA { score: 1 });

    // The TypeRegistry resource contains information about all registered types (including
    // components). This is used to construct scenes.
    let type_registry = world.resource::<AppTypeRegistry>();
    let scene = DynamicScene::from_world(&scene_world, type_registry);

    // Scenes can be serialized like this:
    let serialized_scene = scene.serialize_ron(type_registry).unwrap();

    // Showing the scene in the console
    info!("{}", serialized_scene);

    // Writing the scene to a new file. Using a task to avoid calling the filesystem APIs in a system
    // as they are blocking
    // This can't work in WASM as there is no filesystem access
    #[cfg(not(target_arch = "wasm32"))]
    IoTaskPool::get()
        .spawn(async move {
            // Write the scene RON data to file
            File::create(format!("assets/{NEW_SCENE_FILE_PATH}"))
                .and_then(|mut file| file.write(serialized_scene.as_bytes()))
                .expect("Error while writing scene to file");
        })
        .detach();
}

Read more