From d466b0c398d1c51fdb94bb83c56eeb9e60fd82f7 Mon Sep 17 00:00:00 2001 From: antia Date: Tue, 10 Oct 2023 08:36:50 +0200 Subject: [PATCH 1/5] update texture_atlas example with examples of different padding and sampling --- examples/2d/texture_atlas.rs | 182 +++++++++++++++++++++++++++++------ 1 file changed, 152 insertions(+), 30 deletions(-) diff --git a/examples/2d/texture_atlas.rs b/examples/2d/texture_atlas.rs index ce62f25e2fbb2..3c4e43673ae62 100644 --- a/examples/2d/texture_atlas.rs +++ b/examples/2d/texture_atlas.rs @@ -1,11 +1,18 @@ -//! In this example we generate a new texture atlas (sprite sheet) from a folder containing +//! In this example we generate 4 texture atlases (sprite sheets) from a folder containing //! individual sprites. +//! +//! The texture atlases are generated with different padding and sampling to demonstrate the +//! effect of these settings, and how bleeding issues can be resolved by padding the sprites. +//! +//! Only one padded and one unpadded texture atlas are rendered to the screen. +//! An upscaled sprite from each of the 4 atlases are rendered to the screen. use bevy::{asset::LoadedFolder, prelude::*}; +use bevy_internal::render::texture::ImageSampler; fn main() { App::new() - .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) // prevents blurry sprites + .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) // fallback to nearest sampling .add_state::() .add_systems(OnEnter(AppState::Setup), load_textures) .add_systems(Update, check_textures.run_if(in_state(AppState::Setup))) @@ -49,47 +56,162 @@ fn setup( mut texture_atlases: ResMut>, mut textures: ResMut>, ) { - // Build a `TextureAtlas` using the individual sprites - let mut texture_atlas_builder = TextureAtlasBuilder::default(); let loaded_folder = loaded_folders.get(&rpg_sprite_handles.0).unwrap(); - for handle in loaded_folder.handles.iter() { - let id = handle.id().typed_unchecked::(); - let Some(texture) = textures.get(id) else { - warn!( - "{:?} did not resolve to an `Image` asset.", - handle.path().unwrap() - ); - continue; - }; - texture_atlas_builder.add_texture(id, texture); - } + // create texture atlases with different padding and sampling + + let texture_atlas_linear = create_texture_atlas( + loaded_folder, + None, + Some(ImageSampler::linear()), + &mut textures, + ); + let atlas_linear_handle = texture_atlases.add(texture_atlas_linear.clone()); + + let texture_atlas_nearest = create_texture_atlas( + loaded_folder, + None, + Some(ImageSampler::nearest()), + &mut textures, + ); + let atlas_nearest_handle = texture_atlases.add(texture_atlas_nearest.clone()); + + let texture_atlas_linear_padded = create_texture_atlas( + loaded_folder, + Some(UVec2::new(6, 6)), + Some(ImageSampler::linear()), + &mut textures, + ); + let atlas_linear_padded_handle = texture_atlases.add(texture_atlas_linear_padded.clone()); + + let texture_atlas_nearest_padded = create_texture_atlas( + loaded_folder, + Some(UVec2::new(6, 6)), + Some(ImageSampler::nearest()), + &mut textures, + ); + let atlas_nearest_padded_handle = texture_atlases.add(texture_atlas_nearest_padded.clone()); + + // setup 2d scene + commands.spawn(Camera2dBundle::default()); - let texture_atlas = texture_atlas_builder.finish(&mut textures).unwrap(); - let texture_atlas_texture = texture_atlas.texture.clone(); - let vendor_handle = asset_server + // padded textures are to the right, unpadded to the left + + // draw unpadded texture atlas + commands.spawn(SpriteBundle { + texture: texture_atlas_linear.clone().texture, + transform: Transform::from_xyz(-500.0, -200.0, 0.0), + ..default() + }); + + // draw padded texture atlas + commands.spawn(SpriteBundle { + texture: texture_atlas_linear_padded.clone().texture, + transform: Transform::from_xyz(500.0, -200.0, 0.0), + ..default() + }); + + // draw sprites from texture atlases + + // get handle to a sprite to render + let vendor_handle: Handle = asset_server .get_handle("textures/rpg/chars/vendor/generic-rpg-vendor.png") .unwrap(); - let vendor_index = texture_atlas.get_texture_index(&vendor_handle).unwrap(); - let atlas_handle = texture_atlases.add(texture_atlas); - // set up a scene to display our texture atlas - commands.spawn(Camera2dBundle::default()); - // draw a sprite from the atlas + // linear, no padding commands.spawn(SpriteSheetBundle { transform: Transform { - translation: Vec3::new(150.0, 0.0, 0.0), + translation: Vec3::new(-650.0, 250.0, 0.0), scale: Vec3::splat(4.0), ..default() }, - sprite: TextureAtlasSprite::new(vendor_index), - texture_atlas: atlas_handle, + sprite: TextureAtlasSprite::new( + texture_atlas_linear + .get_texture_index(&vendor_handle) + .unwrap(), + ), + texture_atlas: atlas_linear_handle, ..default() }); - // draw the atlas itself - commands.spawn(SpriteBundle { - texture: texture_atlas_texture, - transform: Transform::from_xyz(-300.0, 0.0, 0.0), + + // linear, padding + commands.spawn(SpriteSheetBundle { + transform: Transform { + translation: Vec3::new(250.0, 250.0, 0.0), + scale: Vec3::splat(4.0), + ..default() + }, + sprite: TextureAtlasSprite::new( + texture_atlas_linear_padded + .get_texture_index(&vendor_handle) + .unwrap(), + ), + texture_atlas: atlas_linear_padded_handle, + ..default() + }); + + // nearest, no padding + commands.spawn(SpriteSheetBundle { + transform: Transform { + translation: Vec3::new(-250.0, 250.0, 0.0), + scale: Vec3::splat(4.0), + ..default() + }, + sprite: TextureAtlasSprite::new( + texture_atlas_nearest + .get_texture_index(&vendor_handle) + .unwrap(), + ), + texture_atlas: atlas_nearest_handle, + ..default() + }); + + // nearest, padding + commands.spawn(SpriteSheetBundle { + transform: Transform { + translation: Vec3::new(650.0, 250.0, 0.0), + scale: Vec3::splat(4.0), + ..default() + }, + sprite: TextureAtlasSprite::new( + texture_atlas_nearest_padded + .get_texture_index(&vendor_handle) + .unwrap(), + ), + texture_atlas: atlas_nearest_padded_handle, ..default() }); } + +/// Create a texture atlas with the given padding and sampling settings +/// from the individual sprites in the given folder. +fn create_texture_atlas( + folder: &LoadedFolder, + padding: Option, + sampling: Option, + textures: &mut ResMut>, +) -> TextureAtlas { + // Build a `TextureAtlas` using the individual sprites + let mut texture_atlas_builder = + TextureAtlasBuilder::default().padding(padding.unwrap_or_default()); + for handle in folder.handles.iter() { + let id = handle.id().typed_unchecked::(); + let Some(texture) = textures.get(id) else { + warn!( + "{:?} did not resolve to an `Image` asset.", + handle.path().unwrap() + ); + continue; + }; + + texture_atlas_builder.add_texture(id, texture); + } + + let texture_atlas = texture_atlas_builder.finish(textures).unwrap(); + + // Update the sampling settings of the texture atlas + let image = textures.get_mut(&texture_atlas.texture).unwrap(); + image.sampler_descriptor = sampling.unwrap_or_default(); + + texture_atlas +} From ed48aed001caf7bbdd8040b5f2a8562366230602 Mon Sep 17 00:00:00 2001 From: antia Date: Wed, 11 Oct 2023 21:19:51 +0200 Subject: [PATCH 2/5] fit example to default window size, clean up code --- examples/2d/texture_atlas.rs | 48 +++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/examples/2d/texture_atlas.rs b/examples/2d/texture_atlas.rs index 3c4e43673ae62..b5db7c392ca74 100644 --- a/examples/2d/texture_atlas.rs +++ b/examples/2d/texture_atlas.rs @@ -7,8 +7,7 @@ //! Only one padded and one unpadded texture atlas are rendered to the screen. //! An upscaled sprite from each of the 4 atlases are rendered to the screen. -use bevy::{asset::LoadedFolder, prelude::*}; -use bevy_internal::render::texture::ImageSampler; +use bevy::{asset::LoadedFolder, prelude::*, render::texture::ImageSampler}; fn main() { App::new() @@ -41,6 +40,7 @@ fn check_textures( mut events: EventReader>, ) { // Advance the `AppState` once all sprite handles have been loaded by the `AssetServer` + // and that the the font has been loaded by the `FontSystem`. for event in events.read() { if event.is_loaded_with_dependencies(&rpg_sprite_folder.0) { next_state.set(AppState::Finished); @@ -99,15 +99,23 @@ fn setup( // draw unpadded texture atlas commands.spawn(SpriteBundle { - texture: texture_atlas_linear.clone().texture, - transform: Transform::from_xyz(-500.0, -200.0, 0.0), + texture: texture_atlas_linear_padded.texture.clone(), + transform: Transform { + translation: Vec3::new(-250.0, -130.0, 0.0), + scale: Vec3::splat(0.8), + ..default() + }, ..default() }); // draw padded texture atlas commands.spawn(SpriteBundle { - texture: texture_atlas_linear_padded.clone().texture, - transform: Transform::from_xyz(500.0, -200.0, 0.0), + texture: texture_atlas_linear_padded.texture.clone(), + transform: Transform { + translation: Vec3::new(250.0, -130.0, 0.0), + scale: Vec3::splat(0.8), + ..default() + }, ..default() }); @@ -121,8 +129,8 @@ fn setup( // linear, no padding commands.spawn(SpriteSheetBundle { transform: Transform { - translation: Vec3::new(-650.0, 250.0, 0.0), - scale: Vec3::splat(4.0), + translation: Vec3::new(-350.0, 170.0, 0.0), + scale: Vec3::splat(3.0), ..default() }, sprite: TextureAtlasSprite::new( @@ -134,43 +142,43 @@ fn setup( ..default() }); - // linear, padding + // nearest, no padding commands.spawn(SpriteSheetBundle { transform: Transform { - translation: Vec3::new(250.0, 250.0, 0.0), - scale: Vec3::splat(4.0), + translation: Vec3::new(-150.0, 170.0, 0.0), + scale: Vec3::splat(3.0), ..default() }, sprite: TextureAtlasSprite::new( - texture_atlas_linear_padded + texture_atlas_nearest .get_texture_index(&vendor_handle) .unwrap(), ), - texture_atlas: atlas_linear_padded_handle, + texture_atlas: atlas_nearest_handle, ..default() }); - // nearest, no padding + // linear, padding commands.spawn(SpriteSheetBundle { transform: Transform { - translation: Vec3::new(-250.0, 250.0, 0.0), - scale: Vec3::splat(4.0), + translation: Vec3::new(150.0, 170.0, 0.0), + scale: Vec3::splat(3.0), ..default() }, sprite: TextureAtlasSprite::new( - texture_atlas_nearest + texture_atlas_linear_padded .get_texture_index(&vendor_handle) .unwrap(), ), - texture_atlas: atlas_nearest_handle, + texture_atlas: atlas_linear_padded_handle, ..default() }); // nearest, padding commands.spawn(SpriteSheetBundle { transform: Transform { - translation: Vec3::new(650.0, 250.0, 0.0), - scale: Vec3::splat(4.0), + translation: Vec3::new(350.0, 170.0, 0.0), + scale: Vec3::splat(3.0), ..default() }, sprite: TextureAtlasSprite::new( From 4a5fdf23b699402e312daf18bb0dcca51d78889c Mon Sep 17 00:00:00 2001 From: antia Date: Fri, 13 Oct 2023 20:17:42 +0200 Subject: [PATCH 3/5] Clean up code with functions for creating sprites Add labels to indicate the different settings Co-authored-by: davidasberg --- examples/2d/texture_atlas.rs | 185 ++++++++++++++++++++++++----------- 1 file changed, 126 insertions(+), 59 deletions(-) diff --git a/examples/2d/texture_atlas.rs b/examples/2d/texture_atlas.rs index b5db7c392ca74..59a2b5a46473d 100644 --- a/examples/2d/texture_atlas.rs +++ b/examples/2d/texture_atlas.rs @@ -74,7 +74,7 @@ fn setup( Some(ImageSampler::nearest()), &mut textures, ); - let atlas_nearest_handle = texture_atlases.add(texture_atlas_nearest.clone()); + let atlas_nearest_handle = texture_atlases.add(texture_atlas_nearest); let texture_atlas_linear_padded = create_texture_atlas( loaded_folder, @@ -90,7 +90,7 @@ fn setup( Some(ImageSampler::nearest()), &mut textures, ); - let atlas_nearest_padded_handle = texture_atlases.add(texture_atlas_nearest_padded.clone()); + let atlas_nearest_padded_handle = texture_atlases.add(texture_atlas_nearest_padded); // setup 2d scene commands.spawn(Camera2dBundle::default()); @@ -110,7 +110,7 @@ fn setup( // draw padded texture atlas commands.spawn(SpriteBundle { - texture: texture_atlas_linear_padded.texture.clone(), + texture: texture_atlas_linear_padded.texture, transform: Transform { translation: Vec3::new(250.0, -130.0, 0.0), scale: Vec3::splat(0.8), @@ -126,69 +126,100 @@ fn setup( .get_handle("textures/rpg/chars/vendor/generic-rpg-vendor.png") .unwrap(); + // get index of the sprite in the texture atlas, this is used to render the sprite + // the index is the same for all the texture atlases, since they are created from the same folder + let vendor_index = texture_atlas_linear + .get_texture_index(&vendor_handle) + .unwrap(); + // linear, no padding - commands.spawn(SpriteSheetBundle { - transform: Transform { - translation: Vec3::new(-350.0, 170.0, 0.0), - scale: Vec3::splat(3.0), - ..default() - }, - sprite: TextureAtlasSprite::new( - texture_atlas_linear - .get_texture_index(&vendor_handle) - .unwrap(), - ), - texture_atlas: atlas_linear_handle, - ..default() - }); + create_sprite_from_atlas( + &mut commands, + (-350.0, 170.0, 0.0), + vendor_index, + atlas_linear_handle, + ); // nearest, no padding - commands.spawn(SpriteSheetBundle { - transform: Transform { - translation: Vec3::new(-150.0, 170.0, 0.0), - scale: Vec3::splat(3.0), - ..default() - }, - sprite: TextureAtlasSprite::new( - texture_atlas_nearest - .get_texture_index(&vendor_handle) - .unwrap(), - ), - texture_atlas: atlas_nearest_handle, - ..default() - }); + create_sprite_from_atlas( + &mut commands, + (-150.0, 170.0, 0.0), + vendor_index, + atlas_nearest_handle, + ); // linear, padding - commands.spawn(SpriteSheetBundle { - transform: Transform { - translation: Vec3::new(150.0, 170.0, 0.0), - scale: Vec3::splat(3.0), - ..default() - }, - sprite: TextureAtlasSprite::new( - texture_atlas_linear_padded - .get_texture_index(&vendor_handle) - .unwrap(), - ), - texture_atlas: atlas_linear_padded_handle, - ..default() - }); + create_sprite_from_atlas( + &mut commands, + (150.0, 170.0, 0.0), + vendor_index, + atlas_linear_padded_handle, + ); // nearest, padding - commands.spawn(SpriteSheetBundle { - transform: Transform { - translation: Vec3::new(350.0, 170.0, 0.0), - scale: Vec3::splat(3.0), - ..default() - }, - sprite: TextureAtlasSprite::new( - texture_atlas_nearest_padded - .get_texture_index(&vendor_handle) - .unwrap(), - ), - texture_atlas: atlas_nearest_padded_handle, - ..default() - }); + create_sprite_from_atlas( + &mut commands, + (350.0, 170.0, 0.0), + vendor_index, + atlas_nearest_padded_handle, + ); + + // labels to indicate the different settings + let font = asset_server.load("fonts/FiraSans-Bold.ttf"); + let text_style = TextStyle { + font: font.clone(), + font_size: 50.0, + color: Color::WHITE, + }; + + // No padding + create_label( + &mut commands, + (-250.0, 330.0, 0.0), + "No padding", + text_style.clone(), + ); + + // Padding + create_label(&mut commands, (250.0, 330.0, 0.0), "Padding", text_style); + + let text_style_smaller = TextStyle { + font, + font_size: 30.0, + color: Color::WHITE, + }; + + // Linear, left + create_label( + &mut commands, + (-350.0, 280.0, 0.0), + "Linear", + text_style_smaller.clone(), + ); + + // Nearest, left + create_label( + &mut commands, + (-150.0, 280.0, 0.0), + "Nearest", + text_style_smaller.clone(), + ); + + // Linear, right + create_label( + &mut commands, + (150.0, 280.0, 0.0), + "Linear", + text_style_smaller.clone(), + ); + + // Nearest, right + create_label( + &mut commands, + (350.0, 280.0, 0.0), + "Nearest", + text_style_smaller, + ); } /// Create a texture atlas with the given padding and sampling settings @@ -223,3 +254,39 @@ fn create_texture_atlas( texture_atlas } + +/// Create and spawn a sprite from a texture atlas +fn create_sprite_from_atlas( + commands: &mut Commands, + translation: (f32, f32, f32), + sprite_index: usize, + atlas_handle: Handle, +) { + commands.spawn(SpriteSheetBundle { + transform: Transform { + translation: Vec3::new(translation.0, translation.1, translation.2), + scale: Vec3::splat(3.0), + ..default() + }, + sprite: TextureAtlasSprite::new(sprite_index), + texture_atlas: atlas_handle, + ..default() + }); +} + +/// Create and spawn a label (text) +fn create_label( + commands: &mut Commands, + translation: (f32, f32, f32), + text: &str, + text_style: TextStyle, +) { + commands.spawn(Text2dBundle { + text: Text::from_section(text, text_style).with_alignment(TextAlignment::Center), + transform: Transform { + translation: Vec3::new(translation.0, translation.1, translation.2), + ..default() + }, + ..default() + }); +} From ebfb44d2f28780df55bbd03e7f07049e38745395 Mon Sep 17 00:00:00 2001 From: antia Date: Thu, 14 Dec 2023 17:30:07 +0100 Subject: [PATCH 4/5] clean up example with iterators --- examples/2d/texture_atlas.rs | 119 ++++++++++++----------------------- 1 file changed, 40 insertions(+), 79 deletions(-) diff --git a/examples/2d/texture_atlas.rs b/examples/2d/texture_atlas.rs index 59a2b5a46473d..6babd3fe540c0 100644 --- a/examples/2d/texture_atlas.rs +++ b/examples/2d/texture_atlas.rs @@ -1,11 +1,11 @@ -//! In this example we generate 4 texture atlases (sprite sheets) from a folder containing +//! In this example we generate four texture atlases (sprite sheets) from a folder containing //! individual sprites. //! //! The texture atlases are generated with different padding and sampling to demonstrate the //! effect of these settings, and how bleeding issues can be resolved by padding the sprites. //! //! Only one padded and one unpadded texture atlas are rendered to the screen. -//! An upscaled sprite from each of the 4 atlases are rendered to the screen. +//! An upscaled sprite from each of the four atlases are rendered to the screen. use bevy::{asset::LoadedFolder, prelude::*, render::texture::ImageSampler}; @@ -119,59 +119,17 @@ fn setup( ..default() }); - // draw sprites from texture atlases - - // get handle to a sprite to render - let vendor_handle: Handle = asset_server - .get_handle("textures/rpg/chars/vendor/generic-rpg-vendor.png") - .unwrap(); - - // get index of the sprite in the texture atlas, this is used to render the sprite - // the index is the same for all the texture atlases, since they are created from the same folder - let vendor_index = texture_atlas_linear - .get_texture_index(&vendor_handle) - .unwrap(); - - // linear, no padding - create_sprite_from_atlas( - &mut commands, - (-350.0, 170.0, 0.0), - vendor_index, - atlas_linear_handle, - ); - - // nearest, no padding - create_sprite_from_atlas( - &mut commands, - (-150.0, 170.0, 0.0), - vendor_index, - atlas_nearest_handle, - ); - - // linear, padding - create_sprite_from_atlas( - &mut commands, - (150.0, 170.0, 0.0), - vendor_index, - atlas_linear_padded_handle, - ); - - // nearest, padding - create_sprite_from_atlas( - &mut commands, - (350.0, 170.0, 0.0), - vendor_index, - atlas_nearest_padded_handle, - ); - - // labels to indicate the different settings let font = asset_server.load("fonts/FiraSans-Bold.ttf"); - let text_style = TextStyle { + + // padding label text style + let text_style: TextStyle = TextStyle { font: font.clone(), font_size: 50.0, color: Color::WHITE, }; + // labels to indicate padding + // No padding create_label( &mut commands, @@ -183,43 +141,46 @@ fn setup( // Padding create_label(&mut commands, (250.0, 330.0, 0.0), "Padding", text_style); - let text_style_smaller = TextStyle { + // get handle to a sprite to render + let vendor_handle: Handle = asset_server + .get_handle("textures/rpg/chars/vendor/generic-rpg-vendor.png") + .unwrap(); + + // get index of the sprite in the texture atlas, this is used to render the sprite + // the index is the same for all the texture atlases, since they are created from the same folder + let vendor_index = texture_atlas_linear + .get_texture_index(&vendor_handle) + .unwrap(); + + // configuration array to render sprites through iteration + let configurations: [(&str, Handle, f32); 4] = [ + ("Linear", atlas_linear_handle, -350.0), + ("Nearest", atlas_nearest_handle, -150.0), + ("Linear", atlas_linear_padded_handle, 150.0), + ("Nearest", atlas_nearest_padded_handle, 350.0), + ]; + + // label text style + let sampling_label_style = TextStyle { font, font_size: 30.0, color: Color::WHITE, }; - // Linear, left - create_label( - &mut commands, - (-350.0, 280.0, 0.0), - "Linear", - text_style_smaller.clone(), - ); - - // Nearest, left - create_label( - &mut commands, - (-150.0, 280.0, 0.0), - "Nearest", - text_style_smaller.clone(), - ); + let base_y = 170.0; // y position of the sprites - // Linear, right - create_label( - &mut commands, - (150.0, 280.0, 0.0), - "Linear", - text_style_smaller.clone(), - ); + for (sampling, atlas_handle, x) in configurations { + // render a sprite from the texture_atlas + create_sprite_from_atlas(&mut commands, (x, base_y, 0.0), vendor_index, atlas_handle); - // Nearest, right - create_label( - &mut commands, - (350.0, 280.0, 0.0), - "Nearest", - text_style_smaller, - ); + // render a label to indicate the sampling setting + create_label( + &mut commands, + (x, base_y + 110.0, 0.0), // offset to y position of the sprite + sampling, + sampling_label_style.clone(), + ); + } } /// Create a texture atlas with the given padding and sampling settings From 3f56e5ab2d54c7b0793872491f700bd59732b174 Mon Sep 17 00:00:00 2001 From: antia Date: Thu, 14 Dec 2023 18:33:14 +0100 Subject: [PATCH 5/5] update texture_atlas example to work with new image sampler api and text justify --- examples/2d/texture_atlas.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/2d/texture_atlas.rs b/examples/2d/texture_atlas.rs index c63dbdad3e5d9..566e07ef58f8c 100644 --- a/examples/2d/texture_atlas.rs +++ b/examples/2d/texture_atlas.rs @@ -211,7 +211,7 @@ fn create_texture_atlas( // Update the sampling settings of the texture atlas let image = textures.get_mut(&texture_atlas.texture).unwrap(); - image.sampler_descriptor = sampling.unwrap_or_default(); + image.sampler = sampling.unwrap_or_default(); texture_atlas } @@ -243,7 +243,7 @@ fn create_label( text_style: TextStyle, ) { commands.spawn(Text2dBundle { - text: Text::from_section(text, text_style).with_alignment(TextAlignment::Center), + text: Text::from_section(text, text_style).with_justify(JustifyText::Center), transform: Transform { translation: Vec3::new(translation.0, translation.1, translation.2), ..default()