Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

210
vendor/bevy/examples/asset/alter_mesh.rs vendored Normal file
View File

@@ -0,0 +1,210 @@
//! Shows how to modify mesh assets after spawning.
use bevy::{
gltf::GltfLoaderSettings,
input::common_conditions::input_just_pressed,
prelude::*,
render::{mesh::VertexAttributeValues, render_asset::RenderAssetUsages},
};
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_mesh.run_if(input_just_pressed(KeyCode::Enter)),
)
.run();
}
#[derive(Component, Debug)]
enum Shape {
Cube,
Sphere,
}
impl Shape {
fn get_model_path(&self) -> String {
match self {
Shape::Cube => "models/cube/cube.gltf".into(),
Shape::Sphere => "models/sphere/sphere.gltf".into(),
}
}
fn set_next_variant(&mut self) {
*self = match self {
Shape::Cube => Shape::Sphere,
Shape::Sphere => Shape::Cube,
}
}
}
#[derive(Component, Debug)]
struct Left;
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let left_shape = Shape::Cube;
let right_shape = Shape::Cube;
// In normal use, you can call `asset_server.load`, however see below for an explanation of
// `RenderAssetUsages`.
let left_shape_model = asset_server.load_with_settings(
GltfAssetLabel::Primitive {
mesh: 0,
// This field stores an index to this primitive in its parent mesh. In this case, we
// want the first one. You might also have seen the syntax:
//
// models/cube/cube.gltf#Scene0
//
// which accomplishes the same thing.
primitive: 0,
}
.from_asset(left_shape.get_model_path()),
// `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 mesh, `MAIN_WORLD` is necessary to inspect
// and modify the mesh (via `ResMut<Assets<Mesh>>`).
//
// Since most games will not need to modify meshes at runtime, many developers opt to pass
// only `RENDER_WORLD`. This is more memory efficient, as we don't need to keep the mesh in
// RAM. For this example however, this would not work, as we need to inspect and modify the
// mesh at runtime.
|settings: &mut GltfLoaderSettings| settings.load_meshes = RenderAssetUsages::all(),
);
// Here, we rely on the default loader settings to achieve a similar result to the above.
let right_shape_model = asset_server.load(
GltfAssetLabel::Primitive {
mesh: 0,
primitive: 0,
}
.from_asset(right_shape.get_model_path()),
);
// Add a material asset directly to the materials storage
let material_handle = materials.add(StandardMaterial {
base_color: Color::srgb(0.6, 0.8, 0.6),
..default()
});
commands.spawn((
Left,
Name::new("Left Shape"),
Mesh3d(left_shape_model),
MeshMaterial3d(material_handle.clone()),
Transform::from_xyz(-3.0, 0.0, 0.0),
left_shape,
));
commands.spawn((
Name::new("Right Shape"),
Mesh3d(right_shape_model),
MeshMaterial3d(material_handle),
Transform::from_xyz(3.0, 0.0, 0.0),
right_shape,
));
commands.spawn((
Name::new("Point Light"),
PointLight::default(),
Transform::from_xyz(4.0, 5.0, 4.0),
));
commands.spawn((
Name::new("Camera"),
Camera3d::default(),
Transform::from_xyz(0.0, 3.0, 20.0).looking_at(Vec3::ZERO, Vec3::Y),
));
}
fn spawn_text(mut commands: Commands) {
commands.spawn((
Name::new("Instructions"),
Text::new(
"Space: swap meshes by mutating a Handle<Mesh>\n\
Return: mutate the mesh itself, changing all copies of it",
),
Node {
position_type: PositionType::Absolute,
top: Val::Px(12.),
left: Val::Px(12.),
..default()
},
));
}
fn alter_handle(
asset_server: Res<AssetServer>,
right_shape: Single<(&mut Mesh3d, &mut Shape), Without<Left>>,
) {
// Mesh handles, like other parts of the ECS, can be queried as mutable and modified at
// runtime. We only spawned one shape without the `Left` marker component.
let (mut mesh, mut shape) = right_shape.into_inner();
// Switch to a new Shape variant
shape.set_next_variant();
// Modify the handle associated with the Shape 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.
mesh.0 = asset_server.load(
GltfAssetLabel::Primitive {
mesh: 0,
primitive: 0,
}
.from_asset(shape.get_model_path()),
);
}
fn alter_mesh(
mut is_mesh_scaled: Local<bool>,
left_shape: Single<&Mesh3d, With<Left>>,
mut meshes: ResMut<Assets<Mesh>>,
) {
// Obtain a mutable reference to the Mesh asset.
let Some(mesh) = meshes.get_mut(*left_shape) else {
return;
};
// Now we can directly manipulate vertices on the mesh. Here, we're just scaling in and out
// for demonstration purposes. This will affect all entities currently using the asset.
//
// To do this, we need to grab the stored attributes of each vertex. `Float32x3` just describes
// the format in which the attributes will be read: each position consists of an array of three
// f32 corresponding to x, y, and z.
//
// `ATTRIBUTE_POSITION` is a constant indicating that we want to know where the vertex is
// located in space (as opposed to which way its normal is facing, vertex color, or other
// details).
if let Some(VertexAttributeValues::Float32x3(positions)) =
mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION)
{
// Check a Local value (which only this system can make use of) to determine if we're
// currently scaled up or not.
let scale_factor = if *is_mesh_scaled { 0.5 } else { 2.0 };
for position in positions.iter_mut() {
// Apply the scale factor to each of x, y, and z.
position[0] *= scale_factor;
position[1] *= scale_factor;
position[2] *= scale_factor;
}
// Flip the local value to reverse the behavior next time the key is pressed.
*is_mesh_scaled = !*is_mesh_scaled;
}
}

