Skip to content

Commit 135c724

Browse files
ManevilleFalice-i-cecilemockersfIceSentry
authored
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](#5103 (comment))) - 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](#5072 (comment))) 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 <[email protected]> Co-authored-by: François <[email protected]> Co-authored-by: IceSentry <[email protected]>
1 parent 9f8db0d commit 135c724

22 files changed

+354
-470
lines changed

crates/bevy_sprite/src/bundle.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
use crate::{
2-
texture_atlas::{TextureAtlas, TextureAtlasSprite},
3-
ImageScaleMode, Sprite,
4-
};
1+
use crate::{texture_atlas::TextureAtlas, ImageScaleMode, Sprite};
52
use bevy_asset::Handle;
63
use bevy_ecs::bundle::Bundle;
74
use bevy_render::{
@@ -32,18 +29,25 @@ pub struct SpriteBundle {
3229
}
3330

3431
/// A [`Bundle`] of components for drawing a single sprite from a sprite sheet (also referred
35-
/// to as a `TextureAtlas`).
32+
/// to as a `TextureAtlas`) or for animated sprites.
33+
///
34+
/// Note:
35+
/// This bundle is identical to [`SpriteBundle`] with an additional [`TextureAtlas`] component.
36+
///
37+
/// Check the following examples for usage:
38+
/// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)
39+
/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
3640
#[derive(Bundle, Clone, Default)]
3741
pub struct SpriteSheetBundle {
38-
/// The specific sprite from the texture atlas to be drawn, defaulting to the sprite at index 0.
39-
pub sprite: TextureAtlasSprite,
42+
pub sprite: Sprite,
4043
/// Controls how the image is altered when scaled.
4144
pub scale_mode: ImageScaleMode,
42-
/// A handle to the texture atlas that holds the sprite images
43-
pub texture_atlas: Handle<TextureAtlas>,
44-
/// Data pertaining to how the sprite is drawn on the screen
4545
pub transform: Transform,
4646
pub global_transform: GlobalTransform,
47+
/// The sprite sheet base texture
48+
pub texture: Handle<Image>,
49+
/// The sprite sheet texture atlas, allowing to draw a custom section of `texture`.
50+
pub atlas: TextureAtlas,
4751
/// User indication of whether an entity is visible
4852
pub visibility: Visibility,
4953
pub inherited_visibility: InheritedVisibility,

crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
use crate::TextureAtlas;
2-
use bevy_asset::Assets;
1+
use crate::TextureAtlasLayout;
2+
use bevy_asset::{Assets, Handle};
33
use bevy_math::{IVec2, Rect, Vec2};
44
use bevy_render::{
55
render_asset::RenderAssetPersistencePolicy,
66
texture::{Image, TextureFormatPixelInfo},
77
};
88
use guillotiere::{size2, Allocation, AtlasAllocator};
99

10-
/// Helper utility to update [`TextureAtlas`] on the fly.
10+
/// Helper utility to update [`TextureAtlasLayout`] on the fly.
1111
///
1212
/// Helpful in cases when texture is created procedurally,
13-
/// e.g: in a font glyph [`TextureAtlas`], only add the [`Image`] texture for letters to be rendered.
13+
/// e.g: in a font glyph [`TextureAtlasLayout`], only add the [`Image`] texture for letters to be rendered.
1414
pub struct DynamicTextureAtlasBuilder {
1515
atlas_allocator: AtlasAllocator,
1616
padding: i32,
@@ -30,22 +30,30 @@ impl DynamicTextureAtlasBuilder {
3030
}
3131
}
3232

33-
/// Add a new texture to [`TextureAtlas`].
34-
/// It is user's responsibility to pass in the correct [`TextureAtlas`],
35-
/// and that [`TextureAtlas::texture`] has [`Image::cpu_persistent_access`]
33+
/// Add a new texture to `atlas_layout`
34+
/// It is the user's responsibility to pass in the correct [`TextureAtlasLayout`]
35+
/// and that `atlas_texture_handle` has [`Image::cpu_persistent_access`]
3636
/// set to [`RenderAssetPersistencePolicy::Keep`]
37+
///
38+
/// # Arguments
39+
///
40+
/// * `altas_layout` - The atlas to add the texture to
41+
/// * `textures` - The texture assets container
42+
/// * `texture` - The new texture to add to the atlas
43+
/// * `atlas_texture_handle` - The atlas texture to edit
3744
pub fn add_texture(
3845
&mut self,
39-
texture_atlas: &mut TextureAtlas,
46+
atlas_layout: &mut TextureAtlasLayout,
4047
textures: &mut Assets<Image>,
4148
texture: &Image,
49+
atlas_texture_handle: &Handle<Image>,
4250
) -> Option<usize> {
4351
let allocation = self.atlas_allocator.allocate(size2(
4452
texture.width() as i32 + self.padding,
4553
texture.height() as i32 + self.padding,
4654
));
4755
if let Some(allocation) = allocation {
48-
let atlas_texture = textures.get_mut(&texture_atlas.texture).unwrap();
56+
let atlas_texture = textures.get_mut(atlas_texture_handle).unwrap();
4957
assert_eq!(
5058
atlas_texture.cpu_persistent_access,
5159
RenderAssetPersistencePolicy::Keep
@@ -54,7 +62,7 @@ impl DynamicTextureAtlasBuilder {
5462
self.place_texture(atlas_texture, allocation, texture);
5563
let mut rect: Rect = to_rect(allocation.rectangle);
5664
rect.max -= self.padding as f32;
57-
Some(texture_atlas.add_texture(rect))
65+
Some(atlas_layout.add_texture(rect))
5866
} else {
5967
None
6068
}

crates/bevy_sprite/src/lib.rs

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub mod prelude {
1515
pub use crate::{
1616
bundle::{SpriteBundle, SpriteSheetBundle},
1717
sprite::{ImageScaleMode, Sprite},
18-
texture_atlas::{TextureAtlas, TextureAtlasSprite},
18+
texture_atlas::{TextureAtlas, TextureAtlasLayout},
1919
texture_slice::{BorderRect, SliceScaleMode, TextureSlicer},
2020
ColorMaterial, ColorMesh2dBundle, TextureAtlasBuilder,
2121
};
@@ -65,13 +65,13 @@ impl Plugin for SpritePlugin {
6565
"render/sprite.wgsl",
6666
Shader::from_wgsl
6767
);
68-
app.init_asset::<TextureAtlas>()
69-
.register_asset_reflect::<TextureAtlas>()
68+
app.init_asset::<TextureAtlasLayout>()
69+
.register_asset_reflect::<TextureAtlasLayout>()
7070
.register_type::<Sprite>()
7171
.register_type::<ImageScaleMode>()
7272
.register_type::<TextureSlicer>()
73-
.register_type::<TextureAtlasSprite>()
7473
.register_type::<Anchor>()
74+
.register_type::<TextureAtlas>()
7575
.register_type::<Mesh2dHandle>()
7676
.add_plugins((Mesh2dRenderPlugin, ColorMaterialPlugin))
7777
.add_systems(
@@ -131,19 +131,15 @@ pub fn calculate_bounds_2d(
131131
mut commands: Commands,
132132
meshes: Res<Assets<Mesh>>,
133133
images: Res<Assets<Image>>,
134-
atlases: Res<Assets<TextureAtlas>>,
134+
atlases: Res<Assets<TextureAtlasLayout>>,
135135
meshes_without_aabb: Query<(Entity, &Mesh2dHandle), (Without<Aabb>, Without<NoFrustumCulling>)>,
136136
sprites_to_recalculate_aabb: Query<
137-
(Entity, &Sprite, &Handle<Image>),
137+
(Entity, &Sprite, &Handle<Image>, Option<&TextureAtlas>),
138138
(
139139
Or<(Without<Aabb>, Changed<Sprite>)>,
140140
Without<NoFrustumCulling>,
141141
),
142142
>,
143-
atlases_without_aabb: Query<
144-
(Entity, &TextureAtlasSprite, &Handle<TextureAtlas>),
145-
(Without<Aabb>, Without<NoFrustumCulling>),
146-
>,
147143
) {
148144
for (entity, mesh_handle) in &meshes_without_aabb {
149145
if let Some(mesh) = meshes.get(&mesh_handle.0) {
@@ -152,27 +148,15 @@ pub fn calculate_bounds_2d(
152148
}
153149
}
154150
}
155-
for (entity, sprite, texture_handle) in &sprites_to_recalculate_aabb {
156-
if let Some(size) = sprite
157-
.custom_size
158-
.or_else(|| images.get(texture_handle).map(|image| image.size_f32()))
159-
{
160-
let aabb = Aabb {
161-
center: (-sprite.anchor.as_vec() * size).extend(0.0).into(),
162-
half_extents: (0.5 * size).extend(0.0).into(),
163-
};
164-
commands.entity(entity).try_insert(aabb);
165-
}
166-
}
167-
for (entity, atlas_sprite, atlas_handle) in &atlases_without_aabb {
168-
if let Some(size) = atlas_sprite.custom_size.or_else(|| {
169-
atlases
170-
.get(atlas_handle)
171-
.and_then(|atlas| atlas.textures.get(atlas_sprite.index))
172-
.map(|rect| (rect.min - rect.max).abs())
151+
for (entity, sprite, texture_handle, atlas) in &sprites_to_recalculate_aabb {
152+
if let Some(size) = sprite.custom_size.or_else(|| match atlas {
153+
// We default to the texture size for regular sprites
154+
None => images.get(texture_handle).map(|image| image.size_f32()),
155+
// We default to the drawn rect for atlas sprites
156+
Some(atlas) => atlas.texture_rect(&atlases).map(|rect| rect.size()),
173157
}) {
174158
let aabb = Aabb {
175-
center: (-atlas_sprite.anchor.as_vec() * size).extend(0.0).into(),
159+
center: (-sprite.anchor.as_vec() * size).extend(0.0).into(),
176160
half_extents: (0.5 * size).extend(0.0).into(),
177161
};
178162
commands.entity(entity).try_insert(aabb);
@@ -199,7 +183,7 @@ mod test {
199183
app.insert_resource(image_assets);
200184
let mesh_assets = Assets::<Mesh>::default();
201185
app.insert_resource(mesh_assets);
202-
let texture_atlas_assets = Assets::<TextureAtlas>::default();
186+
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
203187
app.insert_resource(texture_atlas_assets);
204188

205189
// Add system
@@ -237,7 +221,7 @@ mod test {
237221
app.insert_resource(image_assets);
238222
let mesh_assets = Assets::<Mesh>::default();
239223
app.insert_resource(mesh_assets);
240-
let texture_atlas_assets = Assets::<TextureAtlas>::default();
224+
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
241225
app.insert_resource(texture_atlas_assets);
242226

243227
// Add system

crates/bevy_sprite/src/render/mod.rs

Lines changed: 7 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::ops::Range;
22

33
use crate::{
4-
texture_atlas::{TextureAtlas, TextureAtlasSprite},
4+
texture_atlas::{TextureAtlas, TextureAtlasLayout},
55
ComputedTextureSlices, Sprite, SPRITE_SHADER_HANDLE,
66
};
77
use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
@@ -335,47 +335,40 @@ pub fn extract_sprite_events(
335335
pub fn extract_sprites(
336336
mut commands: Commands,
337337
mut extracted_sprites: ResMut<ExtractedSprites>,
338-
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
338+
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
339339
sprite_query: Extract<
340340
Query<(
341341
Entity,
342342
&ViewVisibility,
343343
&Sprite,
344344
&GlobalTransform,
345345
&Handle<Image>,
346+
Option<&TextureAtlas>,
346347
Option<&ComputedTextureSlices>,
347348
)>,
348349
>,
349-
atlas_query: Extract<
350-
Query<(
351-
Entity,
352-
&ViewVisibility,
353-
&TextureAtlasSprite,
354-
&GlobalTransform,
355-
&Handle<TextureAtlas>,
356-
)>,
357-
>,
358350
) {
359351
extracted_sprites.sprites.clear();
360-
361-
for (entity, view_visibility, sprite, transform, handle, slices) in sprite_query.iter() {
352+
for (entity, view_visibility, sprite, transform, handle, sheet, slices) in sprite_query.iter() {
362353
if !view_visibility.get() {
363354
continue;
364355
}
356+
365357
if let Some(slices) = slices {
366358
extracted_sprites.sprites.extend(
367359
slices
368360
.extract_sprites(transform, entity, sprite, handle)
369361
.map(|e| (commands.spawn_empty().id(), e)),
370362
);
371363
} else {
364+
let rect = sheet.and_then(|s| s.texture_rect(&texture_atlases));
372365
// 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
373366
extracted_sprites.sprites.insert(
374367
entity,
375368
ExtractedSprite {
376369
color: sprite.color,
377370
transform: *transform,
378-
rect: sprite.rect,
371+
rect,
379372
// Pass the custom size
380373
custom_size: sprite.custom_size,
381374
flip_x: sprite.flip_x,
@@ -387,43 +380,6 @@ pub fn extract_sprites(
387380
);
388381
}
389382
}
390-
for (entity, view_visibility, atlas_sprite, transform, texture_atlas_handle) in
391-
atlas_query.iter()
392-
{
393-
if !view_visibility.get() {
394-
continue;
395-
}
396-
if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) {
397-
let rect = Some(
398-
*texture_atlas
399-
.textures
400-
.get(atlas_sprite.index)
401-
.unwrap_or_else(|| {
402-
panic!(
403-
"Sprite index {:?} does not exist for texture atlas handle {:?}.",
404-
atlas_sprite.index,
405-
texture_atlas_handle.id(),
406-
)
407-
}),
408-
);
409-
extracted_sprites.sprites.insert(
410-
entity,
411-
ExtractedSprite {
412-
color: atlas_sprite.color,
413-
transform: *transform,
414-
// Select the area in the texture atlas
415-
rect,
416-
// Pass the custom size
417-
custom_size: atlas_sprite.custom_size,
418-
flip_x: atlas_sprite.flip_x,
419-
flip_y: atlas_sprite.flip_y,
420-
image_handle_id: texture_atlas.texture.id(),
421-
anchor: atlas_sprite.anchor.as_vec(),
422-
original_entity: None,
423-
},
424-
);
425-
}
426-
}
427383
}
428384

429385
#[repr(C)]

0 commit comments

Comments
 (0)