Texture Atlas rework (#5103)

# Objective

> Old MR: #5072 
> ~~Associated UI MR: #5070~~
> Adresses #1618

Unify sprite management

## Solution

- Remove the `Handle<Image>` field in `TextureAtlas` which is the main
cause for all the boilerplate
- Remove the redundant `TextureAtlasSprite` component
- Renamed `TextureAtlas` asset to `TextureAtlasLayout`
([suggestion](https://github.com/bevyengine/bevy/pull/5103#discussion_r917281844))
- Add a `TextureAtlas` component, containing the atlas layout handle and
the section index

The difference between this solution and #5072 is that instead of the
`enum` approach is that we can more easily manipulate texture sheets
without any breaking changes for classic `SpriteBundle`s (@mockersf
[comment](https://github.com/bevyengine/bevy/pull/5072#issuecomment-1165836139))

Also, this approach is more *data oriented* extracting the
`Handle<Image>` and avoiding complex texture atlas manipulations to
retrieve the texture in both applicative and engine code.
With this method, the only difference between a `SpriteBundle` and a
`SpriteSheetBundle` is an **additional** component storing the atlas
handle and the index.

~~This solution can be applied to `bevy_ui` as well (see #5070).~~

EDIT: I also applied this solution to Bevy UI

## Changelog

- (**BREAKING**) Removed `TextureAtlasSprite`
- (**BREAKING**) Renamed `TextureAtlas` to `TextureAtlasLayout`
- (**BREAKING**) `SpriteSheetBundle`:
  - Uses a  `Sprite` instead of a `TextureAtlasSprite` component
- Has a `texture` field containing a `Handle<Image>` like the
`SpriteBundle`
- Has a new `TextureAtlas` component instead of a
`Handle<TextureAtlasLayout>`
- (**BREAKING**) `DynamicTextureAtlasBuilder::add_texture` takes an
additional `&Handle<Image>` parameter
- (**BREAKING**) `TextureAtlasLayout::from_grid` no longer takes a
`Handle<Image>` parameter
- (**BREAKING**) `TextureAtlasBuilder::finish` now returns a
`Result<(TextureAtlasLayout, Handle<Image>), _>`
- `bevy_text`:
  - `GlyphAtlasInfo` stores the texture `Handle<Image>`
  - `FontAtlas` stores the texture `Handle<Image>`
- `bevy_ui`:
- (**BREAKING**) Removed `UiAtlasImage` , the atlas bundle is now
identical to the `ImageBundle` with an additional `TextureAtlas`

## Migration Guide

* Sprites

```diff
fn my_system(
  mut images: ResMut<Assets<Image>>, 
-  mut atlases: ResMut<Assets<TextureAtlas>>, 
+  mut atlases: ResMut<Assets<TextureAtlasLayout>>, 
  asset_server: Res<AssetServer>
) {
    let texture_handle: asset_server.load("my_texture.png");
-   let layout = TextureAtlas::from_grid(texture_handle, Vec2::new(25.0, 25.0), 5, 5, None, None);
+   let layout = TextureAtlasLayout::from_grid(Vec2::new(25.0, 25.0), 5, 5, None, None);
    let layout_handle = atlases.add(layout);
    commands.spawn(SpriteSheetBundle {
-      sprite: TextureAtlasSprite::new(0),
-      texture_atlas: atlas_handle,
+      atlas: TextureAtlas {
+         layout: layout_handle,
+         index: 0
+      },
+      texture: texture_handle,
       ..Default::default()
     });
}
```
* UI


```diff
fn my_system(
  mut images: ResMut<Assets<Image>>, 
-  mut atlases: ResMut<Assets<TextureAtlas>>, 
+  mut atlases: ResMut<Assets<TextureAtlasLayout>>, 
  asset_server: Res<AssetServer>
) {
    let texture_handle: asset_server.load("my_texture.png");
-   let layout = TextureAtlas::from_grid(texture_handle, Vec2::new(25.0, 25.0), 5, 5, None, None);
+   let layout = TextureAtlasLayout::from_grid(Vec2::new(25.0, 25.0), 5, 5, None, None);
    let layout_handle = atlases.add(layout);
    commands.spawn(AtlasImageBundle {
-      texture_atlas_image: UiTextureAtlasImage {
-           index: 0,
-           flip_x: false,
-           flip_y: false,
-       },
-      texture_atlas: atlas_handle,
+      atlas: TextureAtlas {
+         layout: layout_handle,
+         index: 0
+      },
+      image: UiImage {
+           texture: texture_handle,
+           flip_x: false,
+           flip_y: false,
+       },
       ..Default::default()
     });
}
```

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: François <mockersf@gmail.com>
Co-authored-by: IceSentry <IceSentry@users.noreply.github.com>
This commit is contained in:
Félix Lescaudey de Maneville 2024-01-16 14:59:08 +01:00 committed by GitHub
parent 9f8db0de0d
commit 135c7240f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 354 additions and 470 deletions

View File

@ -1,7 +1,4 @@
use crate::{ use crate::{texture_atlas::TextureAtlas, ImageScaleMode, Sprite};
texture_atlas::{TextureAtlas, TextureAtlasSprite},
ImageScaleMode, Sprite,
};
use bevy_asset::Handle; use bevy_asset::Handle;
use bevy_ecs::bundle::Bundle; use bevy_ecs::bundle::Bundle;
use bevy_render::{ use bevy_render::{
@ -32,18 +29,25 @@ pub struct SpriteBundle {
} }
/// A [`Bundle`] of components for drawing a single sprite from a sprite sheet (also referred /// A [`Bundle`] of components for drawing a single sprite from a sprite sheet (also referred
/// to as a `TextureAtlas`). /// to as a `TextureAtlas`) or for animated sprites.
///
/// Note:
/// This bundle is identical to [`SpriteBundle`] with an additional [`TextureAtlas`] component.
///
/// Check the following examples for usage:
/// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)
/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
#[derive(Bundle, Clone, Default)] #[derive(Bundle, Clone, Default)]
pub struct SpriteSheetBundle { pub struct SpriteSheetBundle {
/// The specific sprite from the texture atlas to be drawn, defaulting to the sprite at index 0. pub sprite: Sprite,
pub sprite: TextureAtlasSprite,
/// Controls how the image is altered when scaled. /// Controls how the image is altered when scaled.
pub scale_mode: ImageScaleMode, pub scale_mode: ImageScaleMode,
/// A handle to the texture atlas that holds the sprite images
pub texture_atlas: Handle<TextureAtlas>,
/// Data pertaining to how the sprite is drawn on the screen
pub transform: Transform, pub transform: Transform,
pub global_transform: GlobalTransform, pub global_transform: GlobalTransform,
/// The sprite sheet base texture
pub texture: Handle<Image>,
/// The sprite sheet texture atlas, allowing to draw a custom section of `texture`.
pub atlas: TextureAtlas,
/// User indication of whether an entity is visible /// User indication of whether an entity is visible
pub visibility: Visibility, pub visibility: Visibility,
pub inherited_visibility: InheritedVisibility, pub inherited_visibility: InheritedVisibility,

View File

@ -1,5 +1,5 @@
use crate::TextureAtlas; use crate::TextureAtlasLayout;
use bevy_asset::Assets; use bevy_asset::{Assets, Handle};
use bevy_math::{IVec2, Rect, Vec2}; use bevy_math::{IVec2, Rect, Vec2};
use bevy_render::{ use bevy_render::{
render_asset::RenderAssetPersistencePolicy, render_asset::RenderAssetPersistencePolicy,
@ -7,10 +7,10 @@ use bevy_render::{
}; };
use guillotiere::{size2, Allocation, AtlasAllocator}; use guillotiere::{size2, Allocation, AtlasAllocator};
/// Helper utility to update [`TextureAtlas`] on the fly. /// Helper utility to update [`TextureAtlasLayout`] on the fly.
/// ///
/// Helpful in cases when texture is created procedurally, /// Helpful in cases when texture is created procedurally,
/// e.g: in a font glyph [`TextureAtlas`], only add the [`Image`] texture for letters to be rendered. /// e.g: in a font glyph [`TextureAtlasLayout`], only add the [`Image`] texture for letters to be rendered.
pub struct DynamicTextureAtlasBuilder { pub struct DynamicTextureAtlasBuilder {
atlas_allocator: AtlasAllocator, atlas_allocator: AtlasAllocator,
padding: i32, padding: i32,
@ -30,22 +30,30 @@ impl DynamicTextureAtlasBuilder {
} }
} }
/// Add a new texture to [`TextureAtlas`]. /// Add a new texture to `atlas_layout`
/// It is user's responsibility to pass in the correct [`TextureAtlas`], /// It is the user's responsibility to pass in the correct [`TextureAtlasLayout`]
/// and that [`TextureAtlas::texture`] has [`Image::cpu_persistent_access`] /// and that `atlas_texture_handle` has [`Image::cpu_persistent_access`]
/// set to [`RenderAssetPersistencePolicy::Keep`] /// set to [`RenderAssetPersistencePolicy::Keep`]
///
/// # Arguments
///
/// * `altas_layout` - The atlas to add the texture to
/// * `textures` - The texture assets container
/// * `texture` - The new texture to add to the atlas
/// * `atlas_texture_handle` - The atlas texture to edit
pub fn add_texture( pub fn add_texture(
&mut self, &mut self,
texture_atlas: &mut TextureAtlas, atlas_layout: &mut TextureAtlasLayout,
textures: &mut Assets<Image>, textures: &mut Assets<Image>,
texture: &Image, texture: &Image,
atlas_texture_handle: &Handle<Image>,
) -> Option<usize> { ) -> Option<usize> {
let allocation = self.atlas_allocator.allocate(size2( let allocation = self.atlas_allocator.allocate(size2(
texture.width() as i32 + self.padding, texture.width() as i32 + self.padding,
texture.height() as i32 + self.padding, texture.height() as i32 + self.padding,
)); ));
if let Some(allocation) = allocation { if let Some(allocation) = allocation {
let atlas_texture = textures.get_mut(&texture_atlas.texture).unwrap(); let atlas_texture = textures.get_mut(atlas_texture_handle).unwrap();
assert_eq!( assert_eq!(
atlas_texture.cpu_persistent_access, atlas_texture.cpu_persistent_access,
RenderAssetPersistencePolicy::Keep RenderAssetPersistencePolicy::Keep
@ -54,7 +62,7 @@ impl DynamicTextureAtlasBuilder {
self.place_texture(atlas_texture, allocation, texture); self.place_texture(atlas_texture, allocation, texture);
let mut rect: Rect = to_rect(allocation.rectangle); let mut rect: Rect = to_rect(allocation.rectangle);
rect.max -= self.padding as f32; rect.max -= self.padding as f32;
Some(texture_atlas.add_texture(rect)) Some(atlas_layout.add_texture(rect))
} else { } else {
None None
} }

View File

@ -15,7 +15,7 @@ pub mod prelude {
pub use crate::{ pub use crate::{
bundle::{SpriteBundle, SpriteSheetBundle}, bundle::{SpriteBundle, SpriteSheetBundle},
sprite::{ImageScaleMode, Sprite}, sprite::{ImageScaleMode, Sprite},
texture_atlas::{TextureAtlas, TextureAtlasSprite}, texture_atlas::{TextureAtlas, TextureAtlasLayout},
texture_slice::{BorderRect, SliceScaleMode, TextureSlicer}, texture_slice::{BorderRect, SliceScaleMode, TextureSlicer},
ColorMaterial, ColorMesh2dBundle, TextureAtlasBuilder, ColorMaterial, ColorMesh2dBundle, TextureAtlasBuilder,
}; };
@ -65,13 +65,13 @@ impl Plugin for SpritePlugin {
"render/sprite.wgsl", "render/sprite.wgsl",
Shader::from_wgsl Shader::from_wgsl
); );
app.init_asset::<TextureAtlas>() app.init_asset::<TextureAtlasLayout>()
.register_asset_reflect::<TextureAtlas>() .register_asset_reflect::<TextureAtlasLayout>()
.register_type::<Sprite>() .register_type::<Sprite>()
.register_type::<ImageScaleMode>() .register_type::<ImageScaleMode>()
.register_type::<TextureSlicer>() .register_type::<TextureSlicer>()
.register_type::<TextureAtlasSprite>()
.register_type::<Anchor>() .register_type::<Anchor>()
.register_type::<TextureAtlas>()
.register_type::<Mesh2dHandle>() .register_type::<Mesh2dHandle>()
.add_plugins((Mesh2dRenderPlugin, ColorMaterialPlugin)) .add_plugins((Mesh2dRenderPlugin, ColorMaterialPlugin))
.add_systems( .add_systems(
@ -131,19 +131,15 @@ pub fn calculate_bounds_2d(
mut commands: Commands, mut commands: Commands,
meshes: Res<Assets<Mesh>>, meshes: Res<Assets<Mesh>>,
images: Res<Assets<Image>>, images: Res<Assets<Image>>,
atlases: Res<Assets<TextureAtlas>>, atlases: Res<Assets<TextureAtlasLayout>>,
meshes_without_aabb: Query<(Entity, &Mesh2dHandle), (Without<Aabb>, Without<NoFrustumCulling>)>, meshes_without_aabb: Query<(Entity, &Mesh2dHandle), (Without<Aabb>, Without<NoFrustumCulling>)>,
sprites_to_recalculate_aabb: Query< sprites_to_recalculate_aabb: Query<
(Entity, &Sprite, &Handle<Image>), (Entity, &Sprite, &Handle<Image>, Option<&TextureAtlas>),
( (
Or<(Without<Aabb>, Changed<Sprite>)>, Or<(Without<Aabb>, Changed<Sprite>)>,
Without<NoFrustumCulling>, Without<NoFrustumCulling>,
), ),
>, >,
atlases_without_aabb: Query<
(Entity, &TextureAtlasSprite, &Handle<TextureAtlas>),
(Without<Aabb>, Without<NoFrustumCulling>),
>,
) { ) {
for (entity, mesh_handle) in &meshes_without_aabb { for (entity, mesh_handle) in &meshes_without_aabb {
if let Some(mesh) = meshes.get(&mesh_handle.0) { if let Some(mesh) = meshes.get(&mesh_handle.0) {
@ -152,27 +148,15 @@ pub fn calculate_bounds_2d(
} }
} }
} }
for (entity, sprite, texture_handle) in &sprites_to_recalculate_aabb { for (entity, sprite, texture_handle, atlas) in &sprites_to_recalculate_aabb {
if let Some(size) = sprite if let Some(size) = sprite.custom_size.or_else(|| match atlas {
.custom_size // We default to the texture size for regular sprites
.or_else(|| images.get(texture_handle).map(|image| image.size_f32())) None => images.get(texture_handle).map(|image| image.size_f32()),
{ // We default to the drawn rect for atlas sprites
let aabb = Aabb { Some(atlas) => atlas.texture_rect(&atlases).map(|rect| rect.size()),
center: (-sprite.anchor.as_vec() * size).extend(0.0).into(),
half_extents: (0.5 * size).extend(0.0).into(),
};
commands.entity(entity).try_insert(aabb);
}
}
for (entity, atlas_sprite, atlas_handle) in &atlases_without_aabb {
if let Some(size) = atlas_sprite.custom_size.or_else(|| {
atlases
.get(atlas_handle)
.and_then(|atlas| atlas.textures.get(atlas_sprite.index))
.map(|rect| (rect.min - rect.max).abs())
}) { }) {
let aabb = Aabb { let aabb = Aabb {
center: (-atlas_sprite.anchor.as_vec() * size).extend(0.0).into(), center: (-sprite.anchor.as_vec() * size).extend(0.0).into(),
half_extents: (0.5 * size).extend(0.0).into(), half_extents: (0.5 * size).extend(0.0).into(),
}; };
commands.entity(entity).try_insert(aabb); commands.entity(entity).try_insert(aabb);
@ -199,7 +183,7 @@ mod test {
app.insert_resource(image_assets); app.insert_resource(image_assets);
let mesh_assets = Assets::<Mesh>::default(); let mesh_assets = Assets::<Mesh>::default();
app.insert_resource(mesh_assets); app.insert_resource(mesh_assets);
let texture_atlas_assets = Assets::<TextureAtlas>::default(); let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
app.insert_resource(texture_atlas_assets); app.insert_resource(texture_atlas_assets);
// Add system // Add system
@ -237,7 +221,7 @@ mod test {
app.insert_resource(image_assets); app.insert_resource(image_assets);
let mesh_assets = Assets::<Mesh>::default(); let mesh_assets = Assets::<Mesh>::default();
app.insert_resource(mesh_assets); app.insert_resource(mesh_assets);
let texture_atlas_assets = Assets::<TextureAtlas>::default(); let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
app.insert_resource(texture_atlas_assets); app.insert_resource(texture_atlas_assets);
// Add system // Add system

View File

@ -1,7 +1,7 @@
use std::ops::Range; use std::ops::Range;
use crate::{ use crate::{
texture_atlas::{TextureAtlas, TextureAtlasSprite}, texture_atlas::{TextureAtlas, TextureAtlasLayout},
ComputedTextureSlices, Sprite, SPRITE_SHADER_HANDLE, ComputedTextureSlices, Sprite, SPRITE_SHADER_HANDLE,
}; };
use bevy_asset::{AssetEvent, AssetId, Assets, Handle}; use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
@ -335,7 +335,7 @@ pub fn extract_sprite_events(
pub fn extract_sprites( pub fn extract_sprites(
mut commands: Commands, mut commands: Commands,
mut extracted_sprites: ResMut<ExtractedSprites>, mut extracted_sprites: ResMut<ExtractedSprites>,
texture_atlases: Extract<Res<Assets<TextureAtlas>>>, texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
sprite_query: Extract< sprite_query: Extract<
Query<( Query<(
Entity, Entity,
@ -343,25 +343,17 @@ pub fn extract_sprites(
&Sprite, &Sprite,
&GlobalTransform, &GlobalTransform,
&Handle<Image>, &Handle<Image>,
Option<&TextureAtlas>,
Option<&ComputedTextureSlices>, Option<&ComputedTextureSlices>,
)>, )>,
>, >,
atlas_query: Extract<
Query<(
Entity,
&ViewVisibility,
&TextureAtlasSprite,
&GlobalTransform,
&Handle<TextureAtlas>,
)>,
>,
) { ) {
extracted_sprites.sprites.clear(); extracted_sprites.sprites.clear();
for (entity, view_visibility, sprite, transform, handle, sheet, slices) in sprite_query.iter() {
for (entity, view_visibility, sprite, transform, handle, slices) in sprite_query.iter() {
if !view_visibility.get() { if !view_visibility.get() {
continue; continue;
} }
if let Some(slices) = slices { if let Some(slices) = slices {
extracted_sprites.sprites.extend( extracted_sprites.sprites.extend(
slices slices
@ -369,13 +361,14 @@ pub fn extract_sprites(
.map(|e| (commands.spawn_empty().id(), e)), .map(|e| (commands.spawn_empty().id(), e)),
); );
} else { } else {
let rect = sheet.and_then(|s| s.texture_rect(&texture_atlases));
// PERF: we don't check in this function that the `Image` asset is ready, since it should be in most cases and hashing the handle is expensive // PERF: we don't check in this function that the `Image` asset is ready, since it should be in most cases and hashing the handle is expensive
extracted_sprites.sprites.insert( extracted_sprites.sprites.insert(
entity, entity,
ExtractedSprite { ExtractedSprite {
color: sprite.color, color: sprite.color,
transform: *transform, transform: *transform,
rect: sprite.rect, rect,
// Pass the custom size // Pass the custom size
custom_size: sprite.custom_size, custom_size: sprite.custom_size,
flip_x: sprite.flip_x, flip_x: sprite.flip_x,
@ -387,43 +380,6 @@ pub fn extract_sprites(
); );
} }
} }
for (entity, view_visibility, atlas_sprite, transform, texture_atlas_handle) in
atlas_query.iter()
{
if !view_visibility.get() {
continue;
}
if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) {
let rect = Some(
*texture_atlas
.textures
.get(atlas_sprite.index)
.unwrap_or_else(|| {
panic!(
"Sprite index {:?} does not exist for texture atlas handle {:?}.",
atlas_sprite.index,
texture_atlas_handle.id(),
)
}),
);
extracted_sprites.sprites.insert(
entity,
ExtractedSprite {
color: atlas_sprite.color,
transform: *transform,
// Select the area in the texture atlas
rect,
// Pass the custom size
custom_size: atlas_sprite.custom_size,
flip_x: atlas_sprite.flip_x,
flip_y: atlas_sprite.flip_y,
image_handle_id: texture_atlas.texture.id(),
anchor: atlas_sprite.anchor.as_vec(),
original_entity: None,
},
);
}
}
} }
#[repr(C)] #[repr(C)]

View File

@ -1,97 +1,84 @@
use crate::Anchor; use bevy_asset::{Asset, AssetId, Assets, Handle};
use bevy_asset::{Asset, AssetId, Handle}; use bevy_ecs::component::Component;
use bevy_ecs::{component::Component, reflect::ReflectComponent};
use bevy_math::{Rect, Vec2}; use bevy_math::{Rect, Vec2};
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
use bevy_render::{color::Color, texture::Image}; use bevy_render::texture::Image;
use bevy_utils::HashMap; use bevy_utils::HashMap;
/// An atlas containing multiple textures (like a spritesheet or a tilemap). /// 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.
///
/// Optionaly 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.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)
/// [Example usage loading sprite sheet.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs) /// [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)] #[derive(Asset, Reflect, Debug, Clone)]
#[reflect(Debug)] #[reflect(Debug)]
pub struct TextureAtlas { pub struct TextureAtlasLayout {
/// The handle to the texture in which the sprites are stored
pub texture: Handle<Image>,
// TODO: add support to Uniforms derive to write dimensions and sprites to the same buffer // TODO: add support to Uniforms derive to write dimensions and sprites to the same buffer
pub size: Vec2, pub size: Vec2,
/// The specific areas of the atlas where each texture can be found /// The specific areas of the atlas where each texture can be found
pub textures: Vec<Rect>, pub textures: Vec<Rect>,
/// Mapping from texture handle to index /// 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<HashMap<AssetId<Image>, usize>>, pub(crate) texture_handles: Option<HashMap<AssetId<Image>, usize>>,
} }
/// Specifies the rendering properties of a sprite from a sprite sheet. /// Component used to draw a specific section of a texture.
/// ///
/// This is commonly used as a component within [`SpriteSheetBundle`](crate::bundle::SpriteSheetBundle). /// It stores a handle to [`TextureAtlasLayout`] and the index of the current section of the atlas.
#[derive(Component, Debug, Clone, Reflect)] /// The texture atlas contains various *sections* of a given texture, allowing users to have a single
#[reflect(Component)] /// image file for either sprite animation or global mapping.
pub struct TextureAtlasSprite { /// You can change the texture [`index`](Self::index) of the atlas to animate the sprite or dsplay only a *section* of the texture
/// The tint color used to draw the sprite, defaulting to [`Color::WHITE`] /// for efficient rendering of related game objects.
pub color: Color, ///
/// Texture index in [`TextureAtlas`] /// Check the following examples for usage:
/// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)
/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
#[derive(Component, Default, Debug, Clone, Reflect)]
pub struct TextureAtlas {
/// Texture atlas handle
pub layout: Handle<TextureAtlasLayout>,
/// Texture atlas section index
pub index: usize, pub index: usize,
/// Whether to flip the sprite in the X axis
pub flip_x: bool,
/// Whether to flip the sprite in the Y axis
pub flip_y: bool,
/// An optional custom size for the sprite that will be used when rendering, instead of the size
/// of the sprite's image in the atlas
pub custom_size: Option<Vec2>,
/// [`Anchor`] point of the sprite in the world
pub anchor: Anchor,
} }
impl Default for TextureAtlasSprite { impl TextureAtlasLayout {
fn default() -> Self { /// Create a new empty layout with custom `dimensions`
pub fn new_empty(dimensions: Vec2) -> Self {
Self { Self {
index: 0,
color: Color::WHITE,
flip_x: false,
flip_y: false,
custom_size: None,
anchor: Anchor::default(),
}
}
}
impl TextureAtlasSprite {
/// Create a new [`TextureAtlasSprite`] with a sprite index,
/// it should be valid in the corresponding [`TextureAtlas`]
pub fn new(index: usize) -> TextureAtlasSprite {
Self {
index,
..Default::default()
}
}
}
impl TextureAtlas {
/// Create a new [`TextureAtlas`] that has a texture, but does not have
/// any individual sprites specified
pub fn new_empty(texture: Handle<Image>, dimensions: Vec2) -> Self {
Self {
texture,
size: dimensions, size: dimensions,
texture_handles: None, texture_handles: None,
textures: Vec::new(), textures: Vec::new(),
} }
} }
/// Generate a [`TextureAtlas`] by splitting a texture into a grid where each /// Generate a [`TextureAtlasLayout`] as a grid where each
/// `tile_size` by `tile_size` grid-cell is one of the textures in the /// `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 /// atlas. Grid cells are separated by some `padding`, and the grid starts
/// at `offset` pixels from the top left corner. The resulting [`TextureAtlas`] is /// at `offset` pixels from the top left corner. Resulting layout is
/// indexed left to right, top to bottom. /// 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( pub fn from_grid(
texture: Handle<Image>,
tile_size: Vec2, tile_size: Vec2,
columns: usize, columns: usize,
rows: usize, rows: usize,
padding: Option<Vec2>, padding: Option<Vec2>,
offset: Option<Vec2>, offset: Option<Vec2>,
) -> TextureAtlas { ) -> Self {
let padding = padding.unwrap_or_default(); let padding = padding.unwrap_or_default();
let offset = offset.unwrap_or_default(); let offset = offset.unwrap_or_default();
let mut sprites = Vec::new(); let mut sprites = Vec::new();
@ -119,37 +106,40 @@ impl TextureAtlas {
let grid_size = Vec2::new(columns as f32, rows as f32); let grid_size = Vec2::new(columns as f32, rows as f32);
TextureAtlas { Self {
size: ((tile_size + current_padding) * grid_size) - current_padding, size: ((tile_size + current_padding) * grid_size) - current_padding,
textures: sprites, textures: sprites,
texture,
texture_handles: None, texture_handles: None,
} }
} }
/// Add a sprite to the list of textures in the [`TextureAtlas`] /// Add a *section* to the list in the layout and returns its index
/// returns an index to the texture which can be used with [`TextureAtlasSprite`] /// which can be used with [`TextureAtlas`]
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `rect` - The section of the atlas that contains the texture to be added, /// * `rect` - The section of the texture to be added
/// from the top-left corner of the texture to the bottom-right corner ///
/// [`TextureAtlas`]: crate::TextureAtlas
pub fn add_texture(&mut self, rect: Rect) -> usize { pub fn add_texture(&mut self, rect: Rect) -> usize {
self.textures.push(rect); self.textures.push(rect);
self.textures.len() - 1 self.textures.len() - 1
} }
/// The number of textures in the [`TextureAtlas`] /// The number of textures in the [`TextureAtlasLayout`]
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.textures.len() self.textures.len()
} }
/// Returns `true` if there are no textures in the [`TextureAtlas`]
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.textures.is_empty() self.textures.is_empty()
} }
/// Returns the index of the texture corresponding to the given image handle in the [`TextureAtlas`] /// 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<AssetId<Image>>) -> Option<usize> { pub fn get_texture_index(&self, texture: impl Into<AssetId<Image>>) -> Option<usize> {
let id = texture.into(); let id = texture.into();
self.texture_handles self.texture_handles
@ -157,3 +147,20 @@ impl TextureAtlas {
.and_then(|texture_handles| texture_handles.get(&id).cloned()) .and_then(|texture_handles| texture_handles.get(&id).cloned())
} }
} }
impl TextureAtlas {
/// Retrieves the current texture [`Rect`] of the sprite sheet according to the section `index`
pub fn texture_rect(&self, texture_atlases: &Assets<TextureAtlasLayout>) -> Option<Rect> {
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,
}
}
}

View File

@ -1,3 +1,4 @@
use bevy_asset::Handle;
use bevy_asset::{AssetId, Assets}; use bevy_asset::{AssetId, Assets};
use bevy_log::{debug, error, warn}; use bevy_log::{debug, error, warn};
use bevy_math::{Rect, UVec2, Vec2}; use bevy_math::{Rect, UVec2, Vec2};
@ -13,7 +14,7 @@ use rectangle_pack::{
}; };
use thiserror::Error; use thiserror::Error;
use crate::texture_atlas::TextureAtlas; use crate::TextureAtlasLayout;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum TextureAtlasBuilderError { pub enum TextureAtlasBuilderError {
@ -146,12 +147,41 @@ impl TextureAtlasBuilder {
} }
} }
/// Consumes the builder and returns a result with a new texture atlas. /// Consumes the builder, and returns the newly created texture handle and
/// the assciated atlas layout.
/// ///
/// Internally it copies all rectangles from the textures and copies them /// Internally it copies all rectangles from the textures and copies them
/// into a new texture which the texture atlas will use. It is not useful to /// into a new texture.
/// hold a strong handle to the texture afterwards else it will exist twice /// It is not useful to hold a strong handle to the texture afterwards else
/// in memory. /// it will exist twice in memory.
///
/// # Usage
///
/// ```rust
/// # use bevy_sprite::prelude::*;
/// # use bevy_ecs::prelude::*;
/// # use bevy_asset::*;
/// # use bevy_render::prelude::*;
///
/// fn my_system(mut commands: Commands, mut textures: ResMut<Assets<Image>>, mut layouts: ResMut<Assets<TextureAtlasLayout>>) {
/// // Declare your builder
/// let mut builder = TextureAtlasBuilder::default();
/// // Customize it
/// // ...
/// // Build your texture and the atlas layout
/// let (atlas_layout, texture) = builder.finish(&mut textures).unwrap();
/// let layout = layouts.add(atlas_layout);
/// // Spawn your sprite
/// commands.spawn(SpriteSheetBundle {
/// texture,
/// atlas: TextureAtlas {
/// layout,
/// index: 0
/// },
/// ..Default::default()
/// });
/// }
/// ```
/// ///
/// # Errors /// # Errors
/// ///
@ -160,7 +190,7 @@ impl TextureAtlasBuilder {
pub fn finish( pub fn finish(
self, self,
textures: &mut Assets<Image>, textures: &mut Assets<Image>,
) -> Result<TextureAtlas, TextureAtlasBuilderError> { ) -> Result<(TextureAtlasLayout, Handle<Image>), TextureAtlasBuilderError> {
let initial_width = self.initial_size.x as u32; let initial_width = self.initial_size.x as u32;
let initial_height = self.initial_size.y as u32; let initial_height = self.initial_size.y as u32;
let max_width = self.max_size.x as u32; let max_width = self.max_size.x as u32;
@ -248,11 +278,14 @@ impl TextureAtlasBuilder {
} }
self.copy_converted_texture(&mut atlas_texture, texture, packed_location); self.copy_converted_texture(&mut atlas_texture, texture, packed_location);
} }
Ok(TextureAtlas {
size: atlas_texture.size_f32(), Ok((
texture: textures.add(atlas_texture), TextureAtlasLayout {
textures: texture_rects, size: atlas_texture.size_f32(),
texture_handles: Some(texture_ids), textures: texture_rects,
}) texture_handles: Some(texture_ids),
},
textures.add(atlas_texture),
))
} }
} }

View File

@ -6,7 +6,7 @@ use bevy_render::{
render_resource::{Extent3d, TextureDimension, TextureFormat}, render_resource::{Extent3d, TextureDimension, TextureFormat},
texture::Image, texture::Image,
}; };
use bevy_sprite::{DynamicTextureAtlasBuilder, TextureAtlas}; use bevy_sprite::{DynamicTextureAtlasBuilder, TextureAtlasLayout};
use bevy_utils::HashMap; use bevy_utils::HashMap;
#[cfg(feature = "subpixel_glyph_atlas")] #[cfg(feature = "subpixel_glyph_atlas")]
@ -43,16 +43,17 @@ impl From<Point> for SubpixelOffset {
pub struct FontAtlas { pub struct FontAtlas {
pub dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder, pub dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder,
pub glyph_to_atlas_index: HashMap<(GlyphId, SubpixelOffset), usize>, pub glyph_to_atlas_index: HashMap<(GlyphId, SubpixelOffset), usize>,
pub texture_atlas: Handle<TextureAtlas>, pub texture_atlas: Handle<TextureAtlasLayout>,
pub texture: Handle<Image>,
} }
impl FontAtlas { impl FontAtlas {
pub fn new( pub fn new(
textures: &mut Assets<Image>, textures: &mut Assets<Image>,
texture_atlases: &mut Assets<TextureAtlas>, texture_atlases: &mut Assets<TextureAtlasLayout>,
size: Vec2, size: Vec2,
) -> FontAtlas { ) -> FontAtlas {
let atlas_texture = textures.add(Image::new_fill( let texture = textures.add(Image::new_fill(
Extent3d { Extent3d {
width: size.x as u32, width: size.x as u32,
height: size.y as u32, height: size.y as u32,
@ -64,11 +65,12 @@ impl FontAtlas {
// Need to keep this image CPU persistent in order to add additional glyphs later on // Need to keep this image CPU persistent in order to add additional glyphs later on
RenderAssetPersistencePolicy::Keep, RenderAssetPersistencePolicy::Keep,
)); ));
let texture_atlas = TextureAtlas::new_empty(atlas_texture, size); let texture_atlas = TextureAtlasLayout::new_empty(size);
Self { Self {
texture_atlas: texture_atlases.add(texture_atlas), texture_atlas: texture_atlases.add(texture_atlas),
glyph_to_atlas_index: HashMap::default(), glyph_to_atlas_index: HashMap::default(),
dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder::new(size, 0), dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder::new(size, 0),
texture,
} }
} }
@ -90,16 +92,18 @@ impl FontAtlas {
pub fn add_glyph( pub fn add_glyph(
&mut self, &mut self,
textures: &mut Assets<Image>, textures: &mut Assets<Image>,
texture_atlases: &mut Assets<TextureAtlas>, texture_atlases: &mut Assets<TextureAtlasLayout>,
glyph_id: GlyphId, glyph_id: GlyphId,
subpixel_offset: SubpixelOffset, subpixel_offset: SubpixelOffset,
texture: &Image, texture: &Image,
) -> bool { ) -> bool {
let texture_atlas = texture_atlases.get_mut(&self.texture_atlas).unwrap(); let texture_atlas = texture_atlases.get_mut(&self.texture_atlas).unwrap();
if let Some(index) = if let Some(index) = self.dynamic_texture_atlas_builder.add_texture(
self.dynamic_texture_atlas_builder texture_atlas,
.add_texture(texture_atlas, textures, texture) textures,
{ texture,
&self.texture,
) {
self.glyph_to_atlas_index self.glyph_to_atlas_index
.insert((glyph_id, subpixel_offset), index); .insert((glyph_id, subpixel_offset), index);
true true

View File

@ -6,7 +6,7 @@ use bevy_ecs::prelude::*;
use bevy_math::Vec2; use bevy_math::Vec2;
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
use bevy_render::texture::Image; use bevy_render::texture::Image;
use bevy_sprite::TextureAtlas; use bevy_sprite::TextureAtlasLayout;
use bevy_utils::FloatOrd; use bevy_utils::FloatOrd;
use bevy_utils::HashMap; use bevy_utils::HashMap;
@ -43,7 +43,8 @@ pub struct FontAtlasSet {
#[derive(Debug, Clone, Reflect)] #[derive(Debug, Clone, Reflect)]
pub struct GlyphAtlasInfo { pub struct GlyphAtlasInfo {
pub texture_atlas: Handle<TextureAtlas>, pub texture_atlas: Handle<TextureAtlasLayout>,
pub texture: Handle<Image>,
pub glyph_index: usize, pub glyph_index: usize,
} }
@ -72,7 +73,7 @@ impl FontAtlasSet {
pub fn add_glyph_to_atlas( pub fn add_glyph_to_atlas(
&mut self, &mut self,
texture_atlases: &mut Assets<TextureAtlas>, texture_atlases: &mut Assets<TextureAtlasLayout>,
textures: &mut Assets<Image>, textures: &mut Assets<Image>,
outlined_glyph: OutlinedGlyph, outlined_glyph: OutlinedGlyph,
) -> Result<GlyphAtlasInfo, TextError> { ) -> Result<GlyphAtlasInfo, TextError> {
@ -145,10 +146,17 @@ impl FontAtlasSet {
.find_map(|atlas| { .find_map(|atlas| {
atlas atlas
.get_glyph_index(glyph_id, position.into()) .get_glyph_index(glyph_id, position.into())
.map(|glyph_index| (glyph_index, atlas.texture_atlas.clone_weak())) .map(|glyph_index| {
(
glyph_index,
atlas.texture_atlas.clone_weak(),
atlas.texture.clone_weak(),
)
})
}) })
.map(|(glyph_index, texture_atlas)| GlyphAtlasInfo { .map(|(glyph_index, texture_atlas, texture)| GlyphAtlasInfo {
texture_atlas, texture_atlas,
texture,
glyph_index, glyph_index,
}) })
}) })

View File

@ -3,7 +3,7 @@ use bevy_asset::{AssetId, Assets};
use bevy_math::{Rect, Vec2}; use bevy_math::{Rect, Vec2};
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
use bevy_render::texture::Image; use bevy_render::texture::Image;
use bevy_sprite::TextureAtlas; use bevy_sprite::TextureAtlasLayout;
use bevy_utils::tracing::warn; use bevy_utils::tracing::warn;
use glyph_brush_layout::{ use glyph_brush_layout::{
BuiltInLineBreaker, FontId, GlyphPositioner, Layout, SectionGeometry, SectionGlyph, BuiltInLineBreaker, FontId, GlyphPositioner, Layout, SectionGeometry, SectionGlyph,
@ -60,7 +60,7 @@ impl GlyphBrush {
sections: &[SectionText], sections: &[SectionText],
font_atlas_sets: &mut FontAtlasSets, font_atlas_sets: &mut FontAtlasSets,
fonts: &Assets<Font>, fonts: &Assets<Font>,
texture_atlases: &mut Assets<TextureAtlas>, texture_atlases: &mut Assets<TextureAtlasLayout>,
textures: &mut Assets<Image>, textures: &mut Assets<Image>,
text_settings: &TextSettings, text_settings: &TextSettings,
font_atlas_warning: &mut FontAtlasWarning, font_atlas_warning: &mut FontAtlasWarning,

View File

@ -12,7 +12,7 @@ use bevy_math::Vec2;
use bevy_reflect::prelude::ReflectDefault; use bevy_reflect::prelude::ReflectDefault;
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
use bevy_render::texture::Image; use bevy_render::texture::Image;
use bevy_sprite::TextureAtlas; use bevy_sprite::TextureAtlasLayout;
use bevy_utils::HashMap; use bevy_utils::HashMap;
use glyph_brush_layout::{FontId, GlyphPositioner, SectionGeometry, SectionText, ToSectionText}; use glyph_brush_layout::{FontId, GlyphPositioner, SectionGeometry, SectionText, ToSectionText};
@ -51,7 +51,7 @@ impl TextPipeline {
linebreak_behavior: BreakLineOn, linebreak_behavior: BreakLineOn,
bounds: Vec2, bounds: Vec2,
font_atlas_sets: &mut FontAtlasSets, font_atlas_sets: &mut FontAtlasSets,
texture_atlases: &mut Assets<TextureAtlas>, texture_atlases: &mut Assets<TextureAtlasLayout>,
textures: &mut Assets<Image>, textures: &mut Assets<Image>,
text_settings: &TextSettings, text_settings: &TextSettings,
font_atlas_warning: &mut FontAtlasWarning, font_atlas_warning: &mut FontAtlasWarning,

View File

@ -21,7 +21,7 @@ use bevy_render::{
view::{InheritedVisibility, ViewVisibility, Visibility}, view::{InheritedVisibility, ViewVisibility, Visibility},
Extract, Extract,
}; };
use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, TextureAtlas}; use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, TextureAtlasLayout};
use bevy_transform::prelude::{GlobalTransform, Transform}; use bevy_transform::prelude::{GlobalTransform, Transform};
use bevy_utils::HashSet; use bevy_utils::HashSet;
use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged}; use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged};
@ -83,7 +83,7 @@ pub struct Text2dBundle {
pub fn extract_text2d_sprite( pub fn extract_text2d_sprite(
mut commands: Commands, mut commands: Commands,
mut extracted_sprites: ResMut<ExtractedSprites>, mut extracted_sprites: ResMut<ExtractedSprites>,
texture_atlases: Extract<Res<Assets<TextureAtlas>>>, texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
windows: Extract<Query<&Window, With<PrimaryWindow>>>, windows: Extract<Query<&Window, With<PrimaryWindow>>>,
text2d_query: Extract< text2d_query: Extract<
Query<( Query<(
@ -138,7 +138,7 @@ pub fn extract_text2d_sprite(
color, color,
rect: Some(atlas.textures[atlas_info.glyph_index]), rect: Some(atlas.textures[atlas_info.glyph_index]),
custom_size: None, custom_size: None,
image_handle_id: atlas.texture.id(), image_handle_id: atlas_info.texture.id(),
flip_x: false, flip_x: false,
flip_y: false, flip_y: false,
anchor: Anchor::Center.as_vec(), anchor: Anchor::Center.as_vec(),
@ -166,7 +166,7 @@ pub fn update_text2d_layout(
mut font_atlas_warning: ResMut<FontAtlasWarning>, mut font_atlas_warning: ResMut<FontAtlasWarning>,
windows: Query<&Window, With<PrimaryWindow>>, windows: Query<&Window, With<PrimaryWindow>>,
mut scale_factor_changed: EventReader<WindowScaleFactorChanged>, mut scale_factor_changed: EventReader<WindowScaleFactorChanged>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>, mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
mut font_atlas_sets: ResMut<FontAtlasSets>, mut font_atlas_sets: ResMut<FontAtlasSets>,
mut text_pipeline: ResMut<TextPipeline>, mut text_pipeline: ResMut<TextPipeline>,
mut text_query: Query<(Entity, Ref<Text>, Ref<Text2dBounds>, &mut TextLayoutInfo)>, mut text_query: Query<(Entity, Ref<Text>, Ref<Text2dBounds>, &mut TextLayoutInfo)>,

View File

@ -118,7 +118,6 @@ impl Plugin for UiPlugin {
.register_type::<UiImageSize>() .register_type::<UiImageSize>()
.register_type::<UiRect>() .register_type::<UiRect>()
.register_type::<UiScale>() .register_type::<UiScale>()
.register_type::<UiTextureAtlasImage>()
.register_type::<Val>() .register_type::<Val>()
.register_type::<BorderColor>() .register_type::<BorderColor>()
.register_type::<widget::Button>() .register_type::<widget::Button>()
@ -151,8 +150,7 @@ impl Plugin for UiPlugin {
.ambiguous_with(bevy_text::update_text2d_layout) .ambiguous_with(bevy_text::update_text2d_layout)
// We assume Text is on disjoint UI entities to UiImage and UiTextureAtlasImage // We assume Text is on disjoint UI entities to UiImage and UiTextureAtlasImage
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(widget::update_image_content_size_system) .ambiguous_with(widget::update_image_content_size_system),
.ambiguous_with(widget::update_atlas_content_size_system),
widget::text_system widget::text_system
.after(UiSystem::Layout) .after(UiSystem::Layout)
.after(bevy_text::remove_dropped_font_atlas_sets) .after(bevy_text::remove_dropped_font_atlas_sets)
@ -163,11 +161,7 @@ impl Plugin for UiPlugin {
#[cfg(feature = "bevy_text")] #[cfg(feature = "bevy_text")]
app.add_plugins(accessibility::AccessibilityPlugin); app.add_plugins(accessibility::AccessibilityPlugin);
app.add_systems(PostUpdate, { app.add_systems(PostUpdate, {
let system = widget::update_image_content_size_system let system = widget::update_image_content_size_system.before(UiSystem::Layout);
.before(UiSystem::Layout)
// We assume UiImage, UiTextureAtlasImage are disjoint UI entities
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(widget::update_atlas_content_size_system);
// Potential conflicts: `Assets<Image>` // Potential conflicts: `Assets<Image>`
// They run independently since `widget::image_node_system` will only ever observe // They run independently since `widget::image_node_system` will only ever observe
// its own UiImage, and `widget::text_system` & `bevy_text::update_text2d_layout` // its own UiImage, and `widget::text_system` & `bevy_text::update_text2d_layout`
@ -183,11 +177,7 @@ impl Plugin for UiPlugin {
app.add_systems( app.add_systems(
PostUpdate, PostUpdate,
( (
( update_target_camera_system.before(UiSystem::Layout),
widget::update_atlas_content_size_system,
update_target_camera_system,
)
.before(UiSystem::Layout),
apply_deferred apply_deferred
.after(update_target_camera_system) .after(update_target_camera_system)
.before(UiSystem::Layout), .before(UiSystem::Layout),

View File

@ -5,7 +5,7 @@ use crate::widget::TextFlags;
use crate::{ use crate::{
widget::{Button, UiImageSize}, widget::{Button, UiImageSize},
BackgroundColor, BorderColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage, BackgroundColor, BorderColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage,
UiMaterial, UiTextureAtlasImage, ZIndex, UiMaterial, ZIndex,
}; };
use bevy_asset::Handle; use bevy_asset::Handle;
use bevy_ecs::bundle::Bundle; use bevy_ecs::bundle::Bundle;
@ -115,6 +115,8 @@ pub struct ImageBundle {
} }
/// A UI node that is a texture atlas sprite /// A UI node that is a texture atlas sprite
///
/// This bundle is identical to [`ImageBundle`] with an additional [`TextureAtlas`] component.
#[derive(Bundle, Debug, Default)] #[derive(Bundle, Debug, Default)]
pub struct AtlasImageBundle { pub struct AtlasImageBundle {
/// Describes the logical size of the node /// Describes the logical size of the node
@ -128,10 +130,10 @@ pub struct AtlasImageBundle {
/// ///
/// Combines with `UiImage` to tint the provided image. /// Combines with `UiImage` to tint the provided image.
pub background_color: BackgroundColor, pub background_color: BackgroundColor,
/// The image of the node
pub image: UiImage,
/// A handle to the texture atlas to use for this Ui Node /// A handle to the texture atlas to use for this Ui Node
pub texture_atlas: Handle<TextureAtlas>, pub texture_atlas: TextureAtlas,
/// The descriptor for which sprite to use from the given texture atlas
pub texture_atlas_image: UiTextureAtlasImage,
/// Whether this node should block interaction with lower nodes /// Whether this node should block interaction with lower nodes
pub focus_policy: FocusPolicy, pub focus_policy: FocusPolicy,
/// The size of the image in pixels /// The size of the image in pixels

View File

@ -8,13 +8,13 @@ use bevy_render::{
render_phase::PhaseItem, render_resource::BindGroupEntries, view::ViewVisibility, render_phase::PhaseItem, render_resource::BindGroupEntries, view::ViewVisibility,
ExtractSchedule, Render, ExtractSchedule, Render,
}; };
use bevy_sprite::{SpriteAssetEvents, TextureAtlas};
pub use pipeline::*; pub use pipeline::*;
pub use render_pass::*; pub use render_pass::*;
pub use ui_material_pipeline::*; pub use ui_material_pipeline::*;
use crate::{ use crate::{
BackgroundColor, BorderColor, CalculatedClip, ContentSize, Node, Style, UiImage, UiScale, BackgroundColor, BorderColor, CalculatedClip, ContentSize, Node, Style, UiImage, UiScale, Val,
UiTextureAtlasImage, Val,
}; };
use crate::{DefaultUiCamera, Outline, TargetCamera}; use crate::{DefaultUiCamera, Outline, TargetCamera};
@ -34,7 +34,8 @@ use bevy_render::{
view::{ExtractedView, ViewUniforms}, view::{ExtractedView, ViewUniforms},
Extract, RenderApp, RenderSet, Extract, RenderApp, RenderSet,
}; };
use bevy_sprite::{SpriteAssetEvents, TextureAtlas}; #[cfg(feature = "bevy_text")]
use bevy_sprite::TextureAtlasLayout;
#[cfg(feature = "bevy_text")] #[cfg(feature = "bevy_text")]
use bevy_text::{PositionedGlyph, Text, TextLayoutInfo}; use bevy_text::{PositionedGlyph, Text, TextLayoutInfo};
use bevy_transform::components::GlobalTransform; use bevy_transform::components::GlobalTransform;
@ -82,9 +83,6 @@ pub fn build_ui_render(app: &mut App) {
extract_default_ui_camera_view::<Camera2d>, extract_default_ui_camera_view::<Camera2d>,
extract_default_ui_camera_view::<Camera3d>, extract_default_ui_camera_view::<Camera3d>,
extract_uinodes.in_set(RenderUiSystem::ExtractNode), extract_uinodes.in_set(RenderUiSystem::ExtractNode),
extract_atlas_uinodes
.in_set(RenderUiSystem::ExtractAtlasNode)
.after(RenderUiSystem::ExtractNode),
extract_uinode_borders.after(RenderUiSystem::ExtractAtlasNode), extract_uinode_borders.after(RenderUiSystem::ExtractAtlasNode),
#[cfg(feature = "bevy_text")] #[cfg(feature = "bevy_text")]
extract_text_uinodes.after(RenderUiSystem::ExtractAtlasNode), extract_text_uinodes.after(RenderUiSystem::ExtractAtlasNode),
@ -174,99 +172,6 @@ pub struct ExtractedUiNodes {
pub uinodes: EntityHashMap<Entity, ExtractedUiNode>, pub uinodes: EntityHashMap<Entity, ExtractedUiNode>,
} }
pub fn extract_atlas_uinodes(
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
images: Extract<Res<Assets<Image>>>,
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
default_ui_camera: Extract<DefaultUiCamera>,
uinode_query: Extract<
Query<
(
Entity,
&Node,
&GlobalTransform,
&BackgroundColor,
&ViewVisibility,
Option<&CalculatedClip>,
&Handle<TextureAtlas>,
&UiTextureAtlasImage,
Option<&TargetCamera>,
),
Without<UiImage>,
>,
>,
) {
for (
entity,
uinode,
transform,
color,
view_visibility,
clip,
texture_atlas_handle,
atlas_image,
camera,
) in uinode_query.iter()
{
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
else {
continue;
};
// Skip invisible and completely transparent nodes
if !view_visibility.get() || color.0.is_fully_transparent() {
continue;
}
let (mut atlas_rect, mut atlas_size, image) =
if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) {
let atlas_rect = *texture_atlas
.textures
.get(atlas_image.index)
.unwrap_or_else(|| {
panic!(
"Atlas index {:?} does not exist for texture atlas handle {:?}.",
atlas_image.index,
texture_atlas_handle.id(),
)
});
(
atlas_rect,
texture_atlas.size,
texture_atlas.texture.clone(),
)
} else {
// Atlas not present in assets resource (should this warn the user?)
continue;
};
// Skip loading images
if !images.contains(&image) {
continue;
}
let scale = uinode.size() / atlas_rect.size();
atlas_rect.min *= scale;
atlas_rect.max *= scale;
atlas_size *= scale;
extracted_uinodes.uinodes.insert(
entity,
ExtractedUiNode {
stack_index: uinode.stack_index,
transform: transform.compute_matrix(),
color: color.0,
rect: atlas_rect,
clip: clip.map(|clip| clip.clip),
image: image.id(),
atlas_size: Some(atlas_size),
flip_x: atlas_image.flip_x,
flip_y: atlas_image.flip_y,
camera_entity,
},
);
}
}
pub(crate) fn resolve_border_thickness(value: Val, parent_width: f32, viewport_size: Vec2) -> f32 { pub(crate) fn resolve_border_thickness(value: Val, parent_width: f32, viewport_size: Vec2) -> f32 {
match value { match value {
Val::Auto => 0., Val::Auto => 0.,
@ -495,24 +400,23 @@ pub fn extract_uinode_outlines(
pub fn extract_uinodes( pub fn extract_uinodes(
mut extracted_uinodes: ResMut<ExtractedUiNodes>, mut extracted_uinodes: ResMut<ExtractedUiNodes>,
images: Extract<Res<Assets<Image>>>, images: Extract<Res<Assets<Image>>>,
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
default_ui_camera: Extract<DefaultUiCamera>, default_ui_camera: Extract<DefaultUiCamera>,
uinode_query: Extract< uinode_query: Extract<
Query< Query<(
( Entity,
Entity, &Node,
&Node, &GlobalTransform,
&GlobalTransform, &BackgroundColor,
&BackgroundColor, Option<&UiImage>,
Option<&UiImage>, &ViewVisibility,
&ViewVisibility, Option<&CalculatedClip>,
Option<&CalculatedClip>, Option<&TextureAtlas>,
Option<&TargetCamera>, Option<&TargetCamera>,
), )>,
Without<UiTextureAtlasImage>,
>,
>, >,
) { ) {
for (entity, uinode, transform, color, maybe_image, view_visibility, clip, camera) in for (entity, uinode, transform, color, maybe_image, view_visibility, clip, atlas, camera) in
uinode_query.iter() uinode_query.iter()
{ {
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get()) let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
@ -534,19 +438,39 @@ pub fn extract_uinodes(
(AssetId::default(), false, false) (AssetId::default(), false, false)
}; };
let (rect, atlas_size) = match atlas {
Some(atlas) => {
let Some(layout) = texture_atlases.get(&atlas.layout) else {
// Atlas not present in assets resource (should this warn the user?)
continue;
};
let mut atlas_rect = layout.textures[atlas.index];
let mut atlas_size = layout.size;
let scale = uinode.size() / atlas_rect.size();
atlas_rect.min *= scale;
atlas_rect.max *= scale;
atlas_size *= scale;
(atlas_rect, Some(atlas_size))
}
None => (
Rect {
min: Vec2::ZERO,
max: uinode.calculated_size,
},
None,
),
};
extracted_uinodes.uinodes.insert( extracted_uinodes.uinodes.insert(
entity, entity,
ExtractedUiNode { ExtractedUiNode {
stack_index: uinode.stack_index, stack_index: uinode.stack_index,
transform: transform.compute_matrix(), transform: transform.compute_matrix(),
color: color.0, color: color.0,
rect: Rect { rect,
min: Vec2::ZERO,
max: uinode.calculated_size,
},
clip: clip.map(|clip| clip.clip), clip: clip.map(|clip| clip.clip),
image, image,
atlas_size: None, atlas_size,
flip_x, flip_x,
flip_y, flip_y,
camera_entity, camera_entity,
@ -635,7 +559,7 @@ pub fn extract_text_uinodes(
mut extracted_uinodes: ResMut<ExtractedUiNodes>, mut extracted_uinodes: ResMut<ExtractedUiNodes>,
camera_query: Extract<Query<(Entity, &Camera)>>, camera_query: Extract<Query<(Entity, &Camera)>>,
default_ui_camera: Extract<DefaultUiCamera>, default_ui_camera: Extract<DefaultUiCamera>,
texture_atlases: Extract<Res<Assets<TextureAtlas>>>, texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
ui_scale: Extract<Res<UiScale>>, ui_scale: Extract<Res<UiScale>>,
uinode_query: Extract< uinode_query: Extract<
Query<( Query<(
@ -708,7 +632,7 @@ pub fn extract_text_uinodes(
* Mat4::from_translation(position.extend(0.) * inverse_scale_factor), * Mat4::from_translation(position.extend(0.) * inverse_scale_factor),
color, color,
rect, rect,
image: atlas.texture.id(), image: atlas_info.texture.id(),
atlas_size: Some(atlas.size * inverse_scale_factor), atlas_size: Some(atlas.size * inverse_scale_factor),
clip: clip.map(|clip| clip.clip), clip: clip.map(|clip| clip.clip),
flip_x: false, flip_x: false,

View File

@ -1608,18 +1608,6 @@ impl From<Color> for BackgroundColor {
} }
} }
/// The atlas sprite to be used in a UI Texture Atlas Node
#[derive(Component, Clone, Debug, Reflect, Default)]
#[reflect(Component, Default)]
pub struct UiTextureAtlasImage {
/// Texture index in the TextureAtlas
pub index: usize,
/// Whether to flip the sprite in the X axis
pub flip_x: bool,
/// Whether to flip the sprite in the Y axis
pub flip_y: bool,
}
/// The border color of the UI node. /// The border color of the UI node.
#[derive(Component, Copy, Clone, Debug, Reflect)] #[derive(Component, Copy, Clone, Debug, Reflect)]
#[reflect(Component, Default)] #[reflect(Component, Default)]

View File

@ -1,7 +1,5 @@
use crate::{ use crate::{measurement::AvailableSpace, ContentSize, Measure, Node, UiImage, UiScale};
measurement::AvailableSpace, ContentSize, Measure, Node, UiImage, UiScale, UiTextureAtlasImage, use bevy_asset::Assets;
};
use bevy_asset::{Assets, Handle};
use bevy_ecs::change_detection::DetectChanges; use bevy_ecs::change_detection::DetectChanges;
use bevy_ecs::query::Without; use bevy_ecs::query::Without;
@ -14,7 +12,7 @@ use bevy_ecs::{
use bevy_math::Vec2; use bevy_math::Vec2;
use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::texture::Image; use bevy_render::texture::Image;
use bevy_sprite::TextureAtlas; use bevy_sprite::{TextureAtlas, TextureAtlasLayout};
use bevy_window::{PrimaryWindow, Window}; use bevy_window::{PrimaryWindow, Window};
/// The size of the image's texture /// The size of the image's texture
@ -82,48 +80,15 @@ pub fn update_image_content_size_system(
windows: Query<&Window, With<PrimaryWindow>>, windows: Query<&Window, With<PrimaryWindow>>,
ui_scale: Res<UiScale>, ui_scale: Res<UiScale>,
textures: Res<Assets<Image>>, textures: Res<Assets<Image>>,
mut query: Query<(&mut ContentSize, &UiImage, &mut UiImageSize), UpdateImageFilter>, atlases: Res<Assets<TextureAtlasLayout>>,
) { mut query: Query<
let combined_scale_factor = windows
.get_single()
.map(|window| window.resolution.scale_factor())
.unwrap_or(1.)
* ui_scale.0;
for (mut content_size, image, mut image_size) in &mut query {
if let Some(texture) = textures.get(&image.texture) {
let size = texture.size_f32();
// Update only if size or scale factor has changed to avoid needless layout calculations
if size != image_size.size
|| combined_scale_factor != *previous_combined_scale_factor
|| content_size.is_added()
{
image_size.size = size;
content_size.set(ImageMeasure {
// multiply the image size by the scale factor to get the physical size
size: size * combined_scale_factor,
});
}
}
}
*previous_combined_scale_factor = combined_scale_factor;
}
/// Updates content size of the node based on the texture atlas sprite
pub fn update_atlas_content_size_system(
mut previous_combined_scale_factor: Local<f32>,
windows: Query<&Window, With<PrimaryWindow>>,
ui_scale: Res<UiScale>,
atlases: Res<Assets<TextureAtlas>>,
mut atlas_query: Query<
( (
&mut ContentSize, &mut ContentSize,
&Handle<TextureAtlas>, &UiImage,
&UiTextureAtlasImage,
&mut UiImageSize, &mut UiImageSize,
Option<&TextureAtlas>,
), ),
(UpdateImageFilter, Without<UiImage>), UpdateImageFilter,
>, >,
) { ) {
let combined_scale_factor = windows let combined_scale_factor = windows
@ -132,9 +97,11 @@ pub fn update_atlas_content_size_system(
.unwrap_or(1.) .unwrap_or(1.)
* ui_scale.0; * ui_scale.0;
for (mut content_size, atlas, atlas_image, mut image_size) in &mut atlas_query { for (mut content_size, image, mut image_size, atlas_image) in &mut query {
if let Some(atlas) = atlases.get(atlas) { if let Some(size) = match atlas_image {
let size = atlas.textures[atlas_image.index].size(); Some(atlas) => atlas.texture_rect(&atlases).map(|t| t.size()),
None => textures.get(&image.texture).map(|t| t.size_f32()),
} {
// Update only if size or scale factor has changed to avoid needless layout calculations // Update only if size or scale factor has changed to avoid needless layout calculations
if size != image_size.size if size != image_size.size
|| combined_scale_factor != *previous_combined_scale_factor || combined_scale_factor != *previous_combined_scale_factor

View File

@ -10,7 +10,7 @@ use bevy_ecs::{
use bevy_math::Vec2; use bevy_math::Vec2;
use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::texture::Image; use bevy_render::texture::Image;
use bevy_sprite::TextureAtlas; use bevy_sprite::TextureAtlasLayout;
use bevy_text::{ use bevy_text::{
scale_value, BreakLineOn, Font, FontAtlasSets, FontAtlasWarning, Text, TextError, scale_value, BreakLineOn, Font, FontAtlasSets, FontAtlasWarning, Text, TextError,
TextLayoutInfo, TextMeasureInfo, TextPipeline, TextSettings, YAxisOrientation, TextLayoutInfo, TextMeasureInfo, TextPipeline, TextSettings, YAxisOrientation,
@ -155,7 +155,7 @@ fn queue_text(
text_pipeline: &mut TextPipeline, text_pipeline: &mut TextPipeline,
font_atlas_warning: &mut FontAtlasWarning, font_atlas_warning: &mut FontAtlasWarning,
font_atlas_sets: &mut FontAtlasSets, font_atlas_sets: &mut FontAtlasSets,
texture_atlases: &mut Assets<TextureAtlas>, texture_atlases: &mut Assets<TextureAtlasLayout>,
textures: &mut Assets<Image>, textures: &mut Assets<Image>,
text_settings: &TextSettings, text_settings: &TextSettings,
scale_factor: f32, scale_factor: f32,
@ -226,7 +226,7 @@ pub fn text_system(
text_settings: Res<TextSettings>, text_settings: Res<TextSettings>,
mut font_atlas_warning: ResMut<FontAtlasWarning>, mut font_atlas_warning: ResMut<FontAtlasWarning>,
ui_scale: Res<UiScale>, ui_scale: Res<UiScale>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>, mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
mut font_atlas_sets: ResMut<FontAtlasSets>, mut font_atlas_sets: ResMut<FontAtlasSets>,
mut text_pipeline: ResMut<TextPipeline>, mut text_pipeline: ResMut<TextPipeline>,
mut text_query: Query<(Ref<Node>, &Text, &mut TextLayoutInfo, &mut TextFlags)>, mut text_query: Query<(Ref<Node>, &Text, &mut TextLayoutInfo, &mut TextFlags)>,

View File

@ -22,19 +22,15 @@ struct AnimationTimer(Timer);
fn animate_sprite( fn animate_sprite(
time: Res<Time>, time: Res<Time>,
mut query: Query<( mut query: Query<(&AnimationIndices, &mut AnimationTimer, &mut TextureAtlas)>,
&AnimationIndices,
&mut AnimationTimer,
&mut TextureAtlasSprite,
)>,
) { ) {
for (indices, mut timer, mut sprite) in &mut query { for (indices, mut timer, mut atlas) in &mut query {
timer.tick(time.delta()); timer.tick(time.delta());
if timer.just_finished() { if timer.just_finished() {
sprite.index = if sprite.index == indices.last { atlas.index = if atlas.index == indices.last {
indices.first indices.first
} else { } else {
sprite.index + 1 atlas.index + 1
}; };
} }
} }
@ -43,19 +39,21 @@ fn animate_sprite(
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>, mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
) { ) {
let texture_handle = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png"); let texture = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png");
let texture_atlas = let atlas = TextureAtlasLayout::from_grid(Vec2::new(24.0, 24.0), 7, 1, None, None);
TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1, None, None); let texture_atlas = texture_atlases.add(atlas);
let texture_atlas_handle = texture_atlases.add(texture_atlas);
// Use only the subset of sprites in the sheet that make up the run animation // Use only the subset of sprites in the sheet that make up the run animation
let animation_indices = AnimationIndices { first: 1, last: 6 }; let animation_indices = AnimationIndices { first: 1, last: 6 };
commands.spawn(Camera2dBundle::default()); commands.spawn(Camera2dBundle::default());
commands.spawn(( commands.spawn((
SpriteSheetBundle { SpriteSheetBundle {
texture_atlas: texture_atlas_handle, texture,
sprite: TextureAtlasSprite::new(animation_indices.first), atlas: TextureAtlas {
layout: texture_atlas,
index: animation_indices.first,
},
transform: Transform::from_scale(Vec3::splat(6.0)), transform: Transform::from_scale(Vec3::splat(6.0)),
..default() ..default()
}, },

View File

@ -52,15 +52,15 @@ fn setup(
mut commands: Commands, mut commands: Commands,
rpg_sprite_handles: Res<RpgSpriteFolder>, rpg_sprite_handles: Res<RpgSpriteFolder>,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
loaded_folders: Res<Assets<LoadedFolder>>, loaded_folders: Res<Assets<LoadedFolder>>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
mut textures: ResMut<Assets<Image>>, mut textures: ResMut<Assets<Image>>,
) { ) {
let loaded_folder = loaded_folders.get(&rpg_sprite_handles.0).unwrap(); let loaded_folder = loaded_folders.get(&rpg_sprite_handles.0).unwrap();
// create texture atlases with different padding and sampling // create texture atlases with different padding and sampling
let texture_atlas_linear = create_texture_atlas( let (texture_atlas_linear, linear_texture) = create_texture_atlas(
loaded_folder, loaded_folder,
None, None,
Some(ImageSampler::linear()), Some(ImageSampler::linear()),
@ -68,7 +68,7 @@ fn setup(
); );
let atlas_linear_handle = texture_atlases.add(texture_atlas_linear.clone()); let atlas_linear_handle = texture_atlases.add(texture_atlas_linear.clone());
let texture_atlas_nearest = create_texture_atlas( let (texture_atlas_nearest, nearest_texture) = create_texture_atlas(
loaded_folder, loaded_folder,
None, None,
Some(ImageSampler::nearest()), Some(ImageSampler::nearest()),
@ -76,7 +76,7 @@ fn setup(
); );
let atlas_nearest_handle = texture_atlases.add(texture_atlas_nearest); let atlas_nearest_handle = texture_atlases.add(texture_atlas_nearest);
let texture_atlas_linear_padded = create_texture_atlas( let (texture_atlas_linear_padded, linear_padded_texture) = create_texture_atlas(
loaded_folder, loaded_folder,
Some(UVec2::new(6, 6)), Some(UVec2::new(6, 6)),
Some(ImageSampler::linear()), Some(ImageSampler::linear()),
@ -84,7 +84,7 @@ fn setup(
); );
let atlas_linear_padded_handle = texture_atlases.add(texture_atlas_linear_padded.clone()); let atlas_linear_padded_handle = texture_atlases.add(texture_atlas_linear_padded.clone());
let texture_atlas_nearest_padded = create_texture_atlas( let (texture_atlas_nearest_padded, nearest_padded_texture) = create_texture_atlas(
loaded_folder, loaded_folder,
Some(UVec2::new(6, 6)), Some(UVec2::new(6, 6)),
Some(ImageSampler::nearest()), Some(ImageSampler::nearest()),
@ -99,7 +99,7 @@ fn setup(
// draw unpadded texture atlas // draw unpadded texture atlas
commands.spawn(SpriteBundle { commands.spawn(SpriteBundle {
texture: texture_atlas_linear_padded.texture.clone(), texture: linear_texture.clone(),
transform: Transform { transform: Transform {
translation: Vec3::new(-250.0, -130.0, 0.0), translation: Vec3::new(-250.0, -130.0, 0.0),
scale: Vec3::splat(0.8), scale: Vec3::splat(0.8),
@ -110,7 +110,7 @@ fn setup(
// draw padded texture atlas // draw padded texture atlas
commands.spawn(SpriteBundle { commands.spawn(SpriteBundle {
texture: texture_atlas_linear_padded.texture, texture: linear_padded_texture.clone(),
transform: Transform { transform: Transform {
translation: Vec3::new(250.0, -130.0, 0.0), translation: Vec3::new(250.0, -130.0, 0.0),
scale: Vec3::splat(0.8), scale: Vec3::splat(0.8),
@ -153,11 +153,21 @@ fn setup(
.unwrap(); .unwrap();
// configuration array to render sprites through iteration // configuration array to render sprites through iteration
let configurations: [(&str, Handle<TextureAtlas>, f32); 4] = [ let configurations: [(&str, Handle<TextureAtlasLayout>, Handle<Image>, f32); 4] = [
("Linear", atlas_linear_handle, -350.0), ("Linear", atlas_linear_handle, linear_texture, -350.0),
("Nearest", atlas_nearest_handle, -150.0), ("Nearest", atlas_nearest_handle, nearest_texture, -150.0),
("Linear", atlas_linear_padded_handle, 150.0), (
("Nearest", atlas_nearest_padded_handle, 350.0), "Linear",
atlas_linear_padded_handle,
linear_padded_texture,
150.0,
),
(
"Nearest",
atlas_nearest_padded_handle,
nearest_padded_texture,
350.0,
),
]; ];
// label text style // label text style
@ -169,9 +179,15 @@ fn setup(
let base_y = 170.0; // y position of the sprites let base_y = 170.0; // y position of the sprites
for (sampling, atlas_handle, x) in configurations { for (sampling, atlas_handle, image_handle, x) in configurations {
// render a sprite from the texture_atlas // render a sprite from the texture_atlas
create_sprite_from_atlas(&mut commands, (x, base_y, 0.0), vendor_index, atlas_handle); create_sprite_from_atlas(
&mut commands,
(x, base_y, 0.0),
vendor_index,
atlas_handle,
image_handle,
);
// render a label to indicate the sampling setting // render a label to indicate the sampling setting
create_label( create_label(
@ -190,7 +206,7 @@ fn create_texture_atlas(
padding: Option<UVec2>, padding: Option<UVec2>,
sampling: Option<ImageSampler>, sampling: Option<ImageSampler>,
textures: &mut ResMut<Assets<Image>>, textures: &mut ResMut<Assets<Image>>,
) -> TextureAtlas { ) -> (TextureAtlasLayout, Handle<Image>) {
// Build a `TextureAtlas` using the individual sprites // Build a `TextureAtlas` using the individual sprites
let mut texture_atlas_builder = let mut texture_atlas_builder =
TextureAtlasBuilder::default().padding(padding.unwrap_or_default()); TextureAtlasBuilder::default().padding(padding.unwrap_or_default());
@ -207,13 +223,13 @@ fn create_texture_atlas(
texture_atlas_builder.add_texture(id, texture); texture_atlas_builder.add_texture(id, texture);
} }
let texture_atlas = texture_atlas_builder.finish(textures).unwrap(); let (texture_atlas, texture) = texture_atlas_builder.finish(textures).unwrap();
// Update the sampling settings of the texture atlas // Update the sampling settings of the texture atlas
let image = textures.get_mut(&texture_atlas.texture).unwrap(); let image = textures.get_mut(&texture).unwrap();
image.sampler = sampling.unwrap_or_default(); image.sampler = sampling.unwrap_or_default();
texture_atlas (texture_atlas, texture)
} }
/// Create and spawn a sprite from a texture atlas /// Create and spawn a sprite from a texture atlas
@ -221,7 +237,8 @@ fn create_sprite_from_atlas(
commands: &mut Commands, commands: &mut Commands,
translation: (f32, f32, f32), translation: (f32, f32, f32),
sprite_index: usize, sprite_index: usize,
atlas_handle: Handle<TextureAtlas>, atlas_handle: Handle<TextureAtlasLayout>,
texture: Handle<Image>,
) { ) {
commands.spawn(SpriteSheetBundle { commands.spawn(SpriteSheetBundle {
transform: Transform { transform: Transform {
@ -229,8 +246,11 @@ fn create_sprite_from_atlas(
scale: Vec3::splat(3.0), scale: Vec3::splat(3.0),
..default() ..default()
}, },
sprite: TextureAtlasSprite::new(sprite_index), texture,
texture_atlas: atlas_handle, atlas: TextureAtlas {
index: sprite_index,
layout: atlas_handle,
},
..default() ..default()
}); });
} }

View File

@ -48,7 +48,7 @@ fn main() {
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
assets: Res<AssetServer>, assets: Res<AssetServer>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>, mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
) { ) {
warn!(include_str!("warning_string.txt")); warn!(include_str!("warning_string.txt"));
@ -61,8 +61,7 @@ fn setup(
let half_y = (map_size.y / 2.0) as i32; let half_y = (map_size.y / 2.0) as i32;
let texture_handle = assets.load("textures/rpg/chars/gabe/gabe-idle-run.png"); let texture_handle = assets.load("textures/rpg/chars/gabe/gabe-idle-run.png");
let texture_atlas = let texture_atlas = TextureAtlasLayout::from_grid(Vec2::new(24.0, 24.0), 7, 1, None, None);
TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1, None, None);
let texture_atlas_handle = texture_atlases.add(texture_atlas); let texture_atlas_handle = texture_atlases.add(texture_atlas);
// Spawns the camera // Spawns the camera
@ -81,13 +80,17 @@ fn setup(
commands.spawn(( commands.spawn((
SpriteSheetBundle { SpriteSheetBundle {
texture_atlas: texture_atlas_handle.clone(), texture: texture_handle.clone(),
atlas: TextureAtlas {
layout: texture_atlas_handle.clone(),
..Default::default()
},
transform: Transform { transform: Transform {
translation, translation,
rotation, rotation,
scale, scale,
}, },
sprite: TextureAtlasSprite { sprite: Sprite {
custom_size: Some(tile_size), custom_size: Some(tile_size),
..default() ..default()
}, },
@ -112,18 +115,14 @@ struct AnimationTimer(Timer);
fn animate_sprite( fn animate_sprite(
time: Res<Time>, time: Res<Time>,
texture_atlases: Res<Assets<TextureAtlas>>, texture_atlases: Res<Assets<TextureAtlasLayout>>,
mut query: Query<( mut query: Query<(&mut AnimationTimer, &mut TextureAtlas)>,
&mut AnimationTimer,
&mut TextureAtlasSprite,
&Handle<TextureAtlas>,
)>,
) { ) {
for (mut timer, mut sprite, texture_atlas_handle) in query.iter_mut() { for (mut timer, mut sheet) in query.iter_mut() {
timer.tick(time.delta()); timer.tick(time.delta());
if timer.just_finished() { if timer.just_finished() {
let texture_atlas = texture_atlases.get(texture_atlas_handle).unwrap(); let texture_atlas = texture_atlases.get(&sheet.layout).unwrap();
sprite.index = (sprite.index + 1) % texture_atlas.textures.len(); sheet.index = (sheet.index + 1) % texture_atlas.textures.len();
} }
} }
} }
@ -138,11 +137,7 @@ impl Default for PrintingTimer {
} }
// System for printing the number of sprites on every tick of the timer // System for printing the number of sprites on every tick of the timer
fn print_sprite_count( fn print_sprite_count(time: Res<Time>, mut timer: Local<PrintingTimer>, sprites: Query<&Sprite>) {
time: Res<Time>,
mut timer: Local<PrintingTimer>,
sprites: Query<&TextureAtlasSprite>,
) {
timer.tick(time.delta()); timer.tick(time.delta());
if timer.just_finished() { if timer.just_finished() {

View File

@ -34,7 +34,6 @@ fn atlas_render_system(
mut commands: Commands, mut commands: Commands,
mut state: ResMut<State>, mut state: ResMut<State>,
font_atlas_sets: Res<FontAtlasSets>, font_atlas_sets: Res<FontAtlasSets>,
texture_atlases: Res<Assets<TextureAtlas>>,
) { ) {
if let Some(set) = font_atlas_sets.get(&state.handle) { if let Some(set) = font_atlas_sets.get(&state.handle) {
if let Some((_size, font_atlas)) = set.iter().next() { if let Some((_size, font_atlas)) = set.iter().next() {
@ -42,12 +41,10 @@ fn atlas_render_system(
if state.atlas_count == font_atlas.len() as u32 { if state.atlas_count == font_atlas.len() as u32 {
return; return;
} }
let texture_atlas = texture_atlases let font_atlas = &font_atlas[state.atlas_count as usize];
.get(&font_atlas[state.atlas_count as usize].texture_atlas)
.unwrap();
state.atlas_count += 1; state.atlas_count += 1;
commands.spawn(ImageBundle { commands.spawn(ImageBundle {
image: texture_atlas.texture.clone().into(), image: font_atlas.texture.clone().into(),
style: Style { style: Style {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
top: Val::ZERO, top: Val::ZERO,

View File

@ -20,7 +20,7 @@ fn main() {
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>, mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
) { ) {
// Camera // Camera
commands.spawn(Camera2dBundle::default()); commands.spawn(Camera2dBundle::default());
@ -31,8 +31,7 @@ fn setup(
}; };
let texture_handle = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png"); let texture_handle = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png");
let texture_atlas = let texture_atlas = TextureAtlasLayout::from_grid(Vec2::new(24.0, 24.0), 7, 1, None, None);
TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1, None, None);
let texture_atlas_handle = texture_atlases.add(texture_atlas); let texture_atlas_handle = texture_atlases.add(texture_atlas);
// root node // root node
@ -56,8 +55,8 @@ fn setup(
height: Val::Px(256.), height: Val::Px(256.),
..default() ..default()
}, },
texture_atlas: texture_atlas_handle, texture_atlas: texture_atlas_handle.into(),
texture_atlas_image: UiTextureAtlasImage::default(), image: UiImage::new(texture_handle),
..default() ..default()
}); });
parent.spawn(TextBundle::from_sections([ parent.spawn(TextBundle::from_sections([
@ -75,7 +74,7 @@ fn setup(
} }
fn increment_atlas_index( fn increment_atlas_index(
mut atlas_images: Query<&mut UiTextureAtlasImage>, mut atlas_images: Query<&mut TextureAtlas>,
keyboard: Res<ButtonInput<KeyCode>>, keyboard: Res<ButtonInput<KeyCode>>,
) { ) {
if keyboard.just_pressed(KeyCode::Space) { if keyboard.just_pressed(KeyCode::Space) {