View File

@@ -0,0 +1,137 @@
//! Shows how to modify texture assets after spawning.
use bevy::{
image::ImageLoaderSettings, input::common_conditions::input_just_pressed, prelude::*,
render::render_asset::RenderAssetUsages,
};
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_with_settings(
bird_left.get_texture_path(),
// `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(),
);
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: Val::Px(12.),
left: Val::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(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;
}
}

View File

@@ -0,0 +1,133 @@
//! Implements loader for a Gzip compressed asset.
use bevy::{
asset::{
io::{Reader, VecReader},
AssetLoader, ErasedLoadedAsset, LoadContext, LoadDirectError,
},
prelude::*,
reflect::TypePath,
};
use flate2::read::GzDecoder;
use std::{io::prelude::*, marker::PhantomData};
use thiserror::Error;
#[derive(Asset, TypePath)]
struct GzAsset {
uncompressed: ErasedLoadedAsset,
}
#[derive(Default)]
struct GzAssetLoader;
/// Possible errors that can be produced by [`GzAssetLoader`]
#[non_exhaustive]
#[derive(Debug, Error)]
enum GzAssetLoaderError {
/// An [IO](std::io) Error
#[error("Could not load asset: {0}")]
Io(#[from] std::io::Error),
/// An error caused when the asset path cannot be used to determine the uncompressed asset type.
#[error("Could not determine file path of uncompressed asset")]
IndeterminateFilePath,
/// An error caused by the internal asset loader.
#[error("Could not load contained asset: {0}")]
LoadDirectError(#[from] LoadDirectError),
}
impl AssetLoader for GzAssetLoader {
type Asset = GzAsset;
type Settings = ();
type Error = GzAssetLoaderError;
async fn load(
&self,
reader: &mut dyn Reader,
_settings: &(),
load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let compressed_path = load_context.path();
let file_name = compressed_path
.file_name()
.ok_or(GzAssetLoaderError::IndeterminateFilePath)?
.to_string_lossy();
let uncompressed_file_name = file_name
.strip_suffix(".gz")
.ok_or(GzAssetLoaderError::IndeterminateFilePath)?;
let contained_path = compressed_path.join(uncompressed_file_name);
let mut bytes_compressed = Vec::new();
reader.read_to_end(&mut bytes_compressed).await?;
let mut decoder = GzDecoder::new(bytes_compressed.as_slice());
let mut bytes_uncompressed = Vec::new();
decoder.read_to_end(&mut bytes_uncompressed)?;
// Now that we have decompressed the asset, let's pass it back to the
// context to continue loading
let mut reader = VecReader::new(bytes_uncompressed);
let uncompressed = load_context
.loader()
.with_unknown_type()
.immediate()
.with_reader(&mut reader)
.load(contained_path)
.await?;
Ok(GzAsset { uncompressed })
}
fn extensions(&self) -> &[&str] {
&["gz"]
}
}
#[derive(Component, Default)]
struct Compressed<T> {
compressed: Handle<GzAsset>,
_phantom: PhantomData<T>,
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_asset::<GzAsset>()
.init_asset_loader::<GzAssetLoader>()
.add_systems(Startup, setup)
.add_systems(Update, decompress::<Sprite, Image>)
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
commands.spawn(Compressed::<Image> {
compressed: asset_server.load("data/compressed_image.png.gz"),
..default()
});
}
fn decompress<T: Component + From<Handle<A>>, A: Asset>(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut compressed_assets: ResMut<Assets<GzAsset>>,
query: Query<(Entity, &Compressed<A>)>,
) {
for (entity, Compressed { compressed, .. }) in query.iter() {
let Some(GzAsset { uncompressed }) = compressed_assets.remove(compressed) else {
continue;
};
let uncompressed = uncompressed.take::<A>().unwrap();
commands
.entity(entity)
.remove::<Compressed<A>>()
.insert(T::from(asset_server.add(uncompressed)));
}
}

View File

@@ -0,0 +1,100 @@
//! This example illustrates various ways to load assets.
use bevy::{asset::LoadedFolder, prelude::*};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
meshes: Res<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// By default AssetServer will load assets from inside the "assets" folder.
// For example, the next line will load GltfAssetLabel::Primitive{mesh:0,primitive:0}.from_asset("ROOT/assets/models/cube/cube.gltf"),
// where "ROOT" is the directory of the Application.
//
// This can be overridden by setting [`AssetPlugin.file_path`].
let cube_handle = asset_server.load(
GltfAssetLabel::Primitive {
mesh: 0,
primitive: 0,
}
.from_asset("models/cube/cube.gltf"),
);
let sphere_handle = asset_server.load(
GltfAssetLabel::Primitive {
mesh: 0,
primitive: 0,
}
.from_asset("models/sphere/sphere.gltf"),
);
// All assets end up in their Assets<T> collection once they are done loading:
if let Some(sphere) = meshes.get(&sphere_handle) {
// You might notice that this doesn't run! This is because assets load in parallel without
// blocking. When an asset has loaded, it will appear in relevant Assets<T>
// collection.
info!("{:?}", sphere.primitive_topology());
} else {
info!("sphere hasn't loaded yet");
}
// You can load all assets in a folder like this. They will be loaded in parallel without
// blocking. The LoadedFolder asset holds handles to each asset in the folder. These are all
// dependencies of the LoadedFolder asset, meaning you can wait for the LoadedFolder asset to
// fire AssetEvent::LoadedWithDependencies if you want to wait for all assets in the folder
// to load.
// If you want to keep the assets in the folder alive, make sure you store the returned handle
// somewhere.
let _loaded_folder: Handle<LoadedFolder> = asset_server.load_folder("models/torus");
// If you want a handle to a specific asset in a loaded folder, the easiest way to get one is to call load.
// It will _not_ be loaded a second time.
// The LoadedFolder asset will ultimately also hold handles to the assets, but waiting for it to load
// and finding the right handle is more work!
let torus_handle = asset_server.load(
GltfAssetLabel::Primitive {
mesh: 0,
primitive: 0,
}
.from_asset("models/torus/torus.gltf"),
);
// You can also add assets directly to their Assets<T> storage:
let material_handle = materials.add(StandardMaterial {
base_color: Color::srgb(0.8, 0.7, 0.6),
..default()
});
// torus
commands.spawn((
Mesh3d(torus_handle),
MeshMaterial3d(material_handle.clone()),
Transform::from_xyz(-3.0, 0.0, 0.0),
));
// cube
commands.spawn((
Mesh3d(cube_handle),
MeshMaterial3d(material_handle.clone()),
Transform::from_xyz(0.0, 0.0, 0.0),
));
// sphere
commands.spawn((
Mesh3d(sphere_handle),
MeshMaterial3d(material_handle),
Transform::from_xyz(3.0, 0.0, 0.0),
));
// light
commands.spawn((PointLight::default(), Transform::from_xyz(4.0, 5.0, 4.0)));
// camera
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 3.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
));
}

