Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/asset/asset_saving.rs
9735 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
asset
180
.get_labeled_by_id::<OneBox>(handle)
181
.unwrap()
182
.get()
183
.clone()
184
})
185
.collect();
186
187
// Note: serializing to string isn't ideal since we can't do a streaming write, but this is
188
// fine for an example.
189
let serialized = ron::to_string(&SerializableManyBoxes { boxes })?;
190
writer.write_all(serialized.as_bytes()).await?;
191
192
Ok(())
193
}
194
}
195
196
/// An asset loader for loading [`ManyBoxes`] assets.
197
#[derive(TypePath)]
198
struct ManyBoxesLoader;
199
200
impl AssetLoader for ManyBoxesLoader {
201
type Asset = ManyBoxes;
202
type Error = BevyError;
203
type Settings = ();
204
205
async fn load(
206
&self,
207
reader: &mut dyn Reader,
208
_settings: &Self::Settings,
209
load_context: &mut LoadContext<'_>,
210
) -> Result<Self::Asset, Self::Error> {
211
let mut bytes = vec![];
212
reader.read_to_end(&mut bytes).await?;
213
214
let serialized: SerializableManyBoxes = ron::de::from_bytes(&bytes)?;
215
216
// Add the boxes as subassets.
217
let mut result_boxes = vec![];
218
for (index, one_box) in serialized.boxes.into_iter().enumerate() {
219
result_boxes.push(load_context.add_labeled_asset(index.to_string(), one_box));
220
}
221
222
Ok(ManyBoxes {
223
boxes: result_boxes,
224
})
225
}
226
227
fn extensions(&self) -> &[&str] {
228
&["boxes"]
229
}
230
}
231
232
/// Plugin for doing all the box-editing.
233
///
234
/// This doesn't really have anything to do with asset saving, but provides a real use-case.
235
fn box_editing_plugin(app: &mut App) {
236
app.add_systems(Startup, setup)
237
.add_observer(spawn_box)
238
.add_observer(start_rotate_box_hue)
239
.add_observer(end_rotate_box_hue_on_release)
240
.add_observer(end_rotate_box_hue_on_out)
241
.add_systems(Update, rotate_hue)
242
.add_observer(stop_propagate_on_clicked_box)
243
.add_observer(drag_box);
244
}
245
246
#[derive(Component)]
247
struct Box;
248
249
/// Spawns the initial scene.
250
fn setup(mut commands: Commands) {
251
commands.spawn(Camera2d);
252
253
commands.spawn(Text(
254
r"LMB (on background) - spawn new box
255
LMB (on box) - drag to move
256
RMB (on box) - rotate colors
257
F5 - Save boxes
258
F6 - Load boxes"
259
.into(),
260
));
261
}
262
263
/// Spawns a new box whenever you left-click on the background.
264
fn spawn_box(
265
event: On<Pointer<Press>>,
266
window: Query<(), With<Window>>,
267
camera: Single<(&Camera, &GlobalTransform)>,
268
mut commands: Commands,
269
) {
270
if event.button != PointerButton::Primary {
271
return;
272
}
273
if !window.contains(event.entity) {
274
return;
275
}
276
277
let (camera, camera_transform) = camera.into_inner();
278
let Ok(click_point) =
279
camera.viewport_to_world_2d(camera_transform, event.pointer_location.position)
280
else {
281
return;
282
};
283
commands.spawn((
284
Sprite::from_color(tailwind::RED_500, Vec2::new(100.0, 100.0)),
285
Transform::from_translation(click_point.extend(0.0)),
286
Pickable::default(),
287
Box,
288
));
289
}
290
291
/// A component to rotate the hue of a sprite every frame.
292
#[derive(Component)]
293
struct RotateHue;
294
295
/// Rotates the hue of each [`Sprite`] tagged with [`RotateHue`].
296
fn rotate_hue(time: Res<Time>, mut sprites: Query<&mut Sprite, With<RotateHue>>) {
297
for mut sprite in sprites.iter_mut() {
298
// Make a full rotation every 2 seconds.
299
sprite.color = sprite.color.rotate_hue(time.delta_secs() * 180.0);
300
}
301
}
302
303
/// Starts rotating the hue of a box that has been right-clicked.
304
fn start_rotate_box_hue(
305
event: On<Pointer<Press>>,
306
boxes: Query<(), With<Box>>,
307
mut commands: Commands,
308
) {
309
if event.button != PointerButton::Secondary {
310
return;
311
}
312
if !boxes.contains(event.entity) {
313
return;
314
}
315
commands.entity(event.entity).insert(RotateHue);
316
}
317
318
/// Stops rotating the box hue if it's right-click is released.
319
fn end_rotate_box_hue_on_release(
320
event: On<Pointer<Release>>,
321
boxes: Query<(), (With<Box>, With<RotateHue>)>,
322
mut commands: Commands,
323
) {
324
if event.button != PointerButton::Secondary {
325
return;
326
}
327
if !boxes.contains(event.entity) {
328
return;
329
}
330
commands.entity(event.entity).remove::<RotateHue>();
331
}
332
333
/// Stops rotating the box hue if the cursor moves off the entity.
334
fn end_rotate_box_hue_on_out(
335
event: On<Pointer<Out>>,
336
boxes: Query<(), (With<Box>, With<RotateHue>)>,
337
mut commands: Commands,
338
) {
339
if !boxes.contains(event.entity) {
340
return;
341
}
342
commands.entity(event.entity).remove::<RotateHue>();
343
}
344
345
/// Blocks propagation of pointer press events on left-clicked boxes.
346
fn stop_propagate_on_clicked_box(mut event: On<Pointer<Press>>, boxes: Query<(), With<Box>>) {
347
if event.button != PointerButton::Primary {
348
return;
349
}
350
if !boxes.contains(event.entity) {
351
return;
352
}
353
event.propagate(false);
354
}
355
356
/// Drags a box when you left-click on one.
357
fn drag_box(event: On<Pointer<Drag>>, mut boxes: Query<&mut Transform, With<Box>>) {
358
if event.button != PointerButton::Primary {
359
return;
360
}
361
let Ok(mut transform) = boxes.get_mut(event.entity) else {
362
return;
363
};
364
365
// This is wrong in general (e.g., doesn't consider scale), but it's close enough for our
366
// purposes.
367
transform.translation += Vec3::new(event.delta.x, -event.delta.y, 0.0);
368
}
369
370