Skip to content

Commit 5f09d32

Browse files
ManevilleFalice-i-cecilepablo-lua
authored andcommitted
Slicing support for texture atlas (bevyengine#12059)
# Objective Follow up to bevyengine#11600 and bevyengine#10588 bevyengine#11944 made clear that some people want to use slicing with texture atlases ## Changelog * Added support for `TextureAtlas` slicing and tiling. `SpriteSheetBundle` and `AtlasImageBundle` can now use `ImageScaleMode` * Added new `ui_texture_atlas_slice` example using a texture sheet <img width="798" alt="Screenshot 2024-02-23 at 11 58 35" src="https://github.com/bevyengine/bevy/assets/26703856/47a8b764-127c-4a06-893f-181703777501"> --------- Co-authored-by: Alice Cecile <[email protected]> Co-authored-by: Pablo Reinhardt <[email protected]>
1 parent d07da89 commit 5f09d32

File tree

10 files changed

+283
-47
lines changed

10 files changed

+283
-47
lines changed

Cargo.toml

+11
Original file line numberDiff line numberDiff line change
@@ -2521,6 +2521,17 @@ description = "Illustrates how to use 9 Slicing in UI"
25212521
category = "UI (User Interface)"
25222522
wasm = true
25232523

2524+
[[example]]
2525+
name = "ui_texture_atlas_slice"
2526+
path = "examples/ui/ui_texture_atlas_slice.rs"
2527+
doc-scrape-examples = true
2528+
2529+
[package.metadata.example.ui_texture_atlas_slice]
2530+
name = "UI Texture Atlas Slice"
2531+
description = "Illustrates how to use 9 Slicing for TextureAtlases in UI"
2532+
category = "UI (User Interface)"
2533+
wasm = true
2534+
25242535
[[example]]
25252536
name = "viewport_debug"
25262537
path = "examples/ui/viewport_debug.rs"
Loading

crates/bevy_sprite/src/sprite.rs

-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ pub struct Sprite {
3232
}
3333

3434
/// Controls how the image is altered when scaled.
35-
///
36-
/// Note: This is not yet compatible with texture atlases
3735
#[derive(Component, Debug, Clone, Reflect)]
3836
#[reflect(Component)]
3937
pub enum ImageScaleMode {

crates/bevy_sprite/src/texture_slice/computed_slices.rs

+71-24
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{ExtractedSprite, ImageScaleMode, Sprite};
1+
use crate::{ExtractedSprite, ImageScaleMode, Sprite, TextureAtlas, TextureAtlasLayout};
22

33
use super::TextureSlice;
44
use bevy_asset::{AssetEvent, Assets, Handle};
@@ -63,37 +63,55 @@ impl ComputedTextureSlices {
6363
/// will be computed according to the `image_handle` dimensions or the sprite rect.
6464
///
6565
/// Returns `None` if the image asset is not loaded
66+
///
67+
/// # Arguments
68+
///
69+
/// * `sprite` - The sprite component, will be used to find the draw area size
70+
/// * `scale_mode` - The image scaling component
71+
/// * `image_handle` - The texture to slice or tile
72+
/// * `images` - The image assets, use to retrieve the image dimensions
73+
/// * `atlas` - Optional texture atlas, if set the slicing will happen on the matching sub section
74+
/// of the texture
75+
/// * `atlas_layouts` - The atlas layout assets, used to retrieve the texture atlas section rect
6676
#[must_use]
6777
fn compute_sprite_slices(
6878
sprite: &Sprite,
6979
scale_mode: &ImageScaleMode,
7080
image_handle: &Handle<Image>,
7181
images: &Assets<Image>,
82+
atlas: Option<&TextureAtlas>,
83+
atlas_layouts: &Assets<TextureAtlasLayout>,
7284
) -> Option<ComputedTextureSlices> {
73-
let image_size = images.get(image_handle).map(|i| {
74-
Vec2::new(
75-
i.texture_descriptor.size.width as f32,
76-
i.texture_descriptor.size.height as f32,
77-
)
78-
})?;
79-
let slices = match scale_mode {
80-
ImageScaleMode::Sliced(slicer) => slicer.compute_slices(
81-
sprite.rect.unwrap_or(Rect {
85+
let (image_size, texture_rect) = match atlas {
86+
Some(a) => {
87+
let layout = atlas_layouts.get(&a.layout)?;
88+
(
89+
layout.size.as_vec2(),
90+
layout.textures.get(a.index)?.as_rect(),
91+
)
92+
}
93+
None => {
94+
let image = images.get(image_handle)?;
95+
let size = Vec2::new(
96+
image.texture_descriptor.size.width as f32,
97+
image.texture_descriptor.size.height as f32,
98+
);
99+
let rect = sprite.rect.unwrap_or(Rect {
82100
min: Vec2::ZERO,
83-
max: image_size,
84-
}),
85-
sprite.custom_size,
86-
),
101+
max: size,
102+
});
103+
(size, rect)
104+
}
105+
};
106+
let slices = match scale_mode {
107+
ImageScaleMode::Sliced(slicer) => slicer.compute_slices(texture_rect, sprite.custom_size),
87108
ImageScaleMode::Tiled {
88109
tile_x,
89110
tile_y,
90111
stretch_value,
91112
} => {
92113
let slice = TextureSlice {
93-
texture_rect: sprite.rect.unwrap_or(Rect {
94-
min: Vec2::ZERO,
95-
max: image_size,
96-
}),
114+
texture_rect,
97115
draw_size: sprite.custom_size.unwrap_or(image_size),
98116
offset: Vec2::ZERO,
99117
};
@@ -109,7 +127,14 @@ pub(crate) fn compute_slices_on_asset_event(
109127
mut commands: Commands,
110128
mut events: EventReader<AssetEvent<Image>>,
111129
images: Res<Assets<Image>>,
112-
sprites: Query<(Entity, &ImageScaleMode, &Sprite, &Handle<Image>)>,
130+
atlas_layouts: Res<Assets<TextureAtlasLayout>>,
131+
sprites: Query<(
132+
Entity,
133+
&ImageScaleMode,
134+
&Sprite,
135+
&Handle<Image>,
136+
Option<&TextureAtlas>,
137+
)>,
113138
) {
114139
// We store the asset ids of added/modified image assets
115140
let added_handles: HashSet<_> = events
@@ -123,11 +148,18 @@ pub(crate) fn compute_slices_on_asset_event(
123148
return;
124149
}
125150
// We recompute the sprite slices for sprite entities with a matching asset handle id
126-
for (entity, scale_mode, sprite, image_handle) in &sprites {
151+
for (entity, scale_mode, sprite, image_handle, atlas) in &sprites {
127152
if !added_handles.contains(&image_handle.id()) {
128153
continue;
129154
}
130-
if let Some(slices) = compute_sprite_slices(sprite, scale_mode, image_handle, &images) {
155+
if let Some(slices) = compute_sprite_slices(
156+
sprite,
157+
scale_mode,
158+
image_handle,
159+
&images,
160+
atlas,
161+
&atlas_layouts,
162+
) {
131163
commands.entity(entity).insert(slices);
132164
}
133165
}
@@ -138,17 +170,32 @@ pub(crate) fn compute_slices_on_asset_event(
138170
pub(crate) fn compute_slices_on_sprite_change(
139171
mut commands: Commands,
140172
images: Res<Assets<Image>>,
173+
atlas_layouts: Res<Assets<TextureAtlasLayout>>,
141174
changed_sprites: Query<
142-
(Entity, &ImageScaleMode, &Sprite, &Handle<Image>),
175+
(
176+
Entity,
177+
&ImageScaleMode,
178+
&Sprite,
179+
&Handle<Image>,
180+
Option<&TextureAtlas>,
181+
),
143182
Or<(
144183
Changed<ImageScaleMode>,
145184
Changed<Handle<Image>>,
146185
Changed<Sprite>,
186+
Changed<TextureAtlas>,
147187
)>,
148188
>,
149189
) {
150-
for (entity, scale_mode, sprite, image_handle) in &changed_sprites {
151-
if let Some(slices) = compute_sprite_slices(sprite, scale_mode, image_handle, &images) {
190+
for (entity, scale_mode, sprite, image_handle, atlas) in &changed_sprites {
191+
if let Some(slices) = compute_sprite_slices(
192+
sprite,
193+
scale_mode,
194+
image_handle,
195+
&images,
196+
atlas,
197+
&atlas_layouts,
198+
) {
152199
commands.entity(entity).insert(slices);
153200
}
154201
}

crates/bevy_sprite/src/texture_slice/slicer.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ impl TextureSlicer {
7272
TextureSlice {
7373
texture_rect: Rect {
7474
min: vec2(base_rect.max.x - right, base_rect.min.y),
75-
max: vec2(base_rect.max.x, top),
75+
max: vec2(base_rect.max.x, base_rect.min.y + top),
7676
},
7777
draw_size: vec2(right, top) * min_coef,
7878
offset: vec2(
@@ -198,6 +198,9 @@ impl TextureSlicer {
198198
///
199199
/// * `rect` - The section of the texture to slice in 9 parts
200200
/// * `render_size` - The optional draw size of the texture. If not set the `rect` size will be used.
201+
//
202+
// TODO: Support `URect` and `UVec2` instead (See `https://github.com/bevyengine/bevy/pull/11698`)
203+
//
201204
#[must_use]
202205
pub fn compute_slices(&self, rect: Rect, render_size: Option<Vec2>) -> Vec<TextureSlice> {
203206
let render_size = render_size.unwrap_or_else(|| rect.size());

crates/bevy_ui/src/node_bundles.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ pub struct ImageBundle {
122122

123123
/// A UI node that is a texture atlas sprite
124124
///
125+
/// # Extra behaviours
126+
///
127+
/// You may add the following components to enable additional behaviours
128+
/// - [`ImageScaleMode`](bevy_sprite::ImageScaleMode) to enable either slicing or tiling of the texture
129+
///
125130
/// This bundle is identical to [`ImageBundle`] with an additional [`TextureAtlas`] component.
126131
#[deprecated(
127132
since = "0.14.0",
@@ -295,7 +300,7 @@ where
295300
///
296301
/// You may add the following components to enable additional behaviours:
297302
/// - [`ImageScaleMode`](bevy_sprite::ImageScaleMode) to enable either slicing or tiling of the texture
298-
/// - [`TextureAtlas`] to draw specific sections of the texture
303+
/// - [`TextureAtlas`] to draw specific section of the texture
299304
///
300305
/// Note that `ImageScaleMode` is currently not compatible with `TextureAtlas`.
301306
#[derive(Bundle, Clone, Debug)]

crates/bevy_ui/src/texture_slice.rs

+74-18
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use bevy_asset::{AssetEvent, Assets};
66
use bevy_ecs::prelude::*;
77
use bevy_math::{Rect, Vec2};
88
use bevy_render::texture::Image;
9-
use bevy_sprite::{ImageScaleMode, TextureSlice};
9+
use bevy_sprite::{ImageScaleMode, TextureAtlas, TextureAtlasLayout, TextureSlice};
1010
use bevy_transform::prelude::*;
1111
use bevy_utils::HashSet;
1212

@@ -74,25 +74,48 @@ impl ComputedTextureSlices {
7474
}
7575

7676
/// Generates sprite slices for a `sprite` given a `scale_mode`. The slices
77-
/// will be computed according to the `image_handle` dimensions or the sprite rect.
77+
/// will be computed according to the `image_handle` dimensions.
7878
///
7979
/// Returns `None` if the image asset is not loaded
80+
///
81+
/// # Arguments
82+
///
83+
/// * `draw_area` - The size of the drawing area the slices will have to fit into
84+
/// * `scale_mode` - The image scaling component
85+
/// * `image_handle` - The texture to slice or tile
86+
/// * `images` - The image assets, use to retrieve the image dimensions
87+
/// * `atlas` - Optional texture atlas, if set the slicing will happen on the matching sub section
88+
/// of the texture
89+
/// * `atlas_layouts` - The atlas layout assets, used to retrieve the texture atlas section rect
8090
#[must_use]
8191
fn compute_texture_slices(
8292
draw_area: Vec2,
8393
scale_mode: &ImageScaleMode,
8494
image_handle: &UiImage,
8595
images: &Assets<Image>,
96+
atlas: Option<&TextureAtlas>,
97+
atlas_layouts: &Assets<TextureAtlasLayout>,
8698
) -> Option<ComputedTextureSlices> {
87-
let image_size = images.get(&image_handle.texture).map(|i| {
88-
Vec2::new(
89-
i.texture_descriptor.size.width as f32,
90-
i.texture_descriptor.size.height as f32,
91-
)
92-
})?;
93-
let texture_rect = Rect {
94-
min: Vec2::ZERO,
95-
max: image_size,
99+
let (image_size, texture_rect) = match atlas {
100+
Some(a) => {
101+
let layout = atlas_layouts.get(&a.layout)?;
102+
(
103+
layout.size.as_vec2(),
104+
layout.textures.get(a.index)?.as_rect(),
105+
)
106+
}
107+
None => {
108+
let image = images.get(&image_handle.texture)?;
109+
let size = Vec2::new(
110+
image.texture_descriptor.size.width as f32,
111+
image.texture_descriptor.size.height as f32,
112+
);
113+
let rect = Rect {
114+
min: Vec2::ZERO,
115+
max: size,
116+
};
117+
(size, rect)
118+
}
96119
};
97120
let slices = match scale_mode {
98121
ImageScaleMode::Sliced(slicer) => slicer.compute_slices(texture_rect, Some(draw_area)),
@@ -118,7 +141,14 @@ pub(crate) fn compute_slices_on_asset_event(
118141
mut commands: Commands,
119142
mut events: EventReader<AssetEvent<Image>>,
120143
images: Res<Assets<Image>>,
121-
ui_nodes: Query<(Entity, &ImageScaleMode, &Node, &UiImage)>,
144+
atlas_layouts: Res<Assets<TextureAtlasLayout>>,
145+
ui_nodes: Query<(
146+
Entity,
147+
&ImageScaleMode,
148+
&Node,
149+
&UiImage,
150+
Option<&TextureAtlas>,
151+
)>,
122152
) {
123153
// We store the asset ids of added/modified image assets
124154
let added_handles: HashSet<_> = events
@@ -132,11 +162,18 @@ pub(crate) fn compute_slices_on_asset_event(
132162
return;
133163
}
134164
// We recompute the sprite slices for sprite entities with a matching asset handle id
135-
for (entity, scale_mode, ui_node, image) in &ui_nodes {
165+
for (entity, scale_mode, ui_node, image, atlas) in &ui_nodes {
136166
if !added_handles.contains(&image.texture.id()) {
137167
continue;
138168
}
139-
if let Some(slices) = compute_texture_slices(ui_node.size(), scale_mode, image, &images) {
169+
if let Some(slices) = compute_texture_slices(
170+
ui_node.size(),
171+
scale_mode,
172+
image,
173+
&images,
174+
atlas,
175+
&atlas_layouts,
176+
) {
140177
commands.entity(entity).insert(slices);
141178
}
142179
}
@@ -147,13 +184,32 @@ pub(crate) fn compute_slices_on_asset_event(
147184
pub(crate) fn compute_slices_on_image_change(
148185
mut commands: Commands,
149186
images: Res<Assets<Image>>,
187+
atlas_layouts: Res<Assets<TextureAtlasLayout>>,
150188
changed_nodes: Query<
151-
(Entity, &ImageScaleMode, &Node, &UiImage),
152-
Or<(Changed<ImageScaleMode>, Changed<UiImage>, Changed<Node>)>,
189+
(
190+
Entity,
191+
&ImageScaleMode,
192+
&Node,
193+
&UiImage,
194+
Option<&TextureAtlas>,
195+
),
196+
Or<(
197+
Changed<ImageScaleMode>,
198+
Changed<UiImage>,
199+
Changed<Node>,
200+
Changed<TextureAtlas>,
201+
)>,
153202
>,
154203
) {
155-
for (entity, scale_mode, ui_node, image) in &changed_nodes {
156-
if let Some(slices) = compute_texture_slices(ui_node.size(), scale_mode, image, &images) {
204+
for (entity, scale_mode, ui_node, image, atlas) in &changed_nodes {
205+
if let Some(slices) = compute_texture_slices(
206+
ui_node.size(),
207+
scale_mode,
208+
image,
209+
&images,
210+
atlas,
211+
&atlas_layouts,
212+
) {
157213
commands.entity(entity).insert(slices);
158214
}
159215
}

examples/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ Example | Description
406406
[UI Material](../examples/ui/ui_material.rs) | Demonstrates creating and using custom Ui materials
407407
[UI Scaling](../examples/ui/ui_scaling.rs) | Illustrates how to scale the UI
408408
[UI Texture Atlas](../examples/ui/ui_texture_atlas.rs) | Illustrates how to use TextureAtlases in UI
409+
[UI Texture Atlas Slice](../examples/ui/ui_texture_atlas_slice.rs) | Illustrates how to use 9 Slicing for TextureAtlases in UI
409410
[UI Texture Slice](../examples/ui/ui_texture_slice.rs) | Illustrates how to use 9 Slicing in UI
410411
[UI Z-Index](../examples/ui/z_index.rs) | Demonstrates how to control the relative depth (z-position) of UI elements
411412
[Viewport Debug](../examples/ui/viewport_debug.rs) | An example for debugging viewport coordinates

0 commit comments

Comments
 (0)