 9c5f5b8455
			
		
	
	
		9c5f5b8455
		
			
		
	
	
	
	
		
			
			This commit overhauls the documentation in the Bevy scene loading example. It adds thorough explanatory comments to guide new Rust and Bevy developers. The rewritten docs clarify how to: - Register types for reflection, enabling serialization and dynamic property access - Skip serializing certain fields with `#[reflect(skip_serializing)]` - Use `FromWorld` for components that require runtime initialization - Store and serialize `Resources` in scene files - Load scenes using a `DynamicSceneRoot` and handle updates in a system - Serialize a brand-new scene to a separate file asynchronously using `IoTaskPool` These additions aim to provide a clear, step-by-step reference that demonstrates how to implement a scene-based workflow, making it easier for beginners and experienced developers alike to use Bevy’s scene system effectively. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
		
			
				
	
	
		
			229 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			229 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| //! This example demonstrates how to load scene data from files and then dynamically
 | |
| //! apply that data to entities in your Bevy `World`. This includes spawning new
 | |
| //! entities and applying updates to existing ones. Scenes in Bevy encapsulate
 | |
| //! serialized and deserialized `Components` or `Resources` so that you can easily
 | |
| //! store, load, and manipulate data outside of a purely code-driven context.
 | |
| //!
 | |
| //! This example also shows how to do the following:
 | |
| //! * Register your custom types for reflection, which allows them to be serialized,
 | |
| //!   deserialized, and manipulated dynamically.
 | |
| //! * Skip serialization of fields you don't want stored in your scene files (like
 | |
| //!   runtime values that should always be computed dynamically).
 | |
| //! * Save a new scene to disk to show how it can be updated compared to the original
 | |
| //!   scene file (and how that updated scene file might then be used later on).
 | |
| //!
 | |
| //! The example proceeds by creating components and resources, registering their types,
 | |
| //! loading a scene from a file, logging when changes are detected, and finally saving
 | |
| //! a new scene file to disk. This is useful for anyone wanting to see how to integrate
 | |
| //! file-based scene workflows into their Bevy projects.
 | |
| //!
 | |
| //! # Note on working with files
 | |
| //!
 | |
| //! The saving behavior uses the standard filesystem APIs, which are blocking, so it
 | |
| //! utilizes a thread pool (`IoTaskPool`) to avoid stalling the main thread. This
 | |
| //! won't work on WASM because WASM typically doesn't have direct filesystem access.
 | |
| //!
 | |
| 
 | |
| use bevy::{prelude::*, tasks::IoTaskPool};
 | |
| use core::time::Duration;
 | |
| use std::{fs::File, io::Write};
 | |
| 
 | |
| /// The entry point of our Bevy app.
 | |
| ///
 | |
| /// Sets up default plugins, registers all necessary component/resource types
 | |
| /// for serialization/reflection, and runs the various systems in the correct schedule.
 | |
| 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();
 | |
| }
 | |
| 
 | |
| /// # Components, Resources, and Reflection
 | |
| ///
 | |
| /// Below are some simple examples of how to define your own Bevy `Component` types
 | |
| /// and `Resource` types so that they can be properly reflected, serialized, and
 | |
| /// deserialized. The `#[derive(Reflect)]` macro enables Bevy's reflection features,
 | |
| /// and we add component-specific reflection by using `#[reflect(Component)]`.
 | |
| /// We also illustrate how to skip serializing fields and how `FromWorld` can help
 | |
| /// create runtime-initialized data.
 | |
| ///
 | |
| /// A sample component that is fully serializable.
 | |
| ///
 | |
| /// This component has public `x` and `y` fields that will be included in
 | |
| /// the scene files. Notice how it derives `Default`, `Reflect`, and declares
 | |
| /// itself as a reflected component with `#[reflect(Component)]`.
 | |
| #[derive(Component, Reflect, Default)]
 | |
| #[reflect(Component)] // this tells the reflect derive to also reflect component behaviors
 | |
| struct ComponentA {
 | |
|     /// An example `f32` field
 | |
|     pub x: f32,
 | |
|     /// Another example `f32` field
 | |
|     pub y: f32,
 | |
| }
 | |
| 
 | |
| /// A sample component that includes both serializable and non-serializable fields.
 | |
| ///
 | |
| /// This is useful for skipping serialization of runtime data or fields you
 | |
| /// don't want written to scene files.
 | |
| #[derive(Component, Reflect)]
 | |
| #[reflect(Component)]
 | |
| struct ComponentB {
 | |
|     /// A string field that will be serialized.
 | |
|     pub value: String,
 | |
|     /// A `Duration` field that should never be serialized to the scene file, so we skip it.
 | |
|     #[reflect(skip_serializing)]
 | |
|     pub _time_since_startup: Duration,
 | |
| }
 | |
| 
 | |
| /// This implements `FromWorld` for `ComponentB`, letting us initialize runtime fields
 | |
| /// by accessing the current ECS resources. In this case, we acquire the `Time` resource
 | |
| /// and store the current elapsed time.
 | |
| 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(),
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// A simple resource that also derives `Reflect`, allowing it to be stored in scenes.
 | |
| ///
 | |
| /// Just like a component, you can skip serializing fields or implement `FromWorld` if needed.
 | |
| #[derive(Resource, Reflect, Default)]
 | |
| #[reflect(Resource)]
 | |
| struct ResourceA {
 | |
|     /// This resource tracks a `score` value.
 | |
|     pub score: u32,
 | |
| }
 | |
| 
 | |
| /// # Scene File Paths
 | |
| ///
 | |
| /// `SCENE_FILE_PATH` points to the original scene file that we'll be loading.
 | |
| /// `NEW_SCENE_FILE_PATH` points to the new scene file that we'll be creating
 | |
| /// (and demonstrating how to serialize to disk).
 | |
| ///
 | |
| /// 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";
 | |
| 
 | |
| /// Loads a scene from an asset file and spawns it in the current world.
 | |
| ///
 | |
| /// Spawning a `DynamicSceneRoot` creates a new parent entity, which then spawns new
 | |
| /// instances of the scene's entities as its children. If you modify the
 | |
| /// `SCENE_FILE_PATH` scene file, or if you enable file watching, you can see
 | |
| /// changes reflected immediately.
 | |
| fn load_scene_system(mut commands: Commands, asset_server: Res<AssetServer>) {
 | |
|     commands.spawn(DynamicSceneRoot(asset_server.load(SCENE_FILE_PATH)));
 | |
| }
 | |
| 
 | |
| /// Logs changes made to `ComponentA` entities, and also checks whether `ResourceA`
 | |
| /// has been recently added.
 | |
| ///
 | |
| /// Any time a `ComponentA` is modified, that change will appear here. This system
 | |
| /// demonstrates how you might detect and handle scene updates at runtime.
 | |
| 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);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Demonstrates how to create a new scene from scratch, populate it with data,
 | |
| /// and then serialize it to a file. The new file is written to `NEW_SCENE_FILE_PATH`.
 | |
| ///
 | |
| /// This system creates a fresh world, duplicates the type registry so that our
 | |
| /// custom component types are recognized, spawns some sample entities and resources,
 | |
| /// and then serializes the resulting dynamic scene.
 | |
| 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();
 | |
| }
 | |
| 
 | |
| /// Spawns a simple 2D camera and some text indicating that the user should
 | |
| /// check the console output for scene loading/saving messages.
 | |
| ///
 | |
| /// This system is only necessary for the info message in the UI.
 | |
| 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()
 | |
|         },
 | |
|     ));
 | |
| }
 |