View File

@@ -0,0 +1,78 @@
//! This example demonstrates the usage of '.meta' files and [`AssetServer::load_with_settings`] to override the default settings for loading an asset
use bevy::{
image::{ImageLoaderSettings, ImageSampler},
prelude::*,
};
fn main() {
App::new()
.add_plugins(
// This just tells the asset server to look in the right examples folder
DefaultPlugins.set(AssetPlugin {
file_path: "examples/asset/files".to_string(),
..Default::default()
}),
)
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Without any .meta file specifying settings, the default sampler [ImagePlugin::default()] is used for loading images.
// If you are using a very small image and rendering it larger like seen here, the default linear filtering will result in a blurry image.
// Useful note: The default sampler specified by the ImagePlugin is *not* the same as the default implementation of sampler. This is why
// everything uses linear by default but if you look at the default of sampler, it uses nearest.
commands.spawn((
Sprite {
image: asset_server.load("bevy_pixel_dark.png"),
custom_size: Some(Vec2 { x: 160.0, y: 120.0 }),
..Default::default()
},
Transform::from_xyz(-100.0, 0.0, 0.0),
));
// When a .meta file is added with the same name as the asset and a '.meta' extension
// you can (and must) specify all fields of the asset loader's settings for that
// particular asset, in this case [ImageLoaderSettings]. Take a look at
// examples/asset/files/bevy_pixel_dark_with_meta.png.meta
// for the format and you'll notice, the only non-default option is setting Nearest
// filtering. This tends to work much better for pixel art assets.
// A good reference when filling this out is to check out [ImageLoaderSettings::default()]
// and follow to the default implementation of each fields type.
// https://docs.rs/bevy/latest/bevy/render/texture/struct.ImageLoaderSettings.html#
commands.spawn((
Sprite {
image: asset_server.load("bevy_pixel_dark_with_meta.png"),
custom_size: Some(Vec2 { x: 160.0, y: 120.0 }),
..Default::default()
},
Transform::from_xyz(100.0, 0.0, 0.0),
));
// Another option is to use the AssetServers load_with_settings function.
// With this you can specify the same settings upon loading your asset with a
// couple of differences. A big one is that you aren't required to set *every*
// setting, just modify the ones that you need. It works by passing in a function
// (in this case an anonymous closure) that takes a reference to the settings type
// that is then modified in the function.
// Do note that if you want to load the same asset with different settings, the
// settings changes from any loads after the first of the same asset will be ignored.
// This is why this one loads a differently named copy of the asset instead of using
// same one as without a .meta file.
commands.spawn((
Sprite {
image: asset_server.load_with_settings(
"bevy_pixel_dark_with_settings.png",
|settings: &mut ImageLoaderSettings| {
settings.sampler = ImageSampler::nearest();
},
),
custom_size: Some(Vec2 { x: 160.0, y: 120.0 }),
..Default::default()
},
Transform::from_xyz(0.0, 150.0, 0.0),
));
commands.spawn(Camera2d);
}

View File

