docs/bevy/examples/scene/world_serialization.rs 10 K · 243 lines · raw · history
| 1 | //! This example demonstrates how to load world data from files and then dynamically |
| 2 | //! apply that data to entities in your Bevy `World`. This includes spawning new |
| 3 | //! entities and applying updates to existing ones. World serialization in Bevy |
| 4 | //! serializes and deserializes `Components` and `Resources` so that you can easily |
| 5 | //! store, load, and manipulate data outside of a purely code-driven context. |
| 6 | //! |
| 7 | //! This example also shows how to do the following: |
| 8 | //! * Register your custom types for reflection, which allows them to be serialized, |
| 9 | //! deserialized, and manipulated dynamically. |
| 10 | //! * Skip serialization of fields you don't want stored in your world files (like |
| 11 | //! runtime values that should always be computed dynamically). |
| 12 | //! * Save a new world to disk to show how it can be updated compared to the original |
| 13 | //! world file (and how that updated world file might then be used later on). |
| 14 | //! |
| 15 | //! The example proceeds by creating components and resources, registering their types, |
| 16 | //! loading a world from a file, logging when changes are detected, and finally saving |
| 17 | //! a new world file to disk. This is useful for anyone wanting to see how to integrate |
| 18 | //! file-based ECS workflows into their Bevy projects. |
| 19 | //! |
| 20 | //! # Note on working with files |
| 21 | //! |
| 22 | //! The saving behavior uses the standard filesystem APIs, which are blocking, so it |
| 23 | //! utilizes a thread pool (`IoTaskPool`) to avoid stalling the main thread. This |
| 24 | //! won't work on WASM because WASM typically doesn't have direct filesystem access. |
| 25 | //! |
| 26 | |
| 27 | use bevy::{asset::LoadState, prelude::*, tasks::IoTaskPool}; |
| 28 | |
| 29 | use core::time::Duration; |
| 30 | use std::{fs::File, io::Write}; |
| 31 | |
| 32 | /// The entry point of our Bevy app. |
| 33 | /// |
| 34 | /// Sets up default plugins, registers all necessary component/resource types |
| 35 | /// for serialization/reflection, and runs the various systems in the correct schedule. |
| 36 | fn main() { |
| 37 | App::new() |
| 38 | .add_plugins(DefaultPlugins) |
| 39 | .add_systems( |
| 40 | Startup, |
| 41 | (save_world_system, load_world_system, infotext_system), |
| 42 | ) |
| 43 | .add_systems(Update, (log_system, panic_on_fail)) |
| 44 | .run(); |
| 45 | } |
| 46 | |
| 47 | /// # Components, Resources, and Reflection |
| 48 | /// |
| 49 | /// Below are some simple examples of how to define your own Bevy `Component` types |
| 50 | /// and `Resource` types so that they can be properly reflected, serialized, and |
| 51 | /// deserialized. The `#[derive(Reflect)]` macro enables Bevy's reflection features, |
| 52 | /// and we add component-specific reflection by using `#[reflect(Component)]`. |
| 53 | /// We also illustrate how to skip serializing fields and how `FromWorld` can help |
| 54 | /// create runtime-initialized data. |
| 55 | /// |
| 56 | /// A sample component that is fully serializable. |
| 57 | /// |
| 58 | /// This component has public `x` and `y` fields that will be included in |
| 59 | /// the world files. Notice how it derives `Default`, `Reflect`, and declares |
| 60 | /// itself as a reflected component with `#[reflect(Component)]`. |
| 61 | #[derive(Component, Reflect, Default)] |
| 62 | #[reflect(Component)] // this tells the reflect derive to also reflect component behaviors |
| 63 | struct ComponentA { |
| 64 | /// An example `f32` field |
| 65 | pub x: f32, |
| 66 | /// Another example `f32` field |
| 67 | pub y: f32, |
| 68 | } |
| 69 | |
| 70 | /// A sample component that includes both serializable and non-serializable fields. |
| 71 | /// |
| 72 | /// This is useful for skipping serialization of runtime data or fields you |
| 73 | /// don't want written to world files. |
| 74 | #[derive(Component, Reflect)] |
| 75 | #[reflect(Component)] |
| 76 | struct ComponentB { |
| 77 | /// A string field that will be serialized. |
| 78 | pub value: String, |
| 79 | /// A `Duration` field that should never be serialized to the world file, so we skip it. |
| 80 | #[reflect(skip_serializing)] |
| 81 | pub _time_since_startup: Duration, |
| 82 | } |
| 83 | |
| 84 | /// This implements `FromWorld` for `ComponentB`, letting us initialize runtime fields |
| 85 | /// by accessing the current ECS resources. In this case, we acquire the `Time` resource |
| 86 | /// and store the current elapsed time. |
| 87 | impl FromWorld for ComponentB { |
| 88 | fn from_world(world: &mut World) -> Self { |
| 89 | let time = world.resource::<Time>(); |
| 90 | ComponentB { |
| 91 | _time_since_startup: time.elapsed(), |
| 92 | value: "Default Value".to_string(), |
| 93 | } |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | /// A simple resource that also derives `Reflect`, allowing it to be stored in world files. |
| 98 | /// |
| 99 | /// Just like a component, you can skip serializing fields or implement `FromWorld` if needed. |
| 100 | #[derive(Resource, Reflect, Default)] |
| 101 | #[reflect(Resource)] |
| 102 | struct ResourceA { |
| 103 | /// This resource tracks a `score` value. |
| 104 | pub score: u32, |
| 105 | } |
| 106 | |
| 107 | /// # World File Paths |
| 108 | /// |
| 109 | /// `WORLD_FILE_PATH` points to the original world file that we'll be loading. |
| 110 | /// `NEW_WORLD_FILE_PATH` points to the new world file that we'll be creating |
| 111 | /// (and demonstrating how to serialize to disk). |
| 112 | /// |
| 113 | /// The initial world file will be loaded below and not change when the world is saved. |
| 114 | const WORLD_FILE_PATH: &str = "serialized_worlds/load_scene_example.scn.ron"; |
| 115 | |
| 116 | /// The new, updated world data will be saved here so that you can see the changes. |
| 117 | const NEW_WORLD_FILE_PATH: &str = "serialized_worlds/load_scene_example-new.scn.ron"; |
| 118 | |
| 119 | /// Loads a world from an asset file and spawns it in the current world. |
| 120 | /// |
| 121 | /// Spawning a `DynamicWorldRoot` creates a new parent entity, which then spawns new |
| 122 | /// instances of the world's entities as its children. If you modify the |
| 123 | /// `WORLD_FILE_PATH` file, or if you enable file watching, you can see |
| 124 | /// changes reflected immediately. |
| 125 | fn load_world_system(mut commands: Commands, asset_server: Res<AssetServer>) { |
| 126 | commands.spawn(DynamicWorldRoot(asset_server.load(WORLD_FILE_PATH))); |
| 127 | commands.spawn(( |
| 128 | Camera3d::default(), |
| 129 | Transform::from_xyz(1.0, 1.0, 1.0).looking_at(Vec3::new(0.0, 0.25, 0.0), Vec3::Y), |
| 130 | )); |
| 131 | commands.spawn(( |
| 132 | DirectionalLight::default(), |
| 133 | Transform::default().looking_to(Vec3::new(0.0, -1.0, -1.0), Vec3::Y), |
| 134 | )); |
| 135 | } |
| 136 | |
| 137 | /// Logs changes made to `ComponentA` entities, and also checks whether `ResourceA` |
| 138 | /// has been recently added. |
| 139 | /// |
| 140 | /// Any time a `ComponentA` is modified, that change will appear here. This system |
| 141 | /// demonstrates how you might detect and handle world updates at runtime. |
| 142 | fn log_system( |
| 143 | query: Query<(Entity, &ComponentA), Changed<ComponentA>>, |
| 144 | res: Option<Res<ResourceA>>, |
| 145 | ) { |
| 146 | for (entity, component_a) in &query { |
| 147 | info!(" Entity({})", entity.index()); |
| 148 | info!( |
| 149 | " ComponentA: {{ x: {} y: {} }}\n", |
| 150 | component_a.x, component_a.y |
| 151 | ); |
| 152 | } |
| 153 | if let Some(res) = res |
| 154 | && res.is_added() |
| 155 | { |
| 156 | info!(" New ResourceA: {{ score: {} }}\n", res.score); |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | /// Demonstrates how to create a new world from scratch, populate it with data, |
| 161 | /// and then serialize it to a file. The new file is written to `NEW_WORLD_FILE_PATH`. |
| 162 | /// |
| 163 | /// This system creates a fresh world, duplicates the type registry so that our |
| 164 | /// custom component types are recognized, spawns some sample entities and resources, |
| 165 | /// and then serializes the resulting dynamic world. |
| 166 | fn save_world_system(world: &mut World) { |
| 167 | let asset_server = world.resource::<AssetServer>().clone(); |
| 168 | // The `TypeRegistry` resource contains information about all registered types (including components). |
| 169 | // This is used to construct worlds, so we'll want to ensure that we use the registry from the |
| 170 | // main world. To do this, we can simply clone the `AppTypeRegistry` resource. |
| 171 | let type_registry = world.resource::<AppTypeRegistry>().clone(); |
| 172 | |
| 173 | // Any ECS World can be serialized. |
| 174 | // For demonstration purposes, we'll create a new one. |
| 175 | let mut scene_world = World::new(); |
| 176 | |
| 177 | let mut component_b = ComponentB::from_world(world); |
| 178 | component_b.value = "hello".to_string(); |
| 179 | scene_world.spawn(( |
| 180 | component_b, |
| 181 | ComponentA { x: 1.0, y: 2.0 }, |
| 182 | Transform::IDENTITY, |
| 183 | Name::new("joe"), |
| 184 | WorldAssetRoot(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0")), |
| 185 | )); |
| 186 | scene_world.spawn(ComponentA { x: 3.0, y: 4.0 }); |
| 187 | scene_world.insert_resource(ResourceA { score: 1 }); |
| 188 | |
| 189 | // With our sample world ready to go, we can now create a DynamicWorld from it. |
| 190 | // For simplicity, we will create our scene using DynamicWorld directly, but if |
| 191 | // you need more control, you can use DynamicWorldBuilder. |
| 192 | let dynamic_world = DynamicWorld::from_world_with(&scene_world, &type_registry.read()); |
| 193 | |
| 194 | // Dynamic Worlds can be serialized like this: |
| 195 | let type_registry = world.resource::<AppTypeRegistry>(); |
| 196 | let type_registry = type_registry.read(); |
| 197 | let serialized_world = dynamic_world.serialize(&type_registry).unwrap(); |
| 198 | |
| 199 | // Shows the serialized world in the console |
| 200 | info!("{}", serialized_world); |
| 201 | |
| 202 | // Writing the world to a new file. Using a task to avoid calling the filesystem APIs in a system |
| 203 | // as they are blocking. |
| 204 | // |
| 205 | // This can't work in Wasm as there is no filesystem access. |
| 206 | #[cfg(not(target_arch = "wasm32"))] |
| 207 | IoTaskPool::get() |
| 208 | .spawn(async move { |
| 209 | // Write the world RON data to file |
| 210 | File::create(format!("assets/{NEW_WORLD_FILE_PATH}")) |
| 211 | .and_then(|mut file| file.write(serialized_world.as_bytes())) |
| 212 | .expect("Error while writing world to file"); |
| 213 | }) |
| 214 | .detach(); |
| 215 | } |
| 216 | |
| 217 | /// Spawns a simple 2D camera and some text indicating that the user should |
| 218 | /// check the console output for world loading/saving messages. |
| 219 | /// |
| 220 | /// This system is only necessary for the info message in the UI. |
| 221 | fn infotext_system(mut commands: Commands) { |
| 222 | commands.spawn(( |
| 223 | Text::new("Check the console output for more!"), |
| 224 | TextFont { |
| 225 | font_size: FontSize::Px(42.0), |
| 226 | ..default() |
| 227 | }, |
| 228 | Node { |
| 229 | align_self: AlignSelf::FlexEnd, |
| 230 | ..default() |
| 231 | }, |
| 232 | )); |
| 233 | } |
| 234 | |
| 235 | /// To help with Bevy's automated testing, we want the example to close with an appropriate if the |
| 236 | /// world asset fails to load. This is most likely not something you want in your own app. |
| 237 | fn panic_on_fail(world_roots: Query<&DynamicWorldRoot>, asset_server: Res<AssetServer>) { |
| 238 | for world_root in &world_roots { |
| 239 | if let Some(LoadState::Failed(err)) = asset_server.get_load_state(&world_root.0) { |
| 240 | panic!("Failed to load world. {err}"); |
| 241 | } |
| 242 | } |
| 243 | } |