asset_saving_with_subassets/
asset_saving_with_subassets.rs1use bevy::{
4 asset::{
5 io::{Reader, Writer},
6 saver::{save_using_saver, AssetSaver, SavedAsset, SavedAssetBuilder},
7 AssetLoader, AssetPath, AsyncWriteExt, LoadContext,
8 },
9 color::palettes::tailwind,
10 input::common_conditions::input_just_pressed,
11 prelude::*,
12 tasks::IoTaskPool,
13};
14use serde::{Deserialize, Serialize};
15
16fn main() {
17 App::new()
18 .add_plugins(DefaultPlugins.set(AssetPlugin {
19 file_path: "examples/asset/saved_assets".to_string(),
22 ..Default::default()
23 }))
24 .add_plugins(box_editing_plugin)
25 .init_asset::<OneBox>()
26 .init_asset::<ManyBoxes>()
27 .register_asset_loader(ManyBoxesLoader)
28 .add_systems(
29 PreUpdate,
30 (
31 perform_save.run_if(input_just_pressed(KeyCode::F5)),
32 (
33 start_load.run_if(input_just_pressed(KeyCode::F6)),
34 wait_for_pending_loads,
35 )
36 .chain(),
37 ),
38 )
39 .run();
40}
41
42const ASSET_PATH: &str = "my_scene.boxes";
43
44fn perform_save(boxes: Query<(&Sprite, &Transform), With<Box>>, asset_server: Res<AssetServer>) {
47 let boxes = boxes
49 .iter()
50 .map(|(sprite, transform)| OneBox {
51 position: transform.translation.xy(),
52 color: sprite.color,
53 })
54 .collect::<Vec<_>>();
55
56 let asset_server = asset_server.clone();
57 IoTaskPool::get()
58 .spawn(async move {
59 let mut builder = SavedAssetBuilder::new(asset_server.clone(), ASSET_PATH.into());
61 let mut many_boxes = ManyBoxes { boxes: vec![] };
62 for (index, one_box) in boxes.iter().enumerate() {
63 many_boxes
64 .boxes
65 .push(builder.add_labeled_asset_with_new_handle(
66 index.to_string(),
67 SavedAsset::from_asset(one_box),
68 ));
69 }
70
71 let saved_asset = builder.build(&many_boxes);
72 match save_using_saver(
74 asset_server.clone(),
75 &ManyBoxesSaver,
76 &ASSET_PATH.into(),
77 saved_asset,
78 &(),
79 )
80 .await
81 {
82 Ok(()) => info!("Completed save of {ASSET_PATH}"),
83 Err(err) => error!("Failed to save asset: {err}"),
84 }
85 })
86 .detach();
87}
88
89fn start_load(mut commands: Commands, asset_server: Res<AssetServer>) {
91 commands.spawn(PendingLoad(asset_server.load(ASSET_PATH)));
92}
93
94#[derive(Component)]
98struct PendingLoad(Handle<ManyBoxes>);
99
100fn wait_for_pending_loads(
102 loads: Populated<(Entity, &PendingLoad)>,
103 many_boxes: Res<Assets<ManyBoxes>>,
104 one_boxes: Res<Assets<OneBox>>,
105 existing_boxes: Query<Entity, With<Box>>,
106 mut commands: Commands,
107) {
108 for (entity, load) in loads.iter() {
109 let Some(many_boxes) = many_boxes.get(&load.0) else {
110 continue;
111 };
112
113 commands.entity(entity).despawn();
114 for entity in existing_boxes.iter() {
115 commands.entity(entity).despawn();
116 }
117
118 for box_handle in many_boxes.boxes.iter() {
119 let Some(one_box) = one_boxes.get(box_handle) else {
120 return;
121 };
122 commands.spawn((
123 Sprite::from_color(one_box.color, Vec2::new(100.0, 100.0)),
124 Transform::from_translation(one_box.position.extend(0.0)),
125 Pickable::default(),
126 Box,
127 ));
128 }
129 }
130}
131
132#[derive(Asset, TypePath, Clone, Serialize, Deserialize)]
134struct OneBox {
135 position: Vec2,
137 color: Color,
139}
140
141#[derive(Asset, TypePath)]
143struct ManyBoxes {
144 boxes: Vec<Handle<OneBox>>,
150}
151
152#[derive(Serialize, Deserialize)]
154struct SerializableManyBoxes {
155 boxes: Vec<OneBox>,
157}
158
159#[derive(TypePath)]
161struct ManyBoxesSaver;
162
163impl AssetSaver for ManyBoxesSaver {
164 type Asset = ManyBoxes;
165 type Error = BevyError;
166 type OutputLoader = ManyBoxesLoader;
167 type Settings = ();
168
169 async fn save(
170 &self,
171 writer: &mut Writer,
172 asset: SavedAsset<'_, '_, Self::Asset>,
173 _settings: &Self::Settings,
174 _asset_path: AssetPath<'_>,
175 ) -> Result<(), Self::Error> {
176 let boxes = asset
177 .boxes
178 .iter()
179 .map(|handle| {
180 asset
181 .get_labeled_by_id::<OneBox>(handle)
182 .unwrap()
183 .get()
184 .clone()
185 })
186 .collect();
187
188 let serialized = ron::to_string(&SerializableManyBoxes { boxes })?;
191 writer.write_all(serialized.as_bytes()).await?;
192
193 Ok(())
194 }
195}
196
197#[derive(TypePath)]
199struct ManyBoxesLoader;
200
201impl AssetLoader for ManyBoxesLoader {
202 type Asset = ManyBoxes;
203 type Error = BevyError;
204 type Settings = ();
205
206 async fn load(
207 &self,
208 reader: &mut dyn Reader,
209 _settings: &Self::Settings,
210 load_context: &mut LoadContext<'_>,
211 ) -> Result<Self::Asset, Self::Error> {
212 let mut bytes = vec![];
213 reader.read_to_end(&mut bytes).await?;
214
215 let serialized: SerializableManyBoxes = ron::de::from_bytes(&bytes)?;
216
217 let mut result_boxes = vec![];
219 for (index, one_box) in serialized.boxes.into_iter().enumerate() {
220 result_boxes.push(load_context.add_labeled_asset(index.to_string(), one_box));
221 }
222
223 Ok(ManyBoxes {
224 boxes: result_boxes,
225 })
226 }
227
228 fn extensions(&self) -> &[&str] {
229 &["boxes"]
230 }
231}
232
233fn box_editing_plugin(app: &mut App) {
237 app.add_systems(Startup, setup)
238 .add_observer(spawn_box)
239 .add_observer(start_rotate_box_hue)
240 .add_observer(end_rotate_box_hue_on_release)
241 .add_observer(end_rotate_box_hue_on_out)
242 .add_systems(Update, rotate_hue)
243 .add_observer(stop_propagate_on_clicked_box)
244 .add_observer(drag_box);
245}
246
247#[derive(Component)]
248struct Box;
249
250fn setup(mut commands: Commands) {
252 commands.spawn(Camera2d);
253
254 commands.spawn(Text(
255 r"LMB (on background) - spawn new box
256LMB (on box) - drag to move
257RMB (on box) - rotate colors
258F5 - Save boxes
259F6 - Load boxes"
260 .into(),
261 ));
262}
263
264fn spawn_box(
266 event: On<Pointer<Press>>,
267 window: Query<(), With<Window>>,
268 camera: Single<(&Camera, &GlobalTransform)>,
269 mut commands: Commands,
270) {
271 if event.button != PointerButton::Primary {
272 return;
273 }
274 if !window.contains(event.entity) {
275 return;
276 }
277
278 let (camera, camera_transform) = camera.into_inner();
279 let Ok(click_point) =
280 camera.viewport_to_world_2d(camera_transform, event.pointer_location.position)
281 else {
282 return;
283 };
284 commands.spawn((
285 Sprite::from_color(tailwind::RED_500, Vec2::new(100.0, 100.0)),
286 Transform::from_translation(click_point.extend(0.0)),
287 Pickable::default(),
288 Box,
289 ));
290}
291
292#[derive(Component)]
294struct RotateHue;
295
296fn rotate_hue(time: Res<Time>, mut sprites: Query<&mut Sprite, With<RotateHue>>) {
298 for mut sprite in sprites.iter_mut() {
299 sprite.color = sprite.color.rotate_hue(time.delta_secs() * 180.0);
301 }
302}
303
304fn start_rotate_box_hue(
306 event: On<Pointer<Press>>,
307 boxes: Query<(), With<Box>>,
308 mut commands: Commands,
309) {
310 if event.button != PointerButton::Secondary {
311 return;
312 }
313 if !boxes.contains(event.entity) {
314 return;
315 }
316 commands.entity(event.entity).insert(RotateHue);
317}
318
319fn end_rotate_box_hue_on_release(
321 event: On<Pointer<Release>>,
322 boxes: Query<(), (With<Box>, With<RotateHue>)>,
323 mut commands: Commands,
324) {
325 if event.button != PointerButton::Secondary {
326 return;
327 }
328 if !boxes.contains(event.entity) {
329 return;
330 }
331 commands.entity(event.entity).remove::<RotateHue>();
332}
333
334fn end_rotate_box_hue_on_out(
336 event: On<Pointer<Out>>,
337 boxes: Query<(), (With<Box>, With<RotateHue>)>,
338 mut commands: Commands,
339) {
340 if !boxes.contains(event.entity) {
341 return;
342 }
343 commands.entity(event.entity).remove::<RotateHue>();
344}
345
346fn stop_propagate_on_clicked_box(mut event: On<Pointer<Press>>, boxes: Query<(), With<Box>>) {
348 if event.button != PointerButton::Primary {
349 return;
350 }
351 if !boxes.contains(event.entity) {
352 return;
353 }
354 event.propagate(false);
355}
356
357fn drag_box(event: On<Pointer<Drag>>, mut boxes: Query<&mut Transform, With<Box>>) {
359 if event.button != PointerButton::Primary {
360 return;
361 }
362 let Ok(mut transform) = boxes.get_mut(event.entity) else {
363 return;
364 };
365
366 transform.translation += Vec3::new(event.delta.x, -event.delta.y, 0.0);
369}