Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/asset/asset_saving.rs
9328 views
1
//! This example demonstrates how to save assets.
2
3
use bevy::{
4
asset::{
5
io::{Reader, Writer},
6
saver::{save_using_saver, AssetSaver, SavedAsset, SavedAssetBuilder},
7
AssetLoader, AsyncWriteExt, LoadContext,
8
},
9
color::palettes::tailwind,
10
input::common_conditions::input_just_pressed,
11
prelude::*,
12
tasks::IoTaskPool,
13
};
14
use serde::{Deserialize, Serialize};
15
16
fn main() {
17
App::new()
18
.add_plugins(DefaultPlugins.set(AssetPlugin {
19
// This is just overriding the default asset paths to scope this to the correct example
20
// folder. You can generally skip this in your own projects.
21
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
42
const ASSET_PATH: &str = "my_scene.boxes";
43
44
/// A system that takes the scene data, passes it to a task, and saves that scene data to
45
/// [`ASSET_PATH`].
46
fn perform_save(boxes: Query<(&Sprite, &Transform), With<Box>>, asset_server: Res<AssetServer>) {
47
// First we extract all the data needed to produce an asset we can save.
48
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
// Build a `SavedAsset` instance from the boxes we extracted.
60
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
// Save the asset using the provided saver.
73
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
89
/// A system the starts loading [`ASSET_PATH`].
90
fn start_load(mut commands: Commands, asset_server: Res<AssetServer>) {
91
commands.spawn(PendingLoad(asset_server.load(ASSET_PATH)));
92
}
93
94
/// Marks that a handle is currently loading.
95
///
96
/// Once loading is complete, the [`ManyBoxes`] data will be spawned.
97
#[derive(Component)]
98
struct PendingLoad(Handle<ManyBoxes>);
99
100
/// Waits for any [`PendingLoad`]s to complete, and spawns in their boxes when they do.
101
fn 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
/// An asset representing a single box.
133
#[derive(Asset, TypePath, Clone, Serialize, Deserialize)]
134
struct OneBox {
135
/// The position of the box.
136
position: Vec2,
137
/// The color of the box.
138
color: Color,
139
}
140
141
/// An asset representing many boxes.
142
#[derive(Asset, TypePath)]
143
struct ManyBoxes {
144
/// Stores handles to all the boxes that should be spawned.
145
///
146
/// Note: in this trivial example, it seems more reasonable to just store [`Vec<OneBox>`], but
147
/// in a more realistic example this could be something like a whole [`Mesh`] (where a handle
148
/// makes more sense). We use a handle here to demonstrate saving subassets as well.
149
boxes: Vec<Handle<OneBox>>,
150
}
151
152
/// A serializable version of [`ManyBoxes`].
153
#[derive(Serialize, Deserialize)]
154
struct SerializableManyBoxes {
155
/// The boxes that exist in this scene.
156
boxes: Vec<OneBox>,
157
}
158
159
/// Am asset saver to save [`ManyBoxes`] assets.
160
#[derive(TypePath)]
161
struct ManyBoxesSaver;
162
163
impl 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
) -> Result<(), Self::Error> {
175
let boxes = asset
176
.boxes
177
.iter()
178
.map(|handle| {
179
// TODO: We should have a better to get the asset for a subasset handle.
180
let label = handle
181
.path()
182
.and_then(|path| path.label())
183
.ok_or_else(|| format!("Failed to get label for handle {handle:?}"))?;
184
asset
185
.get_labeled::<OneBox>(label)
186
.map(|subasset| subasset.get().clone())
187
.ok_or_else(|| format!("Failed to find labeled asset for label {label}"))
188
})
189
.collect::<Result<Vec<_>, _>>()?;
190
191
// Note: serializing to string isn't ideal since we can't do a streaming write, but this is
192
// fine for an example.
193
let serialized = ron::to_string(&SerializableManyBoxes { boxes })?;
194
writer.write_all(serialized.as_bytes()).await?;
195
196
Ok(())
197
}
198
}
199
200
/// An asset loader for loading [`ManyBoxes`] assets.
201
#[derive(TypePath)]
202
struct ManyBoxesLoader;
203
204
impl AssetLoader for ManyBoxesLoader {
205
type Asset = ManyBoxes;
206
type Error = BevyError;
207
type Settings = ();
208
209
async fn load(
210
&self,
211
reader: &mut dyn Reader,
212
_settings: &Self::Settings,
213
load_context: &mut LoadContext<'_>,
214
) -> Result<Self::Asset, Self::Error> {
215
let mut bytes = vec![];
216
reader.read_to_end(&mut bytes).await?;
217
218
let serialized: SerializableManyBoxes = ron::de::from_bytes(&bytes)?;
219
220
// Add the boxes as subassets.
221
let mut result_boxes = vec![];
222
for (index, one_box) in serialized.boxes.into_iter().enumerate() {
223
result_boxes.push(load_context.add_labeled_asset(index.to_string(), one_box));
224
}
225
226
Ok(ManyBoxes {
227
boxes: result_boxes,
228
})
229
}
230
231
fn extensions(&self) -> &[&str] {
232
&["boxes"]
233
}
234
}
235
236
/// Plugin for doing all the box-editing.
237
///
238
/// This doesn't really have anything to do with asset saving, but provides a real use-case.
239
fn box_editing_plugin(app: &mut App) {
240
app.add_systems(Startup, setup)
241
.add_observer(spawn_box)
242
.add_observer(start_rotate_box_hue)
243
.add_observer(end_rotate_box_hue_on_release)
244
.add_observer(end_rotate_box_hue_on_out)
245
.add_systems(Update, rotate_hue)
246
.add_observer(stop_propagate_on_clicked_box)
247
.add_observer(drag_box);
248
}
249
250
#[derive(Component)]
251
struct Box;
252
253
/// Spawns the initial scene.
254
fn setup(mut commands: Commands) {
255
commands.spawn(Camera2d);
256
257
commands.spawn(Text(
258
r"LMB (on background) - spawn new box
259
LMB (on box) - drag to move
260
RMB (on box) - rotate colors
261
F5 - Save boxes
262
F6 - Load boxes"
263
.into(),
264
));
265
}
266
267
/// Spawns a new box whenever you left-click on the background.
268
fn spawn_box(
269
event: On<Pointer<Press>>,
270
window: Query<(), With<Window>>,
271
camera: Single<(&Camera, &GlobalTransform)>,
272
mut commands: Commands,
273
) {
274
if event.button != PointerButton::Primary {
275
return;
276
}
277
if !window.contains(event.entity) {
278
return;
279
}
280
281
let (camera, camera_transform) = camera.into_inner();
282
let Ok(click_point) =
283
camera.viewport_to_world_2d(camera_transform, event.pointer_location.position)
284
else {
285
return;
286
};
287
commands.spawn((
288
Sprite::from_color(tailwind::RED_500, Vec2::new(100.0, 100.0)),
289
Transform::from_translation(click_point.extend(0.0)),
290
Pickable::default(),
291
Box,
292
));
293
}
294
295
/// A component to rotate the hue of a sprite every frame.
296
#[derive(Component)]
297
struct RotateHue;
298
299
/// Rotates the hue of each [`Sprite`] tagged with [`RotateHue`].
300
fn rotate_hue(time: Res<Time>, mut sprites: Query<&mut Sprite, With<RotateHue>>) {
301
for mut sprite in sprites.iter_mut() {
302
// Make a full rotation every 2 seconds.
303
sprite.color = sprite.color.rotate_hue(time.delta_secs() * 180.0);
304
}
305
}
306
307
/// Starts rotating the hue of a box that has been right-clicked.
308
fn start_rotate_box_hue(
309
event: On<Pointer<Press>>,
310
boxes: Query<(), With<Box>>,
311
mut commands: Commands,
312
) {
313
if event.button != PointerButton::Secondary {
314
return;
315
}
316
if !boxes.contains(event.entity) {
317
return;
318
}
319
commands.entity(event.entity).insert(RotateHue);
320
}
321
322
/// Stops rotating the box hue if it's right-click is released.
323
fn end_rotate_box_hue_on_release(
324
event: On<Pointer<Release>>,
325
boxes: Query<(), (With<Box>, With<RotateHue>)>,
326
mut commands: Commands,
327
) {
328
if event.button != PointerButton::Secondary {
329
return;
330
}
331
if !boxes.contains(event.entity) {
332
return;
333
}
334
commands.entity(event.entity).remove::<RotateHue>();
335
}
336
337
/// Stops rotating the box hue if the cursor moves off the entity.
338
fn end_rotate_box_hue_on_out(
339
event: On<Pointer<Out>>,
340
boxes: Query<(), (With<Box>, With<RotateHue>)>,
341
mut commands: Commands,
342
) {
343
if !boxes.contains(event.entity) {
344
return;
345
}
346
commands.entity(event.entity).remove::<RotateHue>();
347
}
348
349
/// Blocks propagation of pointer press events on left-clicked boxes.
350
fn stop_propagate_on_clicked_box(mut event: On<Pointer<Press>>, boxes: Query<(), With<Box>>) {
351
if event.button != PointerButton::Primary {
352
return;
353
}
354
if !boxes.contains(event.entity) {
355
return;
356
}
357
event.propagate(false);
358
}
359
360
/// Drags a box when you left-click on one.
361
fn drag_box(event: On<Pointer<Drag>>, mut boxes: Query<&mut Transform, With<Box>>) {
362
if event.button != PointerButton::Primary {
363
return;
364
}
365
let Ok(mut transform) = boxes.get_mut(event.entity) else {
366
return;
367
};
368
369
// This is wrong in general (e.g., doesn't consider scale), but it's close enough for our
370
// purposes.
371
transform.translation += Vec3::new(event.delta.x, -event.delta.y, 0.0);
372
}
373
374