scene/
scene.rs

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