@@ -0,0 +1,159 @@
//! Implements loader for a custom asset type.
use bevy::{
asset::{io::Reader, AssetLoader, LoadContext},
prelude::*,
reflect::TypePath,
};
use serde::Deserialize;
use thiserror::Error;
#[derive(Asset, TypePath, Debug, Deserialize)]
struct CustomAsset {
#[expect(
dead_code,
reason = "Used to show how the data inside an asset file will be loaded into the struct"
)]
value: i32,
}
#[derive(Default)]
struct CustomAssetLoader;
/// Possible errors that can be produced by [`CustomAssetLoader`]
#[non_exhaustive]
#[derive(Debug, Error)]
enum CustomAssetLoaderError {
/// An [IO](std::io) Error
#[error("Could not load asset: {0}")]
Io(#[from] std::io::Error),
/// A [RON](ron) Error
#[error("Could not parse RON: {0}")]
RonSpannedError(#[from] ron::error::SpannedError),
}
impl AssetLoader for CustomAssetLoader {
type Asset = CustomAsset;
type Settings = ();
type Error = CustomAssetLoaderError;
async fn load(
&self,
reader: &mut dyn Reader,
_settings: &(),
_load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let custom_asset = ron::de::from_bytes::<CustomAsset>(&bytes)?;
Ok(custom_asset)
}
fn extensions(&self) -> &[&str] {
&["custom"]
}
}
#[derive(Asset, TypePath, Debug)]
struct Blob {
bytes: Vec<u8>,
}
#[derive(Default)]
struct BlobAssetLoader;
/// Possible errors that can be produced by [`BlobAssetLoader`]
#[non_exhaustive]
#[derive(Debug, Error)]
enum BlobAssetLoaderError {
/// An [IO](std::io) Error
#[error("Could not load file: {0}")]
Io(#[from] std::io::Error),
}
impl AssetLoader for BlobAssetLoader {
type Asset = Blob;
type Settings = ();
type Error = BlobAssetLoaderError;
async fn load(
&self,
reader: &mut dyn Reader,
_settings: &(),
_load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
info!("Loading Blob...");
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
Ok(Blob { bytes })
}
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_resource::<State>()
.init_asset::<CustomAsset>()
.init_asset::<Blob>()
.init_asset_loader::<CustomAssetLoader>()
.init_asset_loader::<BlobAssetLoader>()
.add_systems(Startup, setup)
.add_systems(Update, print_on_load)
.run();
}
#[derive(Resource, Default)]
struct State {
handle: Handle<CustomAsset>,
other_handle: Handle<CustomAsset>,
blob: Handle<Blob>,
printed: bool,
}
fn setup(mut state: ResMut<State>, asset_server: Res<AssetServer>) {
// Recommended way to load an asset
state.handle = asset_server.load("data/asset.custom");
// File extensions are optional, but are recommended for project management and last-resort inference
state.other_handle = asset_server.load("data/asset_no_extension");
// Will use BlobAssetLoader instead of CustomAssetLoader thanks to type inference
state.blob = asset_server.load("data/asset.custom");
}
fn print_on_load(
mut state: ResMut<State>,
custom_assets: Res<Assets<CustomAsset>>,
blob_assets: Res<Assets<Blob>>,
) {
let custom_asset = custom_assets.get(&state.handle);
let other_custom_asset = custom_assets.get(&state.other_handle);
let blob = blob_assets.get(&state.blob);
// Can't print results if the assets aren't ready
if state.printed {
return;
}
if custom_asset.is_none() {
info!("Custom Asset Not Ready");
return;
}
if other_custom_asset.is_none() {
info!("Other Custom Asset Not Ready");
return;
}
if blob.is_none() {
info!("Blob Not Ready");
return;
}
info!("Custom asset loaded: {:?}", custom_asset.unwrap());
info!("Custom asset loaded: {:?}", other_custom_asset.unwrap());
info!("Blob Size: {} Bytes", blob.unwrap().bytes.len());
// Once printed, we won't print again
state.printed = true;
}

View File

@@ -0,0 +1,65 @@
//! Implements a custom asset io loader.
//! An [`AssetReader`] is what the asset server uses to read the raw bytes of assets.
//! It does not know anything about the asset formats, only how to talk to the underlying storage.
use bevy::{
asset::io::{
AssetReader, AssetReaderError, AssetSource, AssetSourceId, ErasedAssetReader, PathStream,
Reader,
},
prelude::*,
};
use std::path::Path;
/// A custom asset reader implementation that wraps a given asset reader implementation
struct CustomAssetReader(Box<dyn ErasedAssetReader>);
impl AssetReader for CustomAssetReader {
async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
info!("Reading {}", path.display());
self.0.read(path).await
}
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
self.0.read_meta(path).await
}
async fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> Result<Box<PathStream>, AssetReaderError> {
self.0.read_directory(path).await
}
async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
self.0.is_directory(path).await
}
}
/// A plugins that registers our new asset reader
struct CustomAssetReaderPlugin;
impl Plugin for CustomAssetReaderPlugin {
fn build(&self, app: &mut App) {
app.register_asset_source(
AssetSourceId::Default,
AssetSource::build().with_reader(|| {
Box::new(CustomAssetReader(
// This is the default reader for the current platform
AssetSource::get_default_reader("assets".to_string())(),
))
}),
);
}
}
fn main() {
App::new()
.add_plugins((CustomAssetReaderPlugin, DefaultPlugins))
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
commands.spawn(Sprite::from_image(asset_server.load("branding/icon.png")));
}

View File

@@ -0,0 +1,58 @@
//! Example of loading an embedded asset.
//! An embedded asset is an asset included in the program's memory, in contrast to other assets that are normally loaded from disk to memory when needed.
//! The below example embeds the asset at program startup, unlike the common use case of embedding an asset at build time. Embedded an asset at program startup can be useful
//! for things like loading screens, since it might be nice to display some art while other, non-embedded, assets are loading.
//! One common use case for embedded assets is including them directly within the executable during its creation. By embedding an asset at build time rather than runtime
//! the program never needs to go to disk for the asset at all, since it is already located in the program's binary executable.
use bevy::{
asset::{embedded_asset, io::AssetSourceId, AssetPath},
prelude::*,
};
use std::path::Path;
fn main() {
App::new()
.add_plugins((DefaultPlugins, EmbeddedAssetPlugin))
.add_systems(Startup, setup)
.run();
}
struct EmbeddedAssetPlugin;
impl Plugin for EmbeddedAssetPlugin {
fn build(&self, app: &mut App) {
// We get to choose some prefix relative to the workspace root which
// will be ignored in "embedded://" asset paths.
let omit_prefix = "examples/asset";
// Path to asset must be relative to this file, because that's how
// include_bytes! works.
embedded_asset!(app, omit_prefix, "files/bevy_pixel_light.png");
}
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
// Each example is its own crate (with name from [[example]] in Cargo.toml).
let crate_name = "embedded_asset";
// The actual file path relative to workspace root is
// "examples/asset/files/bevy_pixel_light.png".
//
// We omit the "examples/asset" from the embedded_asset! call and replace it
// with the crate name.
let path = Path::new(crate_name).join("files/bevy_pixel_light.png");
let source = AssetSourceId::from("embedded");
let asset_path = AssetPath::from_path(&path).with_source(source);
// You could also parse this URL-like string representation for the asset
// path.
assert_eq!(
asset_path,
"embedded://embedded_asset/files/bevy_pixel_light.png".into()
);
commands.spawn(Sprite::from_image(asset_server.load(asset_path)));
}

