Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update texture_atlas example with different padding and sampling #10073

Merged
merged 6 commits into from
Dec 14, 2023
243 changes: 220 additions & 23 deletions examples/2d/texture_atlas.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
//! 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
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
//! 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.
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved

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)))
Expand Down Expand Up @@ -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);
Expand All @@ -49,10 +56,184 @@ 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()
});

// draw sprites from texture atlases

// 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();

// 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,
);
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved

// 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,
);
}
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved

/// 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!(
Expand All @@ -65,31 +246,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_descriptor = 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_alignment(TextAlignment::Center),
transform: Transform {
translation: Vec3::new(translation.0, translation.1, translation.2),
..default()
},
..default()
});
}