use bevy_app::prelude::*;1use bevy_asset::{Asset, AssetApp as _, AssetId, Assets, Handle};2use bevy_math::{Rect, URect, UVec2};3use bevy_platform::collections::HashMap;4#[cfg(not(feature = "bevy_reflect"))]5use bevy_reflect::TypePath;6#[cfg(feature = "bevy_reflect")]7use bevy_reflect::{std_traits::ReflectDefault, Reflect};8#[cfg(feature = "serialize")]9use bevy_reflect::{ReflectDeserialize, ReflectSerialize};1011use crate::Image;1213/// Adds support for texture atlases.14pub struct TextureAtlasPlugin;1516impl Plugin for TextureAtlasPlugin {17fn build(&self, app: &mut App) {18app.init_asset::<TextureAtlasLayout>();1920#[cfg(feature = "bevy_reflect")]21app.register_asset_reflect::<TextureAtlasLayout>();22}23}2425/// Stores a mapping from sub texture handles to the related area index.26///27/// Generated by [`TextureAtlasBuilder`].28///29/// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder30#[derive(Debug)]31pub struct TextureAtlasSources {32/// Maps from a specific image handle to the index in `textures` where they can be found.33pub texture_ids: HashMap<AssetId<Image>, usize>,34}3536impl TextureAtlasSources {37/// Retrieves the texture *section* index of the given `texture` handle.38pub fn texture_index(&self, texture: impl Into<AssetId<Image>>) -> Option<usize> {39let id = texture.into();40self.texture_ids.get(&id).cloned()41}4243/// Creates a [`TextureAtlas`] handle for the given `texture` handle.44pub fn handle(45&self,46layout: Handle<TextureAtlasLayout>,47texture: impl Into<AssetId<Image>>,48) -> Option<TextureAtlas> {49Some(TextureAtlas {50layout,51index: self.texture_index(texture)?,52})53}5455/// Retrieves the texture *section* rectangle of the given `texture` handle in pixels.56pub fn texture_rect(57&self,58layout: &TextureAtlasLayout,59texture: impl Into<AssetId<Image>>,60) -> Option<URect> {61layout.textures.get(self.texture_index(texture)?).cloned()62}6364/// Retrieves the texture *section* rectangle of the given `texture` handle in UV coordinates.65/// These are within the range [0..1], as a fraction of the entire texture atlas' size.66pub fn uv_rect(67&self,68layout: &TextureAtlasLayout,69texture: impl Into<AssetId<Image>>,70) -> Option<Rect> {71self.texture_rect(layout, texture).map(|rect| {72let rect = rect.as_rect();73let size = layout.size.as_vec2();74Rect::from_corners(rect.min / size, rect.max / size)75})76}77}7879/// Stores a map used to lookup the position of a texture in a [`TextureAtlas`].80/// 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.81///82/// Optionally it can store a mapping from sub texture handles to the related area index (see83/// [`TextureAtlasBuilder`]).84///85/// [Example usage animating sprite.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)86/// [Example usage animating sprite in response to an event.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs)87/// [Example usage loading sprite sheet.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)88///89/// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder90#[derive(Asset, PartialEq, Eq, Debug, Clone)]91#[cfg_attr(92feature = "bevy_reflect",93derive(Reflect),94reflect(Debug, PartialEq, Clone)95)]96#[cfg_attr(97feature = "serialize",98derive(serde::Serialize, serde::Deserialize),99reflect(Serialize, Deserialize)100)]101#[cfg_attr(not(feature = "bevy_reflect"), derive(TypePath))]102pub struct TextureAtlasLayout {103/// Total size of texture atlas.104pub size: UVec2,105/// The specific areas of the atlas where each texture can be found106pub textures: Vec<URect>,107}108109impl TextureAtlasLayout {110/// Create a new empty layout with custom `dimensions`111pub fn new_empty(dimensions: UVec2) -> Self {112Self {113size: dimensions,114textures: Vec::new(),115}116}117118/// Generate a [`TextureAtlasLayout`] as a grid where each119/// `tile_size` by `tile_size` grid-cell is one of the *section* in the120/// atlas. Grid cells are separated by some `padding`, and the grid starts121/// at `offset` pixels from the top left corner. Resulting layout is122/// indexed left to right, top to bottom.123///124/// # Arguments125///126/// * `tile_size` - Each layout grid cell size127/// * `columns` - Grid column count128/// * `rows` - Grid row count129/// * `padding` - Optional padding between cells130/// * `offset` - Optional global grid offset131pub fn from_grid(132tile_size: UVec2,133columns: u32,134rows: u32,135padding: Option<UVec2>,136offset: Option<UVec2>,137) -> Self {138let padding = padding.unwrap_or_default();139let offset = offset.unwrap_or_default();140let mut sprites = Vec::new();141let mut current_padding = UVec2::ZERO;142143for y in 0..rows {144if y > 0 {145current_padding.y = padding.y;146}147for x in 0..columns {148if x > 0 {149current_padding.x = padding.x;150}151152let cell = UVec2::new(x, y);153let rect_min = (tile_size + current_padding) * cell + offset;154155sprites.push(URect {156min: rect_min,157max: rect_min + tile_size,158});159}160}161162let grid_size = UVec2::new(columns, rows);163164Self {165size: ((tile_size + current_padding) * grid_size) - current_padding,166textures: sprites,167}168}169170/// Add a *section* to the list in the layout and returns its index171/// which can be used with [`TextureAtlas`]172///173/// # Arguments174///175/// * `rect` - The section of the texture to be added176///177/// [`TextureAtlas`]: crate::TextureAtlas178pub fn add_texture(&mut self, rect: URect) -> usize {179self.textures.push(rect);180self.textures.len() - 1181}182183/// The number of textures in the [`TextureAtlasLayout`]184pub fn len(&self) -> usize {185self.textures.len()186}187188pub fn is_empty(&self) -> bool {189self.textures.is_empty()190}191}192193/// An index into a [`TextureAtlasLayout`], which corresponds to a specific section of a texture.194///195/// It stores a handle to [`TextureAtlasLayout`] and the index of the current section of the atlas.196/// The texture atlas contains various *sections* of a given texture, allowing users to have a single197/// image file for either sprite animation or global mapping.198/// You can change the texture [`index`](Self::index) of the atlas to animate the sprite or display only a *section* of the texture199/// for efficient rendering of related game objects.200///201/// Check the following examples for usage:202/// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)203/// - [`sprite animation event example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs)204/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)205#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]206#[cfg_attr(207feature = "bevy_reflect",208derive(Reflect),209reflect(Default, Debug, PartialEq, Hash, Clone)210)]211pub struct TextureAtlas {212/// Texture atlas layout handle213pub layout: Handle<TextureAtlasLayout>,214/// Texture atlas section index215pub index: usize,216}217218impl TextureAtlas {219/// Retrieves the current texture [`URect`] of the sprite sheet according to the section `index`220pub fn texture_rect(&self, texture_atlases: &Assets<TextureAtlasLayout>) -> Option<URect> {221let atlas = texture_atlases.get(&self.layout)?;222atlas.textures.get(self.index).copied()223}224225/// Returns this [`TextureAtlas`] with the specified index.226pub fn with_index(mut self, index: usize) -> Self {227self.index = index;228self229}230231/// Returns this [`TextureAtlas`] with the specified [`TextureAtlasLayout`] handle.232pub fn with_layout(mut self, layout: Handle<TextureAtlasLayout>) -> Self {233self.layout = layout;234self235}236}237238impl From<Handle<TextureAtlasLayout>> for TextureAtlas {239fn from(texture_atlas: Handle<TextureAtlasLayout>) -> Self {240Self {241layout: texture_atlas,242index: 0,243}244}245}246247248