diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 9dba6deabd9f7..45bab606ceb99 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -74,17 +74,18 @@ shader_format_glsl = [ shader_format_spirv = ["bevy_render/shader_format_spirv"] serialize = [ + "bevy_color?/serialize", "bevy_core/serialize", - "bevy_input/serialize", "bevy_ecs/serialize", - "bevy_time/serialize", - "bevy_window/serialize", - "bevy_winit?/serialize", - "bevy_transform/serialize", + "bevy_input/serialize", "bevy_math/serialize", "bevy_scene?/serialize", + "bevy_sprite?/serialize", + "bevy_time/serialize", + "bevy_transform/serialize", "bevy_ui?/serialize", - "bevy_color?/serialize", + "bevy_window/serialize", + "bevy_winit?/serialize", ] multi_threaded = [ "bevy_asset?/multi_threaded", diff --git a/crates/bevy_sprite/Cargo.toml b/crates/bevy_sprite/Cargo.toml index 7e0a8345141b5..baf62b882da43 100644 --- a/crates/bevy_sprite/Cargo.toml +++ b/crates/bevy_sprite/Cargo.toml @@ -11,6 +11,7 @@ keywords = ["bevy"] [features] default = ["bevy_sprite_picking_backend"] bevy_sprite_picking_backend = ["bevy_picking", "bevy_window"] +serialize = ["dep:serde"] webgl = [] webgpu = [] @@ -41,6 +42,7 @@ rectangle-pack = "0.4" bitflags = "2.3" radsort = "0.1" nonmax = "0.5" +serde = { version = "1", features = ["derive"], optional = true } [lints] workspace = true diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index a411cef4afc1f..a5d645e001873 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -30,7 +30,7 @@ pub mod prelude { pub use crate::{ bundle::SpriteBundle, sprite::{ImageScaleMode, Sprite}, - texture_atlas::{TextureAtlas, TextureAtlasLayout}, + texture_atlas::{TextureAtlas, TextureAtlasLayout, TextureAtlasSources}, texture_slice::{BorderRect, SliceScaleMode, TextureSlice, TextureSlicer}, ColorMaterial, ColorMesh2dBundle, TextureAtlasBuilder, }; diff --git a/crates/bevy_sprite/src/texture_atlas.rs b/crates/bevy_sprite/src/texture_atlas.rs index a22cbe8e913e0..9fe109bd64a20 100644 --- a/crates/bevy_sprite/src/texture_atlas.rs +++ b/crates/bevy_sprite/src/texture_atlas.rs @@ -2,9 +2,50 @@ use bevy_asset::{Asset, AssetId, Assets, Handle}; use bevy_ecs::{component::Component, reflect::ReflectComponent}; use bevy_math::{URect, UVec2}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +#[cfg(feature = "serialize")] +use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; use bevy_render::texture::Image; use bevy_utils::HashMap; +/// Stores a mapping from sub texture handles to the related area index. +/// +/// Generated by [`TextureAtlasBuilder`]. +/// +/// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder +#[derive(Debug)] +pub struct TextureAtlasSources { + /// Maps from a specific image handle to the index in `textures` where they can be found. + pub(crate) texture_ids: HashMap, usize>, +} +impl TextureAtlasSources { + /// Retrieves the texture *section* index of the given `texture` handle. + pub fn texture_index(&self, texture: impl Into>) -> Option { + let id = texture.into(); + self.texture_ids.get(&id).cloned() + } + + /// Creates a [`TextureAtlas`] handle for the given `texture` handle. + pub fn handle( + &self, + layout: Handle, + texture: impl Into>, + ) -> Option { + Some(TextureAtlas { + layout, + index: self.texture_index(texture)?, + }) + } + + /// Retrieves the texture *section* rectangle of the given `texture` handle. + pub fn texture_rect( + &self, + layout: &TextureAtlasLayout, + texture: impl Into>, + ) -> Option { + layout.textures.get(self.texture_index(texture)?).cloned() + } +} + /// Stores a map used to lookup the position of a texture in a [`TextureAtlas`]. /// This can be used to either use and look up a specific section of a texture, or animate frame-by-frame as a sprite sheet. /// @@ -16,18 +57,15 @@ use bevy_utils::HashMap; /// [Example usage loading sprite sheet.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs) /// /// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder -#[derive(Asset, Reflect, Debug, Clone)] -#[reflect(Debug)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Asset, Reflect, PartialEq, Eq, Debug, Clone)] +#[reflect(Debug, PartialEq)] +#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))] pub struct TextureAtlasLayout { + /// Total size of texture atlas. pub size: UVec2, /// The specific areas of the atlas where each texture can be found pub textures: Vec, - /// Maps from a specific image handle to the index in `textures` where they can be found. - /// - /// This field is set by [`TextureAtlasBuilder`]. - /// - /// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder - pub(crate) texture_handles: Option, usize>>, } impl TextureAtlasLayout { @@ -35,7 +73,6 @@ impl TextureAtlasLayout { pub fn new_empty(dimensions: UVec2) -> Self { Self { size: dimensions, - texture_handles: None, textures: Vec::new(), } } @@ -89,7 +126,6 @@ impl TextureAtlasLayout { Self { size: ((tile_size + current_padding) * grid_size) - current_padding, textures: sprites, - texture_handles: None, } } @@ -114,18 +150,6 @@ impl TextureAtlasLayout { pub fn is_empty(&self) -> bool { self.textures.is_empty() } - - /// Retrieves the texture *section* index of the given `texture` handle. - /// - /// This requires the layout to have been built using a [`TextureAtlasBuilder`] - /// - /// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder - pub fn get_texture_index(&self, texture: impl Into>) -> Option { - let id = texture.into(); - self.texture_handles - .as_ref() - .and_then(|texture_handles| texture_handles.get(&id).cloned()) - } } /// Component used to draw a specific section of a texture. diff --git a/crates/bevy_sprite/src/texture_atlas_builder.rs b/crates/bevy_sprite/src/texture_atlas_builder.rs index 8aad5216c02aa..ddeea0723804f 100644 --- a/crates/bevy_sprite/src/texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/texture_atlas_builder.rs @@ -15,7 +15,7 @@ use rectangle_pack::{ }; use thiserror::Error; -use crate::TextureAtlasLayout; +use crate::{TextureAtlasLayout, TextureAtlasSources}; #[derive(Debug, Error)] pub enum TextureAtlasBuilderError { @@ -159,7 +159,9 @@ impl<'a> TextureAtlasBuilder<'a> { since = "0.14.0", note = "TextureAtlasBuilder::finish() was not idiomatic. Use TextureAtlasBuilder::build() instead." )] - pub fn finish(&mut self) -> Result<(TextureAtlasLayout, Image), TextureAtlasBuilderError> { + pub fn finish( + &mut self, + ) -> Result<(TextureAtlasLayout, TextureAtlasSources, Image), TextureAtlasBuilderError> { self.build() } @@ -184,7 +186,7 @@ impl<'a> TextureAtlasBuilder<'a> { /// // Customize it /// // ... /// // Build your texture and the atlas layout - /// let (atlas_layout, texture) = builder.build().unwrap(); + /// let (atlas_layout, atlas_sources, texture) = builder.build().unwrap(); /// let texture = textures.add(texture); /// let layout = layouts.add(atlas_layout); /// // Spawn your sprite @@ -199,7 +201,9 @@ impl<'a> TextureAtlasBuilder<'a> { /// /// If there is not enough space in the atlas texture, an error will /// be returned. It is then recommended to make a larger sprite sheet. - pub fn build(&mut self) -> Result<(TextureAtlasLayout, Image), TextureAtlasBuilderError> { + pub fn build( + &mut self, + ) -> Result<(TextureAtlasLayout, TextureAtlasSources, Image), TextureAtlasBuilderError> { let max_width = self.max_size.x; let max_height = self.max_size.y; @@ -295,8 +299,8 @@ impl<'a> TextureAtlasBuilder<'a> { TextureAtlasLayout { size: atlas_texture.size(), textures: texture_rects, - texture_handles: Some(texture_ids), }, + TextureAtlasSources { texture_ids }, atlas_texture, )) } diff --git a/examples/2d/texture_atlas.rs b/examples/2d/texture_atlas.rs index 0bb8615f4aa42..d910f0ae9d230 100644 --- a/examples/2d/texture_atlas.rs +++ b/examples/2d/texture_atlas.rs @@ -59,15 +59,15 @@ fn setup( // create texture atlases with different padding and sampling - let (texture_atlas_linear, linear_texture) = create_texture_atlas( + let (texture_atlas_linear, linear_sources, linear_texture) = create_texture_atlas( loaded_folder, None, Some(ImageSampler::linear()), &mut textures, ); - let atlas_linear_handle = texture_atlases.add(texture_atlas_linear.clone()); + let atlas_linear_handle = texture_atlases.add(texture_atlas_linear); - let (texture_atlas_nearest, nearest_texture) = create_texture_atlas( + let (texture_atlas_nearest, nearest_sources, nearest_texture) = create_texture_atlas( loaded_folder, None, Some(ImageSampler::nearest()), @@ -75,20 +75,22 @@ fn setup( ); let atlas_nearest_handle = texture_atlases.add(texture_atlas_nearest); - let (texture_atlas_linear_padded, linear_padded_texture) = create_texture_atlas( - loaded_folder, - Some(UVec2::new(6, 6)), - Some(ImageSampler::linear()), - &mut textures, - ); + let (texture_atlas_linear_padded, linear_padded_sources, linear_padded_texture) = + 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, nearest_padded_texture) = create_texture_atlas( - loaded_folder, - Some(UVec2::new(6, 6)), - Some(ImageSampler::nearest()), - &mut textures, - ); + let (texture_atlas_nearest_padded, nearest_padded_sources, nearest_padded_texture) = + 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 @@ -145,25 +147,39 @@ 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(); - // configuration array to render sprites through iteration - let configurations: [(&str, Handle, Handle, f32); 4] = [ - ("Linear", atlas_linear_handle, linear_texture, -350.0), - ("Nearest", atlas_nearest_handle, nearest_texture, -150.0), + let configurations: [( + &str, + Handle, + TextureAtlasSources, + Handle, + f32, + ); 4] = [ + ( + "Linear", + atlas_linear_handle, + linear_sources, + linear_texture, + -350.0, + ), + ( + "Nearest", + atlas_nearest_handle, + nearest_sources, + nearest_texture, + -150.0, + ), ( "Linear", atlas_linear_padded_handle, + linear_padded_sources, linear_padded_texture, 150.0, ), ( "Nearest", atlas_nearest_padded_handle, + nearest_padded_sources, nearest_padded_texture, 350.0, ), @@ -178,14 +194,15 @@ fn setup( let base_y = 170.0; // y position of the sprites - for (sampling, atlas_handle, image_handle, x) in configurations { + for (sampling, atlas_handle, atlas_sources, atlas_texture, x) in configurations { // render a sprite from the texture_atlas create_sprite_from_atlas( &mut commands, (x, base_y, 0.0), - vendor_index, + atlas_texture, + atlas_sources, atlas_handle, - image_handle, + &vendor_handle, ); // render a label to indicate the sampling setting @@ -205,7 +222,7 @@ fn create_texture_atlas( padding: Option, sampling: Option, textures: &mut ResMut>, -) -> (TextureAtlasLayout, Handle) { +) -> (TextureAtlasLayout, TextureAtlasSources, Handle) { // Build a texture atlas using the individual sprites let mut texture_atlas_builder = TextureAtlasBuilder::default(); texture_atlas_builder.padding(padding.unwrap_or_default()); @@ -222,23 +239,25 @@ fn create_texture_atlas( texture_atlas_builder.add_texture(Some(id), texture); } - let (texture_atlas_layout, texture) = texture_atlas_builder.build().unwrap(); + let (texture_atlas_layout, texture_atlas_sources, texture) = + texture_atlas_builder.build().unwrap(); let texture = textures.add(texture); // Update the sampling settings of the texture atlas let image = textures.get_mut(&texture).unwrap(); image.sampler = sampling.unwrap_or_default(); - (texture_atlas_layout, texture) + (texture_atlas_layout, texture_atlas_sources, texture) } /// 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_texture: Handle, + atlas_sources: TextureAtlasSources, atlas_handle: Handle, - texture: Handle, + vendor_handle: &Handle, ) { commands.spawn(( SpriteBundle { @@ -247,13 +266,10 @@ fn create_sprite_from_atlas( scale: Vec3::splat(3.0), ..default() }, - texture, + texture: atlas_texture, ..default() }, - TextureAtlas { - layout: atlas_handle, - index: sprite_index, - }, + atlas_sources.handle(atlas_handle, vendor_handle).unwrap(), )); }