View File

@@ -0,0 +1,46 @@
//! An example of registering an extra asset source, and loading assets from it.
//! This asset source exists in addition to the default asset source.
use bevy::{
asset::{
io::{AssetSourceBuilder, AssetSourceId},
AssetPath,
},
prelude::*,
};
use std::path::Path;
fn main() {
App::new()
// Add an extra asset source with the name "example_files" to
// AssetSourceBuilders.
//
// This must be done before AssetPlugin finalizes building assets.
.register_asset_source(
"example_files",
AssetSourceBuilder::platform_default("examples/asset/files", None),
)
// DefaultPlugins contains AssetPlugin so it must be added to our App
// after inserting our new asset source.
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
// Now we can load the asset using our new asset source.
//
// The actual file path relative to workspace root is
// "examples/asset/files/bevy_pixel_light.png".
let path = Path::new("bevy_pixel_light.png");
let source = AssetSourceId::from("example_files");
let asset_path = AssetPath::from_path(path).with_source(source);
// You could also parse this URL-like string representation for the asset
// path.
assert_eq!(asset_path, "example_files://bevy_pixel_light.png".into());
commands.spawn(Sprite::from_image(asset_server.load(asset_path)));
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

View File

@@ -0,0 +1,25 @@
(
meta_format_version: "1.0",
asset: Load(
loader: "bevy_image::image_loader::ImageLoader",
settings: (
format: FromExtension,
is_srgb: true,
sampler: Descriptor (ImageSamplerDescriptor(
address_mode_u: ClampToEdge,
address_mode_v: ClampToEdge,
address_mode_w: ClampToEdge,
mag_filter: Nearest,
min_filter: Nearest,
mipmap_filter: Nearest,
lod_min_clamp: 0,
lod_max_clamp: 32.0,
compare: None,
anisotropy_clamp: 1,
border_color: None,
label: None,
)),
asset_usage: RenderAssetUsages("RENDER_WORLD | MAIN_WORLD"),
),
),
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

View File

@@ -0,0 +1,37 @@
//! Hot reloading allows you to modify assets files to be immediately reloaded while your game is
//! running. This lets you immediately see the results of your changes without restarting the game.
//! This example illustrates hot reloading mesh changes.
//!
//! Note that hot asset reloading requires the [`AssetWatcher`](bevy::asset::io::AssetWatcher) to be enabled
//! for your current platform. For desktop platforms, enable the `file_watcher` cargo feature.
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Load our mesh:
let scene_handle =
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/torus/torus.gltf"));
// Any changes to the mesh will be reloaded automatically! Try making a change to torus.gltf.
// You should see the changes immediately show up in your app.
// mesh
commands.spawn(SceneRoot(scene_handle));
// light
commands.spawn((
DirectionalLight::default(),
Transform::from_xyz(4.0, 5.0, 4.0).looking_at(Vec3::ZERO, Vec3::Y),
));
// camera
commands.spawn((
Camera3d::default(),
Transform::from_xyz(2.0, 2.0, 6.0).looking_at(Vec3::ZERO, Vec3::Y),
));
}

View File

@@ -0,0 +1,277 @@
//! This example illustrates how to wait for multiple assets to be loaded.
use std::{
f32::consts::PI,
ops::Drop,
sync::{
atomic::{AtomicBool, AtomicU32, Ordering},
Arc,
},
};
use bevy::{gltf::Gltf, prelude::*, tasks::AsyncComputeTaskPool};
use event_listener::Event;
use futures_lite::Future;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_state::<LoadingState>()
.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 2000.,
..default()
})
.add_systems(Startup, setup_assets)
.add_systems(Startup, setup_scene)
.add_systems(Startup, setup_ui)
// This showcases how to wait for assets using sync code.
// This approach polls a value in a system.
.add_systems(Update, wait_on_load.run_if(assets_loaded))
// This showcases how to wait for assets using async
// by spawning a `Future` in `AsyncComputeTaskPool`.
.add_systems(
Update,
get_async_loading_state.run_if(in_state(LoadingState::Loading)),
)
// This showcases how to react to asynchronous world mutation synchronously.
.add_systems(
OnExit(LoadingState::Loading),
despawn_loading_state_entities,
)
.run();
}
/// [`States`] of asset loading.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, States, Default)]
pub enum LoadingState {
/// Is loading.
#[default]
Loading,
/// Loading completed.
Loaded,
}
/// Holds a bunch of [`Gltf`]s that takes time to load.
#[derive(Debug, Resource)]
pub struct OneHundredThings([Handle<Gltf>; 100]);
/// This is required to support both sync and async.
///
/// For sync only the easiest implementation is
/// [`Arc<()>`] and use [`Arc::strong_count`] for completion.
/// [`Arc<Atomic>`] is a more robust alternative.
#[derive(Debug, Resource, Deref)]
pub struct AssetBarrier(Arc<AssetBarrierInner>);
/// This guard is to be acquired by [`AssetServer::load_acquire`]
/// and dropped once finished.
#[derive(Debug, Deref)]
pub struct AssetBarrierGuard(Arc<AssetBarrierInner>);
/// Tracks how many guards are remaining.
#[derive(Debug, Resource)]
pub struct AssetBarrierInner {
count: AtomicU32,
/// This can be omitted if async is not needed.
notify: Event,
}
/// State of loading asynchronously.
#[derive(Debug, Resource)]
pub struct AsyncLoadingState(Arc<AtomicBool>);
/// Entities that are to be removed once loading finished
#[derive(Debug, Component)]
pub struct Loading;
/// Marker for the "Loading..." Text component.
#[derive(Debug, Component)]
pub struct LoadingText;
impl AssetBarrier {
/// Create an [`AssetBarrier`] with a [`AssetBarrierGuard`].
pub fn new() -> (AssetBarrier, AssetBarrierGuard) {
let inner = Arc::new(AssetBarrierInner {
count: AtomicU32::new(1),
notify: Event::new(),
});
(AssetBarrier(inner.clone()), AssetBarrierGuard(inner))
}
/// Returns true if all [`AssetBarrierGuard`] is dropped.
pub fn is_ready(&self) -> bool {
self.count.load(Ordering::Acquire) == 0
}
/// Wait for all [`AssetBarrierGuard`]s to be dropped asynchronously.
pub fn wait_async(&self) -> impl Future<Output = ()> + 'static + use<> {
let shared = self.0.clone();
async move {
loop {
// Acquire an event listener.
let listener = shared.notify.listen();
// If all barrier guards are dropped, return
if shared.count.load(Ordering::Acquire) == 0 {
return;
}
// Wait for the last barrier guard to notify us
listener.await;
}
}
}
}
// Increment count on clone.
impl Clone for AssetBarrierGuard {
fn clone(&self) -> Self {
self.count.fetch_add(1, Ordering::AcqRel);
AssetBarrierGuard(self.0.clone())
}
}
// Decrement count on drop.
impl Drop for AssetBarrierGuard {
fn drop(&mut self) {
let prev = self.count.fetch_sub(1, Ordering::AcqRel);
if prev == 1 {
// Notify all listeners if count reaches 0.
self.notify.notify(usize::MAX);
}
}
}
fn setup_assets(mut commands: Commands, asset_server: Res<AssetServer>) {
let (barrier, guard) = AssetBarrier::new();
commands.insert_resource(OneHundredThings(std::array::from_fn(|i| match i % 5 {
0 => asset_server.load_acquire("models/GolfBall/GolfBall.glb", guard.clone()),
1 => asset_server.load_acquire("models/AlienCake/alien.glb", guard.clone()),
2 => asset_server.load_acquire("models/AlienCake/cakeBirthday.glb", guard.clone()),
3 => asset_server.load_acquire("models/FlightHelmet/FlightHelmet.gltf", guard.clone()),
4 => asset_server.load_acquire("models/torus/torus.gltf", guard.clone()),
_ => unreachable!(),
})));
let future = barrier.wait_async();
commands.insert_resource(barrier);
let loading_state = Arc::new(AtomicBool::new(false));
commands.insert_resource(AsyncLoadingState(loading_state.clone()));
// await the `AssetBarrierFuture`.
AsyncComputeTaskPool::get()
.spawn(async move {
future.await;
// Notify via `AsyncLoadingState`
loading_state.store(true, Ordering::Release);
})
.detach();
}
fn setup_ui(mut commands: Commands) {
// Display the result of async loading.
commands.spawn((
LoadingText,
Text::new("Loading...".to_owned()),
Node {
position_type: PositionType::Absolute,
left: Val::Px(12.0),
top: Val::Px(12.0),
..default()
},
));
}
fn setup_scene(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// Camera
commands.spawn((
Camera3d::default(),
Transform::from_xyz(10.0, 10.0, 15.0).looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),
));
// Light
commands.spawn((
DirectionalLight {
shadows_enabled: true,
..default()
},
Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)),
));
// Plane
commands.spawn((
Mesh3d(meshes.add(Plane3d::default().mesh().size(50000.0, 50000.0))),
MeshMaterial3d(materials.add(Color::srgb(0.7, 0.2, 0.2))),
Loading,
));
}
// A run condition for all assets being loaded.
fn assets_loaded(barrier: Option<Res<AssetBarrier>>) -> bool {
// If our barrier isn't ready, return early and wait another cycle
barrier.map(|b| b.is_ready()) == Some(true)
}
// This showcases how to wait for assets using sync code and systems.
//
// This function only runs if `assets_loaded` returns true.
fn wait_on_load(
mut commands: Commands,
foxes: Res<OneHundredThings>,
gltfs: Res<Assets<Gltf>>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// Change color of plane to green
commands.spawn((
Mesh3d(meshes.add(Plane3d::default().mesh().size(50000.0, 50000.0))),
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
Transform::from_translation(Vec3::Z * -0.01),
));
// Spawn our scenes.
for i in 0..10 {
for j in 0..10 {
let index = i * 10 + j;
let position = Vec3::new(i as f32 - 5.0, 0.0, j as f32 - 5.0);
// All gltfs must exist because this is guarded by the `AssetBarrier`.
let gltf = gltfs.get(&foxes.0[index]).unwrap();
let scene = gltf.scenes.first().unwrap().clone();
commands.spawn((SceneRoot(scene), Transform::from_translation(position)));
}
}
}
// This showcases how to wait for assets using async.
fn get_async_loading_state(
state: Res<AsyncLoadingState>,
mut next_loading_state: ResMut<NextState<LoadingState>>,
mut text: Query<&mut Text, With<LoadingText>>,
) {
// Load the value written by the `Future`.
let is_loaded = state.0.load(Ordering::Acquire);
// If loaded, change the state.
if is_loaded {
next_loading_state.set(LoadingState::Loaded);
if let Ok(mut text) = text.single_mut() {
"Loaded!".clone_into(&mut **text);
}
}
}
// This showcases how to react to asynchronous world mutations synchronously.
fn despawn_loading_state_entities(mut commands: Commands, loading: Query<Entity, With<Loading>>) {
// Despawn entities in the loading phase.
for entity in loading.iter() {
commands.entity(entity).despawn();
}
// Despawn resources used in the loading phase.
commands.remove_resource::<AssetBarrier>();
commands.remove_resource::<AsyncLoadingState>();
}

