//! This example demonstrates how to load scene data from files and then dynamically1//! apply that data to entities in your Bevy `World`. This includes spawning new2//! entities and applying updates to existing ones. Scenes in Bevy encapsulate3//! serialized and deserialized `Components` or `Resources` so that you can easily4//! store, load, and manipulate data outside of a purely code-driven context.5//!6//! This example also shows how to do the following:7//! * Register your custom types for reflection, which allows them to be serialized,8//! deserialized, and manipulated dynamically.9//! * Skip serialization of fields you don't want stored in your scene files (like10//! runtime values that should always be computed dynamically).11//! * Save a new scene to disk to show how it can be updated compared to the original12//! scene file (and how that updated scene file might then be used later on).13//!14//! The example proceeds by creating components and resources, registering their types,15//! loading a scene from a file, logging when changes are detected, and finally saving16//! a new scene file to disk. This is useful for anyone wanting to see how to integrate17//! file-based scene workflows into their Bevy projects.18//!19//! # Note on working with files20//!21//! The saving behavior uses the standard filesystem APIs, which are blocking, so it22//! utilizes a thread pool (`IoTaskPool`) to avoid stalling the main thread. This23//! won't work on WASM because WASM typically doesn't have direct filesystem access.24//!2526use bevy::{asset::LoadState, prelude::*, tasks::IoTaskPool};27use core::time::Duration;28use std::{fs::File, io::Write};2930/// The entry point of our Bevy app.31///32/// Sets up default plugins, registers all necessary component/resource types33/// for serialization/reflection, and runs the various systems in the correct schedule.34fn main() {35App::new()36.add_plugins(DefaultPlugins)37.add_systems(38Startup,39(save_scene_system, load_scene_system, infotext_system),40)41.add_systems(Update, (log_system, panic_on_fail))42.run();43}4445/// # Components, Resources, and Reflection46///47/// Below are some simple examples of how to define your own Bevy `Component` types48/// and `Resource` types so that they can be properly reflected, serialized, and49/// deserialized. The `#[derive(Reflect)]` macro enables Bevy's reflection features,50/// and we add component-specific reflection by using `#[reflect(Component)]`.51/// We also illustrate how to skip serializing fields and how `FromWorld` can help52/// create runtime-initialized data.53///54/// A sample component that is fully serializable.55///56/// This component has public `x` and `y` fields that will be included in57/// the scene files. Notice how it derives `Default`, `Reflect`, and declares58/// itself as a reflected component with `#[reflect(Component)]`.59#[derive(Component, Reflect, Default)]60#[reflect(Component)] // this tells the reflect derive to also reflect component behaviors61struct ComponentA {62/// An example `f32` field63pub x: f32,64/// Another example `f32` field65pub y: f32,66}6768/// A sample component that includes both serializable and non-serializable fields.69///70/// This is useful for skipping serialization of runtime data or fields you71/// don't want written to scene files.72#[derive(Component, Reflect)]73#[reflect(Component)]74struct ComponentB {75/// A string field that will be serialized.76pub value: String,77/// A `Duration` field that should never be serialized to the scene file, so we skip it.78#[reflect(skip_serializing)]79pub _time_since_startup: Duration,80}8182/// This implements `FromWorld` for `ComponentB`, letting us initialize runtime fields83/// by accessing the current ECS resources. In this case, we acquire the `Time` resource84/// and store the current elapsed time.85impl FromWorld for ComponentB {86fn from_world(world: &mut World) -> Self {87let time = world.resource::<Time>();88ComponentB {89_time_since_startup: time.elapsed(),90value: "Default Value".to_string(),91}92}93}9495/// A simple resource that also derives `Reflect`, allowing it to be stored in scenes.96///97/// Just like a component, you can skip serializing fields or implement `FromWorld` if needed.98#[derive(Resource, Reflect, Default)]99#[reflect(Resource)]100struct ResourceA {101/// This resource tracks a `score` value.102pub score: u32,103}104105/// # Scene File Paths106///107/// `SCENE_FILE_PATH` points to the original scene file that we'll be loading.108/// `NEW_SCENE_FILE_PATH` points to the new scene file that we'll be creating109/// (and demonstrating how to serialize to disk).110///111/// The initial scene file will be loaded below and not change when the scene is saved.112const SCENE_FILE_PATH: &str = "scenes/load_scene_example.scn.ron";113114/// The new, updated scene data will be saved here so that you can see the changes.115const NEW_SCENE_FILE_PATH: &str = "scenes/load_scene_example-new.scn.ron";116117/// Loads a scene from an asset file and spawns it in the current world.118///119/// Spawning a `DynamicSceneRoot` creates a new parent entity, which then spawns new120/// instances of the scene's entities as its children. If you modify the121/// `SCENE_FILE_PATH` scene file, or if you enable file watching, you can see122/// changes reflected immediately.123fn load_scene_system(mut commands: Commands, asset_server: Res<AssetServer>) {124commands.spawn(DynamicSceneRoot(asset_server.load(SCENE_FILE_PATH)));125}126127/// Logs changes made to `ComponentA` entities, and also checks whether `ResourceA`128/// has been recently added.129///130/// Any time a `ComponentA` is modified, that change will appear here. This system131/// demonstrates how you might detect and handle scene updates at runtime.132fn log_system(133query: Query<(Entity, &ComponentA), Changed<ComponentA>>,134res: Option<Res<ResourceA>>,135) {136for (entity, component_a) in &query {137info!(" Entity({})", entity.index());138info!(139" ComponentA: {{ x: {} y: {} }}\n",140component_a.x, component_a.y141);142}143if let Some(res) = res144&& res.is_added()145{146info!(" New ResourceA: {{ score: {} }}\n", res.score);147}148}149150/// Demonstrates how to create a new scene from scratch, populate it with data,151/// and then serialize it to a file. The new file is written to `NEW_SCENE_FILE_PATH`.152///153/// This system creates a fresh world, duplicates the type registry so that our154/// custom component types are recognized, spawns some sample entities and resources,155/// and then serializes the resulting dynamic scene.156fn save_scene_system(world: &mut World) {157// Scenes can be created from any ECS World.158// You can either create a new one for the scene or use the current World.159// For demonstration purposes, we'll create a new one.160let mut scene_world = World::new();161162// The `TypeRegistry` resource contains information about all registered types (including components).163// This is used to construct scenes, so we'll want to ensure that our previous type registrations164// exist in this new scene world as well.165// To do this, we can simply clone the `AppTypeRegistry` resource.166let type_registry = world.resource::<AppTypeRegistry>().clone();167scene_world.insert_resource(type_registry);168169let mut component_b = ComponentB::from_world(world);170component_b.value = "hello".to_string();171scene_world.spawn((172component_b,173ComponentA { x: 1.0, y: 2.0 },174Transform::IDENTITY,175Name::new("joe"),176));177scene_world.spawn(ComponentA { x: 3.0, y: 4.0 });178scene_world.insert_resource(ResourceA { score: 1 });179180// With our sample world ready to go, we can now create our scene using DynamicScene or DynamicSceneBuilder.181// For simplicity, we will create our scene using DynamicScene:182let scene = DynamicScene::from_world(&scene_world);183184// Scenes can be serialized like this:185let type_registry = world.resource::<AppTypeRegistry>();186let type_registry = type_registry.read();187let serialized_scene = scene.serialize(&type_registry).unwrap();188189// Showing the scene in the console190info!("{}", serialized_scene);191192// Writing the scene to a new file. Using a task to avoid calling the filesystem APIs in a system193// as they are blocking.194//195// This can't work in Wasm as there is no filesystem access.196#[cfg(not(target_arch = "wasm32"))]197IoTaskPool::get()198.spawn(async move {199// Write the scene RON data to file200File::create(format!("assets/{NEW_SCENE_FILE_PATH}"))201.and_then(|mut file| file.write(serialized_scene.as_bytes()))202.expect("Error while writing scene to file");203})204.detach();205}206207/// Spawns a simple 2D camera and some text indicating that the user should208/// check the console output for scene loading/saving messages.209///210/// This system is only necessary for the info message in the UI.211fn infotext_system(mut commands: Commands) {212commands.spawn(Camera2d);213commands.spawn((214Text::new("Nothing to see in this window! Check the console output!"),215TextFont {216font_size: 42.0,217..default()218},219Node {220align_self: AlignSelf::FlexEnd,221..default()222},223));224}225226/// To help with Bevy's automated testing, we want the example to close with an appropriate if the227/// scene fails to load. This is most likely not something you want in your own app.228fn panic_on_fail(scenes: Query<&DynamicSceneRoot>, asset_server: Res<AssetServer>) {229for scene in &scenes {230if let Some(LoadState::Failed(err)) = asset_server.get_load_state(&scene.0) {231panic!("Failed to load scene. {err}");232}233}234}235236237