//! This example illustrates loading scenes from files. use bevy::{prelude::*, tasks::IoTaskPool, utils::Duration}; use std::{fs::File, io::Write}; fn main() { App::new() .add_plugins(DefaultPlugins) .register_type::<ComponentA>() .register_type::<ComponentB>() .register_type::<ResourceA>() .add_systems( Startup, (save_scene_system, load_scene_system, infotext_system), ) .add_systems(Update, log_system) .run(); } // Registered components must implement the `Reflect` and `FromWorld` traits. // The `Reflect` trait enables serialization, deserialization, and dynamic property access. // `Reflect` enable a bunch of cool behaviors, so its worth checking out the dedicated `reflect.rs` // example. 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 // `FromWorld`). The simplest registered component just needs these three derives: #[derive(Component, Reflect, Default)] #[reflect(Component)] // this tells the reflect derive to also reflect component behaviors struct ComponentA { pub x: f32, pub y: f32, } // Some components have fields that cannot (or should not) be written to scene files. These can be // ignored with the #[reflect(skip_serializing)] attribute. This is also generally where the `FromWorld` // trait comes into play. `FromWorld` gives you access to your App's current ECS `Resources` // when you construct your component. #[derive(Component, Reflect)] #[reflect(Component)] struct ComponentB { pub value: String, #[reflect(skip_serializing)] pub _time_since_startup: Duration, } 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(), } } } // Resources can be serialized in scenes as well, with the same requirements `Component`s have. #[derive(Resource, Reflect, Default)] #[reflect(Resource)] struct ResourceA { pub score: u32, } // The initial scene file will be loaded below and not change when the scene is saved const SCENE_FILE_PATH: &str = "scenes/load_scene_example.scn.ron"; // The new, updated scene data will be saved here so that you can see the changes const NEW_SCENE_FILE_PATH: &str = "scenes/load_scene_example-new.scn.ron"; fn load_scene_system(mut commands: Commands, asset_server: Res<AssetServer>) { // Spawning a DynamicSceneRoot creates a new entity and spawns new instances // of the given scene's entities as children of that entity. // Scenes can be loaded just like any other asset. commands.spawn(DynamicSceneRoot(asset_server.load(SCENE_FILE_PATH))); } // This system logs all ComponentA components in our world. Try making a change to a ComponentA in // load_scene_example.scn. If you enable the `file_watcher` cargo feature you should immediately see // the changes appear in the console whenever you make a change. fn log_system( query: Query<(Entity, &ComponentA), Changed<ComponentA>>, res: Option<Res<ResourceA>>, ) { for (entity, component_a) in &query { info!(" Entity({})", entity.index()); info!( " ComponentA: {{ x: {} y: {} }}\n", component_a.x, component_a.y ); } if let Some(res) = res { if res.is_added() { info!(" New ResourceA: {{ score: {} }}\n", res.score); } } } 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. // For demonstration purposes, we'll create a new one. let mut scene_world = World::new(); // The `TypeRegistry` resource contains information about all registered types (including components). // This is used to construct scenes, so we'll want to ensure that our previous type registrations // exist in this new scene world as well. // To do this, we can simply clone the `AppTypeRegistry` resource. let type_registry = world.resource::<AppTypeRegistry>().clone(); scene_world.insert_resource(type_registry); 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, Name::new("joe"), )); scene_world.spawn(ComponentA { x: 3.0, y: 4.0 }); scene_world.insert_resource(ResourceA { score: 1 }); // With our sample world ready to go, we can now create our scene using DynamicScene or DynamicSceneBuilder. // For simplicity, we will create our scene using DynamicScene: let scene = DynamicScene::from_world(&scene_world); // Scenes can be serialized like this: let type_registry = world.resource::<AppTypeRegistry>(); let type_registry = type_registry.read(); let serialized_scene = scene.serialize(&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(); } // This is only necessary for the info message in the UI. See examples/ui/text.rs for a standalone // text example. fn infotext_system(mut commands: Commands) { commands.spawn(Camera2d); commands.spawn(( Text::new("Nothing to see in this window! Check the console output!"), TextFont { font_size: 42.0, ..default() }, Node { align_self: AlignSelf::FlexEnd, ..default() }, )); }