View File

@@ -0,0 +1,267 @@
//! This example illustrates how to define custom `AssetLoader`s, `AssetTransformer`s, and `AssetSaver`s, how to configure them, and how to register asset processors.
use bevy::{
asset::{
embedded_asset,
io::{Reader, Writer},
processor::LoadTransformAndSave,
saver::{AssetSaver, SavedAsset},
transformer::{AssetTransformer, TransformedAsset},
AssetLoader, AsyncWriteExt, LoadContext,
},
prelude::*,
reflect::TypePath,
};
use serde::{Deserialize, Serialize};
use std::convert::Infallible;
use thiserror::Error;
fn main() {
App::new()
// Using the "processed" mode will configure the AssetPlugin to use asset processing.
// If you also enable the `asset_processor` cargo feature, this will run the AssetProcessor
// in the background, run them through configured asset processors, and write the results to
// the `imported_assets` folder. If you also enable the `file_watcher` cargo feature, changes to the
// source assets will be detected and they will be reprocessed.
//
// The AssetProcessor will create `.meta` files automatically for assets in the `assets` folder,
// which can then be used to configure how the asset will be processed.
.add_plugins((
DefaultPlugins.set(AssetPlugin {
mode: AssetMode::Processed,
// This is just overriding the default paths to scope this to the correct example folder
// You can generally skip this in your own projects
file_path: "examples/asset/processing/assets".to_string(),
processed_file_path: "examples/asset/processing/imported_assets/Default"
.to_string(),
..default()
}),
TextPlugin,
))
.add_systems(Startup, setup)
.add_systems(Update, print_text)
.run();
}
/// This [`TextPlugin`] defines two assets types:
/// * [`CoolText`]: a custom RON text format that supports dependencies and embedded dependencies
/// * [`Text`]: a "normal" plain text file
///
/// It also defines an asset processor that will load [`CoolText`], resolve embedded dependencies, and write the resulting
/// output to a "normal" plain text file. When the processed asset is loaded, it is loaded as a Text (plaintext) asset.
/// This illustrates that when you process an asset, you can change its type! However you don't _need_ to change the type.
struct TextPlugin;
impl Plugin for TextPlugin {
fn build(&self, app: &mut App) {
embedded_asset!(app, "examples/asset/processing/", "e.txt");
app.init_asset::<CoolText>()
.init_asset::<Text>()
.register_asset_loader(CoolTextLoader)
.register_asset_loader(TextLoader)
.register_asset_processor::<LoadTransformAndSave<CoolTextLoader, CoolTextTransformer, CoolTextSaver>>(
LoadTransformAndSave::new(CoolTextTransformer, CoolTextSaver),
)
.set_default_asset_processor::<LoadTransformAndSave<CoolTextLoader, CoolTextTransformer, CoolTextSaver>>("cool.ron");
}
}
#[derive(Asset, TypePath, Debug)]
struct Text(String);
#[derive(Default)]
struct TextLoader;
#[derive(Clone, Default, Serialize, Deserialize)]
struct TextSettings {
text_override: Option<String>,
}
impl AssetLoader for TextLoader {
type Asset = Text;
type Settings = TextSettings;
type Error = std::io::Error;
async fn load(
&self,
reader: &mut dyn Reader,
settings: &TextSettings,
_load_context: &mut LoadContext<'_>,
) -> Result<Text, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let value = if let Some(ref text) = settings.text_override {
text.clone()
} else {
String::from_utf8(bytes).unwrap()
};
Ok(Text(value))
}
fn extensions(&self) -> &[&str] {
&["txt"]
}
}
#[derive(Serialize, Deserialize)]
struct CoolTextRon {
text: String,
dependencies: Vec<String>,
embedded_dependencies: Vec<String>,
dependencies_with_settings: Vec<(String, TextSettings)>,
}
#[derive(Asset, TypePath, Debug)]
struct CoolText {
text: String,
#[expect(
dead_code,
reason = "Used to show that our assets can hold handles to other assets"
)]
dependencies: Vec<Handle<Text>>,
}
#[derive(Default)]
struct CoolTextLoader;
#[derive(Debug, Error)]
enum CoolTextLoaderError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
RonSpannedError(#[from] ron::error::SpannedError),
#[error(transparent)]
LoadDirectError(#[from] bevy::asset::LoadDirectError),
}
impl AssetLoader for CoolTextLoader {
type Asset = CoolText;
type Settings = ();
type Error = CoolTextLoaderError;
async fn load(
&self,
reader: &mut dyn Reader,
_settings: &Self::Settings,
load_context: &mut LoadContext<'_>,
) -> Result<CoolText, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let ron: CoolTextRon = ron::de::from_bytes(&bytes)?;
let mut base_text = ron.text;
for embedded in ron.embedded_dependencies {
let loaded = load_context
.loader()
.immediate()
.load::<Text>(&embedded)
.await?;
base_text.push_str(&loaded.get().0);
}
for (path, settings_override) in ron.dependencies_with_settings {
let loaded = load_context
.loader()
.with_settings(move |settings| {
*settings = settings_override.clone();
})
.immediate()
.load::<Text>(&path)
.await?;
base_text.push_str(&loaded.get().0);
}
Ok(CoolText {
text: base_text,
dependencies: ron
.dependencies
.iter()
.map(|p| load_context.load(p))
.collect(),
})
}
fn extensions(&self) -> &[&str] {
&["cool.ron"]
}
}
#[derive(Default)]
struct CoolTextTransformer;
#[derive(Default, Serialize, Deserialize)]
struct CoolTextTransformerSettings {
appended: String,
}
impl AssetTransformer for CoolTextTransformer {
type AssetInput = CoolText;
type AssetOutput = CoolText;
type Settings = CoolTextTransformerSettings;
type Error = Infallible;
async fn transform<'a>(
&'a self,
mut asset: TransformedAsset<Self::AssetInput>,
settings: &'a Self::Settings,
) -> Result<TransformedAsset<Self::AssetOutput>, Self::Error> {
asset.text = format!("{}{}", asset.text, settings.appended);
Ok(asset)
}
}
struct CoolTextSaver;
impl AssetSaver for CoolTextSaver {
type Asset = CoolText;
type Settings = ();
type OutputLoader = TextLoader;
type Error = std::io::Error;
async fn save(
&self,
writer: &mut Writer,
asset: SavedAsset<'_, Self::Asset>,
_settings: &Self::Settings,
) -> Result<TextSettings, Self::Error> {
writer.write_all(asset.text.as_bytes()).await?;
Ok(TextSettings::default())
}
}
#[derive(Resource)]
struct TextAssets {
a: Handle<Text>,
b: Handle<Text>,
c: Handle<Text>,
d: Handle<Text>,
e: Handle<Text>,
}
fn setup(mut commands: Commands, assets: Res<AssetServer>) {
// This the final processed versions of `assets/a.cool.ron` and `assets/foo.c.cool.ron`
// Check out their counterparts in `imported_assets` to see what the outputs look like.
commands.insert_resource(TextAssets {
a: assets.load("a.cool.ron"),
b: assets.load("foo/b.cool.ron"),
c: assets.load("foo/c.cool.ron"),
d: assets.load("d.cool.ron"),
e: assets.load("embedded://asset_processing/e.txt"),
});
}
fn print_text(
handles: Res<TextAssets>,
texts: Res<Assets<Text>>,
mut asset_events: EventReader<AssetEvent<Text>>,
) {
if !asset_events.is_empty() {
// This prints the current values of the assets
// Hot-reloading is supported, so try modifying the source assets (and their meta files)!
println!("Current Values:");
println!(" a: {:?}", texts.get(&handles.a));
println!(" b: {:?}", texts.get(&handles.b));
println!(" c: {:?}", texts.get(&handles.c));
println!(" d: {:?}", texts.get(&handles.d));
println!(" e: {:?}", texts.get(&handles.e));
println!("(You can modify source assets and their .meta files to hot-reload changes!)");
println!();
asset_events.clear();
}
}

