mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Update texture_atlas example with different padding and sampling (#10073)
# Objective - Expand the texture_atlas example with padding and show how it can resolve sprite bleeding for different types of sampling. - Fixes #9522 ## Solution Updated the texture_atlas example by adding 4 different texture atlases: 1. linear, no padding 2. linear, padding 3. nearest neighbor, no padding 4. nearest neighbor, padding Now renders one padded and one unpadded texture atlas, and the same upscaled sprite from each of the new texture atlases. See the screenshot below (taken on 1080p monitor). ![Screenshot from 2023-10-10 08-37-43](https://github.com/bevyengine/bevy/assets/46004494/4cef707c-e117-4835-b2c8-66503d8c275f) **From left->right:** linear no padding, nearest no padding, linear padding, nearest padding. --- --------- Co-authored-by: davidasberg <david.aasberg@gmail.com>
This commit is contained in:
parent
16c5a4b7cd
commit
ca1874e6c6
1 changed files with 181 additions and 23 deletions
|
@ -1,11 +1,17 @@
|
|||
//! In this example we generate a new texture atlas (sprite sheet) 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 four atlases are rendered to the screen.
|
||||
|
||||
use bevy::{asset::LoadedFolder, prelude::*};
|
||||
use bevy::{asset::LoadedFolder, prelude::*, 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::<AppState>()
|
||||
.add_systems(OnEnter(AppState::Setup), load_textures)
|
||||
.add_systems(Update, check_textures.run_if(in_state(AppState::Setup)))
|
||||
|
@ -34,6 +40,7 @@ fn check_textures(
|
|||
mut events: EventReader<AssetEvent<LoadedFolder>>,
|
||||
) {
|
||||
// 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);
|
||||
|
@ -49,10 +56,145 @@ fn setup(
|
|||
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
||||
mut textures: ResMut<Assets<Image>>,
|
||||
) {
|
||||
// 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() {
|
||||
|
||||
// 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);
|
||||
|
||||
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);
|
||||
|
||||
// setup 2d scene
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
|
||||
// padded textures are to the right, unpadded to the left
|
||||
|
||||
// draw unpadded texture atlas
|
||||
commands.spawn(SpriteBundle {
|
||||
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.texture,
|
||||
transform: Transform {
|
||||
translation: Vec3::new(250.0, -130.0, 0.0),
|
||||
scale: Vec3::splat(0.8),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
});
|
||||
|
||||
let font = asset_server.load("fonts/FiraSans-Bold.ttf");
|
||||
|
||||
// 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,
|
||||
(-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);
|
||||
|
||||
// get handle to a sprite to render
|
||||
let vendor_handle: Handle<Image> = 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<TextureAtlas>, 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,
|
||||
};
|
||||
|
||||
let base_y = 170.0; // y position of the sprites
|
||||
|
||||
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);
|
||||
|
||||
// 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 the individual sprites in the given folder.
|
||||
fn create_texture_atlas(
|
||||
folder: &LoadedFolder,
|
||||
padding: Option<UVec2>,
|
||||
sampling: Option<ImageSampler>,
|
||||
textures: &mut ResMut<Assets<Image>>,
|
||||
) -> 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::<Image>();
|
||||
let Some(texture) = textures.get(id) else {
|
||||
warn!(
|
||||
|
@ -65,31 +207,47 @@ fn setup(
|
|||
texture_atlas_builder.add_texture(id, texture);
|
||||
}
|
||||
|
||||
let texture_atlas = texture_atlas_builder.finish(&mut textures).unwrap();
|
||||
let texture_atlas_texture = texture_atlas.texture.clone();
|
||||
let vendor_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);
|
||||
let texture_atlas = texture_atlas_builder.finish(textures).unwrap();
|
||||
|
||||
// set up a scene to display our texture atlas
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
// draw a sprite from the atlas
|
||||
// Update the sampling settings of the texture atlas
|
||||
let image = textures.get_mut(&texture_atlas.texture).unwrap();
|
||||
image.sampler = sampling.unwrap_or_default();
|
||||
|
||||
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<TextureAtlas>,
|
||||
) {
|
||||
commands.spawn(SpriteSheetBundle {
|
||||
transform: Transform {
|
||||
translation: Vec3::new(150.0, 0.0, 0.0),
|
||||
scale: Vec3::splat(4.0),
|
||||
translation: Vec3::new(translation.0, translation.1, translation.2),
|
||||
scale: Vec3::splat(3.0),
|
||||
..default()
|
||||
},
|
||||
sprite: TextureAtlasSprite::new(vendor_index),
|
||||
sprite: TextureAtlasSprite::new(sprite_index),
|
||||
texture_atlas: atlas_handle,
|
||||
..default()
|
||||
});
|
||||
// draw the atlas itself
|
||||
commands.spawn(SpriteBundle {
|
||||
texture: texture_atlas_texture,
|
||||
transform: Transform::from_xyz(-300.0, 0.0, 0.0),
|
||||
}
|
||||
|
||||
/// 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_justify(JustifyText::Center),
|
||||
transform: Transform {
|
||||
translation: Vec3::new(translation.0, translation.1, translation.2),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue