# Objective Now that #13432 has been merged, it's important we update our reflected types to properly opt into this feature. If we do not, then this could cause issues for users downstream who want to make use of reflection-based cloning. ## Solution This PR is broken into 4 commits: 1. Add `#[reflect(Clone)]` on all types marked `#[reflect(opaque)]` that are also `Clone`. This is mandatory as these types would otherwise cause the cloning operation to fail for any type that contains it at any depth. 2. Update the reflection example to suggest adding `#[reflect(Clone)]` on opaque types. 3. Add `#[reflect(clone)]` attributes on all fields marked `#[reflect(ignore)]` that are also `Clone`. This prevents the ignored field from causing the cloning operation to fail. Note that some of the types that contain these fields are also `Clone`, and thus can be marked `#[reflect(Clone)]`. This makes the `#[reflect(clone)]` attribute redundant. However, I think it's safer to keep it marked in the case that the `Clone` impl/derive is ever removed. I'm open to removing them, though, if people disagree. 4. Finally, I added `#[reflect(Clone)]` on all types that are also `Clone`. While not strictly necessary, it enables us to reduce the generated output since we can just call `Clone::clone` directly instead of calling `PartialReflect::reflect_clone` on each variant/field. It also means we benefit from any optimizations or customizations made in the `Clone` impl, including directly dereferencing `Copy` values and increasing reference counters. Along with that change I also took the liberty of adding any missing registrations that I saw could be applied to the type as well, such as `Default`, `PartialEq`, and `Hash`. There were hundreds of these to edit, though, so it's possible I missed quite a few. That last commit is **_massive_**. There were nearly 700 types to update. So it's recommended to review the first three before moving onto that last one. Additionally, I can break the last commit off into its own PR or into smaller PRs, but I figured this would be the easiest way of doing it (and in a timely manner since I unfortunately don't have as much time as I used to for code contributions). ## Testing You can test locally with a `cargo check`: ``` cargo check --workspace --all-features ```
232 lines
8.0 KiB
Rust
232 lines
8.0 KiB
Rust
use bevy_app::prelude::*;
|
|
use bevy_asset::{Asset, AssetApp as _, AssetId, Assets, Handle};
|
|
use bevy_math::{Rect, URect, UVec2};
|
|
use bevy_platform_support::collections::HashMap;
|
|
#[cfg(feature = "bevy_reflect")]
|
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
|
#[cfg(feature = "serialize")]
|
|
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
|
|
|
use crate::Image;
|
|
|
|
/// Adds support for texture atlases.
|
|
pub struct TextureAtlasPlugin;
|
|
|
|
impl Plugin for TextureAtlasPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.init_asset::<TextureAtlasLayout>();
|
|
|
|
#[cfg(feature = "bevy_reflect")]
|
|
app.register_asset_reflect::<TextureAtlasLayout>()
|
|
.register_type::<TextureAtlas>();
|
|
}
|
|
}
|
|
|
|
/// 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 texture_ids: HashMap<AssetId<Image>, usize>,
|
|
}
|
|
impl TextureAtlasSources {
|
|
/// Retrieves the texture *section* index of the given `texture` handle.
|
|
pub fn texture_index(&self, texture: impl Into<AssetId<Image>>) -> Option<usize> {
|
|
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<TextureAtlasLayout>,
|
|
texture: impl Into<AssetId<Image>>,
|
|
) -> Option<TextureAtlas> {
|
|
Some(TextureAtlas {
|
|
layout,
|
|
index: self.texture_index(texture)?,
|
|
})
|
|
}
|
|
|
|
/// Retrieves the texture *section* rectangle of the given `texture` handle in pixels.
|
|
pub fn texture_rect(
|
|
&self,
|
|
layout: &TextureAtlasLayout,
|
|
texture: impl Into<AssetId<Image>>,
|
|
) -> Option<URect> {
|
|
layout.textures.get(self.texture_index(texture)?).cloned()
|
|
}
|
|
|
|
/// Retrieves the texture *section* rectangle of the given `texture` handle in UV coordinates.
|
|
/// These are within the range [0..1], as a fraction of the entire texture atlas' size.
|
|
pub fn uv_rect(
|
|
&self,
|
|
layout: &TextureAtlasLayout,
|
|
texture: impl Into<AssetId<Image>>,
|
|
) -> Option<Rect> {
|
|
self.texture_rect(layout, texture).map(|rect| {
|
|
let rect = rect.as_rect();
|
|
let size = layout.size.as_vec2();
|
|
Rect::from_corners(rect.min / size, rect.max / size)
|
|
})
|
|
}
|
|
}
|
|
|
|
/// 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.
|
|
///
|
|
/// Optionally it can store a mapping from sub texture handles to the related area index (see
|
|
/// [`TextureAtlasBuilder`]).
|
|
///
|
|
/// [Example usage animating sprite.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)
|
|
/// [Example usage animating sprite in response to an event.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs)
|
|
/// [Example usage loading sprite sheet.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
|
|
///
|
|
/// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder
|
|
#[derive(Asset, PartialEq, Eq, Debug, Clone)]
|
|
#[cfg_attr(
|
|
feature = "bevy_reflect",
|
|
derive(Reflect),
|
|
reflect(Debug, PartialEq, Clone)
|
|
)]
|
|
#[cfg_attr(
|
|
feature = "serialize",
|
|
derive(serde::Serialize, serde::Deserialize),
|
|
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<URect>,
|
|
}
|
|
|
|
impl TextureAtlasLayout {
|
|
/// Create a new empty layout with custom `dimensions`
|
|
pub fn new_empty(dimensions: UVec2) -> Self {
|
|
Self {
|
|
size: dimensions,
|
|
textures: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// Generate a [`TextureAtlasLayout`] as a grid where each
|
|
/// `tile_size` by `tile_size` grid-cell is one of the *section* in the
|
|
/// atlas. Grid cells are separated by some `padding`, and the grid starts
|
|
/// at `offset` pixels from the top left corner. Resulting layout is
|
|
/// indexed left to right, top to bottom.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `tile_size` - Each layout grid cell size
|
|
/// * `columns` - Grid column count
|
|
/// * `rows` - Grid row count
|
|
/// * `padding` - Optional padding between cells
|
|
/// * `offset` - Optional global grid offset
|
|
pub fn from_grid(
|
|
tile_size: UVec2,
|
|
columns: u32,
|
|
rows: u32,
|
|
padding: Option<UVec2>,
|
|
offset: Option<UVec2>,
|
|
) -> Self {
|
|
let padding = padding.unwrap_or_default();
|
|
let offset = offset.unwrap_or_default();
|
|
let mut sprites = Vec::new();
|
|
let mut current_padding = UVec2::ZERO;
|
|
|
|
for y in 0..rows {
|
|
if y > 0 {
|
|
current_padding.y = padding.y;
|
|
}
|
|
for x in 0..columns {
|
|
if x > 0 {
|
|
current_padding.x = padding.x;
|
|
}
|
|
|
|
let cell = UVec2::new(x, y);
|
|
let rect_min = (tile_size + current_padding) * cell + offset;
|
|
|
|
sprites.push(URect {
|
|
min: rect_min,
|
|
max: rect_min + tile_size,
|
|
});
|
|
}
|
|
}
|
|
|
|
let grid_size = UVec2::new(columns, rows);
|
|
|
|
Self {
|
|
size: ((tile_size + current_padding) * grid_size) - current_padding,
|
|
textures: sprites,
|
|
}
|
|
}
|
|
|
|
/// Add a *section* to the list in the layout and returns its index
|
|
/// which can be used with [`TextureAtlas`]
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `rect` - The section of the texture to be added
|
|
///
|
|
/// [`TextureAtlas`]: crate::TextureAtlas
|
|
pub fn add_texture(&mut self, rect: URect) -> usize {
|
|
self.textures.push(rect);
|
|
self.textures.len() - 1
|
|
}
|
|
|
|
/// The number of textures in the [`TextureAtlasLayout`]
|
|
pub fn len(&self) -> usize {
|
|
self.textures.len()
|
|
}
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
self.textures.is_empty()
|
|
}
|
|
}
|
|
|
|
/// An index into a [`TextureAtlasLayout`], which corresponds to a specific section of a texture.
|
|
///
|
|
/// It stores a handle to [`TextureAtlasLayout`] and the index of the current section of the atlas.
|
|
/// The texture atlas contains various *sections* of a given texture, allowing users to have a single
|
|
/// image file for either sprite animation or global mapping.
|
|
/// You can change the texture [`index`](Self::index) of the atlas to animate the sprite or display only a *section* of the texture
|
|
/// for efficient rendering of related game objects.
|
|
///
|
|
/// Check the following examples for usage:
|
|
/// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)
|
|
/// - [`sprite animation event example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs)
|
|
/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
|
|
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
|
|
#[cfg_attr(
|
|
feature = "bevy_reflect",
|
|
derive(Reflect),
|
|
reflect(Default, Debug, PartialEq, Hash, Clone)
|
|
)]
|
|
pub struct TextureAtlas {
|
|
/// Texture atlas layout handle
|
|
pub layout: Handle<TextureAtlasLayout>,
|
|
/// Texture atlas section index
|
|
pub index: usize,
|
|
}
|
|
|
|
impl TextureAtlas {
|
|
/// Retrieves the current texture [`URect`] of the sprite sheet according to the section `index`
|
|
pub fn texture_rect(&self, texture_atlases: &Assets<TextureAtlasLayout>) -> Option<URect> {
|
|
let atlas = texture_atlases.get(&self.layout)?;
|
|
atlas.textures.get(self.index).copied()
|
|
}
|
|
}
|
|
|
|
impl From<Handle<TextureAtlasLayout>> for TextureAtlas {
|
|
fn from(texture_atlas: Handle<TextureAtlasLayout>) -> Self {
|
|
Self {
|
|
layout: texture_atlas,
|
|
index: 0,
|
|
}
|
|
}
|
|
}
|