View File

@@ -0,0 +1 @@
e

View File

@@ -0,0 +1,93 @@
//! By default Bevy loads images to textures that clamps the image to the edges
//! This example shows how to configure it to repeat the image instead.
use bevy::{
image::{ImageAddressMode, ImageLoaderSettings, ImageSampler, ImageSamplerDescriptor},
math::Affine2,
prelude::*,
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let image_with_default_sampler =
asset_server.load("textures/fantasy_ui_borders/panel-border-010.png");
// central cube with not repeated texture
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
MeshMaterial3d(materials.add(StandardMaterial {
base_color_texture: Some(image_with_default_sampler.clone()),
..default()
})),
Transform::from_translation(Vec3::ZERO),
));
// left cube with repeated texture
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
MeshMaterial3d(materials.add(StandardMaterial {
base_color_texture: Some(asset_server.load_with_settings(
"textures/fantasy_ui_borders/panel-border-010-repeated.png",
|s: &mut _| {
*s = ImageLoaderSettings {
sampler: ImageSampler::Descriptor(ImageSamplerDescriptor {
// rewriting mode to repeat image,
address_mode_u: ImageAddressMode::Repeat,
address_mode_v: ImageAddressMode::Repeat,
..default()
}),
..default()
}
},
)),
// uv_transform used here for proportions only, but it is full Affine2
// that's why you can use rotation and shift also
uv_transform: Affine2::from_scale(Vec2::new(2., 3.)),
..default()
})),
Transform::from_xyz(-1.5, 0.0, 0.0),
));
// right cube with scaled texture, because with default sampler
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
MeshMaterial3d(materials.add(StandardMaterial {
// there is no sampler set, that's why
// by default you see only one small image in a row/column
// and other space is filled by image edge
base_color_texture: Some(image_with_default_sampler),
// uv_transform used here for proportions only, but it is full Affine2
// that's why you can use rotation and shift also
uv_transform: Affine2::from_scale(Vec2::new(2., 3.)),
..default()
})),
Transform::from_xyz(1.5, 0.0, 0.0),
));
// light
commands.spawn((
PointLight {
shadows_enabled: true,
..default()
},
Transform::from_xyz(4.0, 8.0, 4.0),
));
// camera
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 1.5, 4.0).looking_at(Vec3::ZERO, Vec3::Y),
));
}