mirror of
https://github.com/bevyengine/bevy.git
synced 2026-05-06 06:06:42 -04:00
61127f6d01
# Objective
- In 0.18, we had 10 different functions that load assets (I'm not even
counting `load_folder`).
- In 0.19, we've even added `load_erased` - but it unfortunately doesn't
support all the features that the other variants support.
- We apparently needed `load_acquire_with_settings_override` which 1)
loads the asset, 2) uses the settings provided, 3) allows reading
unapproved asset paths, and 4) drops a guard once the load completes.
- That's fine if that's necessary. But we needed to create an explicit
variant for that.
- We need fewer load paths!
## Solution
- Create a builder.
- Store all these options dynamically instead of statically handling
each case.
- Have the caller choose a particular "kind" of load when they are
ready: `load`, `load_erased`, `load_untyped`, or `load_untyped_async`.
- I intentionally didn't provide a `load_async` or `load_erased_async`,
since those can be replicated using `load`/`load_erased` +
`AssetServer::wait_for_asset_id` to get the exact same effect.
I am also intentionally leaving `NestedLoader` untouched in this PR, but
a followup will duplicate this API for `NestedLoader`, which should make
it easier to understand.
Unlike the `NestedLoader` API, we aren't doing any type-state craziness,
so the docs are much more clear: users don't need to understand how
type-state stuff works, they just call the handful of methods on the
type. The "cost" here is we now need to be careful about including the
cross product of loads between static asset type, runtime asset type, or
dynamic asset type, crossed with deferred or async. In theory, if we
added more kinds on either side, we would need to expand this cross
product a lot. In practice though, it seems unlikely there will be any
more variants there. (maybe there could be a blocking variant? I don't
think this is a popular opinion though).
A big con here is some somewhat common calls are now more verbose.
Specifically, `asset_server.load_with_settings()` has become
`asset_server.load_builder().with_settings().load()`. I am not really
concerned about this though, since it really isn't that painful.
## Testing
- Tests all pass!
---
## Showcase
Now instead of:
```rust
asset_server.load_acquire_with_settings_override("some_path", |settings: &mut GltfLoaderSettings| { ... }, my_lock_guard);
```
You can instead do:
```rust
asset_server.load_builder()
.with_guard(my_lock_guard)
.with_settings(|settings: &mut GltfLoaderSettings| { ... })
.override_unapproved()
.load("some_path");
```
We also now cover more variants! For example, you can now load an asset
untyped with a guard, or with override_unapproved, etc.
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
140 lines
4.7 KiB
Rust
140 lines
4.7 KiB
Rust
//! Shows how to modify texture assets after spawning.
|
|
|
|
use bevy::{
|
|
asset::RenderAssetUsages, image::ImageLoaderSettings,
|
|
input::common_conditions::input_just_pressed, prelude::*,
|
|
};
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(DefaultPlugins)
|
|
.add_systems(Startup, (setup, spawn_text))
|
|
.add_systems(
|
|
Update,
|
|
alter_handle.run_if(input_just_pressed(KeyCode::Space)),
|
|
)
|
|
.add_systems(
|
|
Update,
|
|
alter_asset.run_if(input_just_pressed(KeyCode::Enter)),
|
|
)
|
|
.run();
|
|
}
|
|
|
|
#[derive(Component, Debug)]
|
|
enum Bird {
|
|
Normal,
|
|
Logo,
|
|
}
|
|
|
|
impl Bird {
|
|
fn get_texture_path(&self) -> String {
|
|
match self {
|
|
Bird::Normal => "branding/bevy_bird_dark.png".into(),
|
|
Bird::Logo => "branding/bevy_logo_dark.png".into(),
|
|
}
|
|
}
|
|
|
|
fn set_next_variant(&mut self) {
|
|
*self = match self {
|
|
Bird::Normal => Bird::Logo,
|
|
Bird::Logo => Bird::Normal,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Component, Debug)]
|
|
struct Left;
|
|
|
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|
let bird_left = Bird::Normal;
|
|
let bird_right = Bird::Normal;
|
|
commands.spawn(Camera2d);
|
|
|
|
let texture_left = asset_server
|
|
.load_builder()
|
|
.with_settings(
|
|
// `RenderAssetUsages::all()` is already the default, so the line below could be omitted.
|
|
// It's helpful to know it exists, however.
|
|
//
|
|
// `RenderAssetUsages` tell Bevy whether to keep the data around:
|
|
// - for the GPU (`RenderAssetUsages::RENDER_WORLD`),
|
|
// - for the CPU (`RenderAssetUsages::MAIN_WORLD`),
|
|
// - or both.
|
|
// `RENDER_WORLD` is necessary to render the image, `MAIN_WORLD` is necessary to inspect
|
|
// and modify the image (via `ResMut<Assets<Image>>`).
|
|
//
|
|
// Since most games will not need to modify textures at runtime, many developers opt to pass
|
|
// only `RENDER_WORLD`. This is more memory efficient, as we don't need to keep the image in
|
|
// RAM. For this example however, this would not work, as we need to inspect and modify the
|
|
// image at runtime.
|
|
|settings: &mut ImageLoaderSettings| settings.asset_usage = RenderAssetUsages::all(),
|
|
)
|
|
.load(bird_left.get_texture_path());
|
|
|
|
commands.spawn((
|
|
Name::new("Bird Left"),
|
|
// This marker component ensures we can easily find either of the Birds by using With and
|
|
// Without query filters.
|
|
Left,
|
|
Sprite::from_image(texture_left),
|
|
Transform::from_xyz(-200.0, 0.0, 0.0),
|
|
bird_left,
|
|
));
|
|
|
|
commands.spawn((
|
|
Name::new("Bird Right"),
|
|
// In contrast to the above, here we rely on the default `RenderAssetUsages` loader setting
|
|
Sprite::from_image(asset_server.load(bird_right.get_texture_path())),
|
|
Transform::from_xyz(200.0, 0.0, 0.0),
|
|
bird_right,
|
|
));
|
|
}
|
|
|
|
fn spawn_text(mut commands: Commands) {
|
|
commands.spawn((
|
|
Name::new("Instructions"),
|
|
Text::new(
|
|
"Space: swap the right sprite's image handle\n\
|
|
Return: modify the image Asset of the left sprite, affecting all uses of it",
|
|
),
|
|
Node {
|
|
position_type: PositionType::Absolute,
|
|
top: px(12),
|
|
left: px(12),
|
|
..default()
|
|
},
|
|
));
|
|
}
|
|
|
|
fn alter_handle(
|
|
asset_server: Res<AssetServer>,
|
|
right_bird: Single<(&mut Bird, &mut Sprite), Without<Left>>,
|
|
) {
|
|
// Image handles, like other parts of the ECS, can be queried as mutable and modified at
|
|
// runtime. We only spawned one bird without the `Left` marker component.
|
|
let (mut bird, mut sprite) = right_bird.into_inner();
|
|
|
|
// Switch to a new Bird variant
|
|
bird.set_next_variant();
|
|
|
|
// Modify the handle associated with the Bird on the right side. Note that we will only
|
|
// have to load the same path from storage media once: repeated attempts will re-use the
|
|
// asset.
|
|
sprite.image = asset_server.load(bird.get_texture_path());
|
|
}
|
|
|
|
fn alter_asset(mut images: ResMut<Assets<Image>>, left_bird: Single<&Sprite, With<Left>>) {
|
|
// Obtain a mutable reference to the Image asset.
|
|
let Some(mut image) = images.get_mut(&left_bird.image) else {
|
|
return;
|
|
};
|
|
|
|
for pixel in image.data.as_mut().unwrap() {
|
|
// Directly modify the asset data, which will affect all users of this asset. By
|
|
// contrast, mutating the handle (as we did above) affects only one copy. In this case,
|
|
// we'll just invert the colors, by way of demonstration. Notice that both uses of the
|
|
// asset show the change, not just the one on the left.
|
|
*pixel = 255 - *pixel;
|
|
}
|
|
}
|