scene/
scene.rs

1//! This example illustrates loading scenes from files.
2use bevy::{prelude::*, tasks::IoTaskPool, utils::Duration};
3use std::{fs::File, io::Write};
4
5fn main() {
6    App::new()
7        .add_plugins(DefaultPlugins)
8        .register_type::<ComponentA>()
9        .register_type::<ComponentB>()
10        .register_type::<ResourceA>()
11        .add_systems(
12            Startup,
13            (save_scene_system, load_scene_system, infotext_system),
14        )
15        .add_systems(Update, log_system)
16        .run();
17}
18
19// Registered components must implement the `Reflect` and `FromWorld` traits.
20// The `Reflect` trait enables serialization, deserialization, and dynamic property access.
21// `Reflect` enable a bunch of cool behaviors, so its worth checking out the dedicated `reflect.rs`
22// example. The `FromWorld` trait determines how your component is constructed when it loads.
23// For simple use cases you can just implement the `Default` trait (which automatically implements
24// `FromWorld`). The simplest registered component just needs these three derives:
25#[derive(Component, Reflect, Default)]
26#[reflect(Component)] // this tells the reflect derive to also reflect component behaviors
27struct ComponentA {
28    pub x: f32,
29    pub y: f32,
30}
31
32// Some components have fields that cannot (or should not) be written to scene files. These can be
33// ignored with the #[reflect(skip_serializing)] attribute. This is also generally where the `FromWorld`
34// trait comes into play. `FromWorld` gives you access to your App's current ECS `Resources`
35// when you construct your component.
36#[derive(Component, Reflect)]
37#[reflect(Component)]
38struct ComponentB {
39    pub value: String,
40    #[reflect(skip_serializing)]
41    pub _time_since_startup: Duration,
42}
43
44impl FromWorld for ComponentB {
45    fn from_world(world: &mut World) -> Self {
46        let time = world.resource::<Time>();
47        ComponentB {
48            _time_since_startup: time.elapsed(),
49            value: "Default Value".to_string(),
50        }
51    }
52}
53
54// Resources can be serialized in scenes as well, with the same requirements `Component`s have.
55#[derive(Resource, Reflect, Default)]
56#[reflect(Resource)]
57struct ResourceA {
58    pub score: u32,
59}
60
61// The initial scene file will be loaded below and not change when the scene is saved
62const SCENE_FILE_PATH: &str = "scenes/load_scene_example.scn.ron";
63
64// The new, updated scene data will be saved here so that you can see the changes
65const NEW_SCENE_FILE_PATH: &str = "scenes/load_scene_example-new.scn.ron";
66
67fn load_scene_system(mut commands: Commands, asset_server: Res<AssetServer>) {
68    // Spawning a DynamicSceneRoot creates a new entity and spawns new instances
69    // of the given scene's entities as children of that entity.
70    // Scenes can be loaded just like any other asset.
71    commands.spawn(DynamicSceneRoot(asset_server.load(SCENE_FILE_PATH)));
72}
73
74// This system logs all ComponentA components in our world. Try making a change to a ComponentA in
75// load_scene_example.scn. If you enable the `file_watcher` cargo feature you should immediately see
76// the changes appear in the console whenever you make a change.
77fn log_system(
78    query: Query<(Entity, &ComponentA), Changed<ComponentA>>,
79    res: Option<Res<ResourceA>>,
80) {
81    for (entity, component_a) in &query {
82        info!("  Entity({})", entity.index());
83        info!(
84            "    ComponentA: {{ x: {} y: {} }}\n",
85            component_a.x, component_a.y
86        );
87    }
88    if let Some(res) = res {
89        if res.is_added() {
90            info!("  New ResourceA: {{ score: {} }}\n", res.score);
91        }
92    }
93}
94
95fn save_scene_system(world: &mut World) {
96    // Scenes can be created from any ECS World.
97    // You can either create a new one for the scene or use the current World.
98    // For demonstration purposes, we'll create a new one.
99    let mut scene_world = World::new();
100
101    // The `TypeRegistry` resource contains information about all registered types (including components).
102    // This is used to construct scenes, so we'll want to ensure that our previous type registrations
103    // exist in this new scene world as well.
104    // To do this, we can simply clone the `AppTypeRegistry` resource.
105    let type_registry = world.resource::<AppTypeRegistry>().clone();
106    scene_world.insert_resource(type_registry);
107
108    let mut component_b = ComponentB::from_world(world);
109    component_b.value = "hello".to_string();
110    scene_world.spawn((
111        component_b,
112        ComponentA { x: 1.0, y: 2.0 },
113        Transform::IDENTITY,
114        Name::new("joe"),
115    ));
116    scene_world.spawn(ComponentA { x: 3.0, y: 4.0 });
117    scene_world.insert_resource(ResourceA { score: 1 });
118
119    // With our sample world ready to go, we can now create our scene using DynamicScene or DynamicSceneBuilder.
120    // For simplicity, we will create our scene using DynamicScene:
121    let scene = DynamicScene::from_world(&scene_world);
122
123    // Scenes can be serialized like this:
124    let type_registry = world.resource::<AppTypeRegistry>();
125    let type_registry = type_registry.read();
126    let serialized_scene = scene.serialize(&type_registry).unwrap();
127
128    // Showing the scene in the console
129    info!("{}", serialized_scene);
130
131    // Writing the scene to a new file. Using a task to avoid calling the filesystem APIs in a system
132    // as they are blocking
133    // This can't work in Wasm as there is no filesystem access
134    #[cfg(not(target_arch = "wasm32"))]
135    IoTaskPool::get()
136        .spawn(async move {
137            // Write the scene RON data to file
138            File::create(format!("assets/{NEW_SCENE_FILE_PATH}"))
139                .and_then(|mut file| file.write(serialized_scene.as_bytes()))
140                .expect("Error while writing scene to file");
141        })
142        .detach();
143}
144
145// This is only necessary for the info message in the UI. See examples/ui/text.rs for a standalone
146// text example.
147fn infotext_system(mut commands: Commands) {
148    commands.spawn(Camera2d);
149    commands.spawn((
150        Text::new("Nothing to see in this window! Check the console output!"),
151        TextFont {
152            font_size: 42.0,
153            ..default()
154        },
155        Node {
156            align_self: AlignSelf::FlexEnd,
157            ..default()
158        },
159    ));
160}