Skip to content

Commit 9f85d5a

Browse files
committed
Working slice, added example
register example Refactoring Fixed issue on top/bot rects, added max scaling for corners Tiling Fixed example artifacts Update Cargo.toml Co-authored-by: Alice Cecile <[email protected]> Example page update Sprite draw mode removed extra type registration Fixed performance regression Rect slicing Code cleanup Applied review suggestions Rebased on main
1 parent a1cd5a1 commit 9f85d5a

File tree

11 files changed

+653
-110
lines changed

11 files changed

+653
-110
lines changed

Cargo.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,26 @@ description = "Renders a sprite"
212212
category = "2D Rendering"
213213
wasm = true
214214

215+
[[example]]
216+
name = "sprite_tile"
217+
path = "examples/2d/sprite_tile.rs"
218+
219+
[package.metadata.example.sprite_tile]
220+
name = "Sprite Tile"
221+
description = "Renders a sprite tiled in a grid"
222+
category = "2D Rendering"
223+
wasm = true
224+
225+
[[example]]
226+
name = "sprite_slice"
227+
path = "examples/2d/sprite_slice.rs"
228+
229+
[package.metadata.example.sprite_slice]
230+
name = "Sprite Slice"
231+
description = "Showcases slicing sprites into sections that can be scaled independently via the 9-patch technique"
232+
category = "2D Rendering"
233+
wasm = true
234+
215235
[[example]]
216236
name = "sprite_flipping"
217237
path = "examples/2d/sprite_flipping.rs"

assets/textures/slice_sprite.png

366 KB
Loading

assets/textures/slice_square.png

13.2 KB
Loading

assets/textures/slice_square_2.png

12.4 KB
Loading

crates/bevy_sprite/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@ mod render;
55
mod sprite;
66
mod texture_atlas;
77
mod texture_atlas_builder;
8+
mod texture_slice;
89

910
pub mod collide_aabb;
1011

1112
pub mod prelude {
1213
#[doc(hidden)]
1314
pub use crate::{
1415
bundle::{SpriteBundle, SpriteSheetBundle},
15-
rect::{BorderRect, Rect},
16-
sprite::Sprite,
16+
sprite::{Sprite, SpriteDrawMode},
1717
texture_atlas::{TextureAtlas, TextureAtlasSprite},
18+
texture_slice::{BorderRect, SliceScaleMode, TextureSlicer},
1819
ColorMaterial, ColorMesh2dBundle, TextureAtlasBuilder,
1920
};
2021
}
@@ -26,6 +27,7 @@ pub use render::*;
2627
pub use sprite::*;
2728
pub use texture_atlas::*;
2829
pub use texture_atlas_builder::*;
30+
pub use texture_slice::*;
2931

3032
use bevy_app::prelude::*;
3133
use bevy_asset::{AddAsset, Assets, HandleUntyped};

crates/bevy_sprite/src/render/mod.rs

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::cmp::Ordering;
22

33
use crate::{
44
texture_atlas::{TextureAtlas, TextureAtlasSprite},
5-
Rect, Sprite, SpriteSlice, SPRITE_SHADER_HANDLE,
5+
Sprite, SpriteDrawMode, TextureSlice, SPRITE_SHADER_HANDLE,
66
};
77
use bevy_asset::{AssetEvent, Assets, Handle, HandleId};
88
use bevy_core_pipeline::{core_2d::Transparent2d, tonemapping::Tonemapping};
@@ -30,7 +30,7 @@ use bevy_render::{
3030
},
3131
Extract,
3232
};
33-
use bevy_transform::components::GlobalTransform;
33+
use bevy_transform::components::{GlobalTransform, Transform};
3434
use bevy_utils::FloatOrd;
3535
use bevy_utils::HashMap;
3636
use bytemuck::{Pod, Zeroable};
@@ -324,7 +324,6 @@ pub fn extract_sprites(
324324
&Sprite,
325325
&GlobalTransform,
326326
&Handle<Image>,
327-
Option<&SpriteSlice>
328327
)>,
329328
>,
330329
atlas_query: Extract<
@@ -338,48 +337,72 @@ pub fn extract_sprites(
338337
>,
339338
) {
340339
extracted_sprites.sprites.clear();
341-
for (entity, visibility, sprite, transform, handle, slice) in sprite_query.iter() {
340+
for (entity, visibility, sprite, transform, handle) in sprite_query.iter() {
342341
if !visibility.is_visible() {
343342
continue;
344343
}
345-
if let Some(slice) = slice {
346-
let image_size = match images.get(handle) {
347-
None => continue,
348-
Some(i) => Vec2::new(
349-
i.texture_descriptor.size.width as f32,
350-
i.texture_descriptor.size.height as f32,
351-
),
352-
};
353-
// TODO: remove
354-
let slice: &SpriteSlice = slice;
355-
//
356-
for rect in slice.slice_rects(image_size) {
357-
extracted_sprites.sprites.alloc().init(ExtractedSprite {
358-
entity,
359-
color: sprite.color,
360-
transform: *transform,
361-
rect: Some(rect),
362-
// Pass the custom size
363-
custom_size: None,
364-
flip_x: sprite.flip_x,
365-
flip_y: sprite.flip_y,
366-
image_handle_id: handle.id,
367-
anchor: sprite.anchor.as_vec(),
368-
});
369-
}
370-
} else {
344+
if let SpriteDrawMode::Simple = sprite.draw_mode {
371345
// 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
372-
extracted_sprites.sprites.alloc().init(ExtractedSprite {
346+
extracted_sprites.sprites.push(ExtractedSprite {
373347
entity,
374348
color: sprite.color,
375349
transform: *transform,
376350
// Use the full texture
377351
rect: None,
378-
// Pass the custom size
379352
custom_size: sprite.custom_size,
380353
flip_x: sprite.flip_x,
381354
flip_y: sprite.flip_y,
382-
image_handle_id: handle.id,
355+
image_handle_id: handle.id(),
356+
anchor: sprite.anchor.as_vec(),
357+
});
358+
continue;
359+
}
360+
let image_size = match images.get(handle) {
361+
None => continue,
362+
Some(i) => Vec2::new(
363+
i.texture_descriptor.size.width as f32,
364+
i.texture_descriptor.size.height as f32,
365+
),
366+
};
367+
368+
let slices = match &sprite.draw_mode {
369+
SpriteDrawMode::Sliced(slicer) => slicer.compute_slices(
370+
Rect {
371+
min: Vec2::ZERO,
372+
max: image_size,
373+
},
374+
sprite.custom_size,
375+
),
376+
SpriteDrawMode::Tiled {
377+
tile_x,
378+
tile_y,
379+
stretch_value,
380+
} => {
381+
let slice = TextureSlice {
382+
texture_rect: Rect {
383+
min: Vec2::ZERO,
384+
max: image_size,
385+
},
386+
draw_size: sprite.custom_size.unwrap_or(image_size),
387+
offset: Vec2::ZERO,
388+
};
389+
slice.tiled(*stretch_value, (*tile_x, *tile_y))
390+
}
391+
SpriteDrawMode::Simple => unreachable!(),
392+
};
393+
for slice in slices {
394+
let mut transform: GlobalTransform = *transform;
395+
transform =
396+
transform.mul_transform(Transform::from_translation(slice.offset.extend(0.0)));
397+
extracted_sprites.sprites.push(ExtractedSprite {
398+
entity,
399+
color: sprite.color,
400+
transform,
401+
rect: Some(slice.texture_rect),
402+
custom_size: Some(slice.draw_size),
403+
flip_x: sprite.flip_x,
404+
flip_y: sprite.flip_y,
405+
image_handle_id: handle.id(),
383406
anchor: sprite.anchor.as_vec(),
384407
});
385408
}
@@ -575,7 +598,7 @@ pub fn queue_sprites(
575598
};
576599
let mut current_batch_entity = Entity::from_raw(u32::MAX);
577600
let mut current_image_size = Vec2::ZERO;
578-
// Add a phase item for each sprite, and detect when succesive items can be batched.
601+
// Add a phase item for each sprite, and detect when successive items can be batched.
579602
// Spawn an entity with a `SpriteBatch` component for each possible batch.
580603
// Compatible items share the same entity.
581604
// Batches are merged later (in `batch_phase_system()`), so that they can be interrupted

crates/bevy_sprite/src/sprite.rs

Lines changed: 21 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{BorderRect, Rect};
1+
use crate::TextureSlicer;
22
use bevy_ecs::component::Component;
33
use bevy_math::{Rect, Vec2};
44
use bevy_reflect::Reflect;
@@ -21,27 +21,27 @@ pub struct Sprite {
2121
pub rect: Option<Rect>,
2222
/// [`Anchor`] point of the sprite in the world
2323
pub anchor: Anchor,
24+
/// Define how the Sprite scales when its dimensions change
25+
pub draw_mode: SpriteDrawMode,
2426
}
2527

26-
/// Defines how the non corner sections of a [`SpriteSlice`] are scaled.
27-
#[derive(Debug, Copy, Clone, Default, Reflect)]
28-
pub enum SliceScaleMode {
29-
/// The sections will stretch
28+
#[derive(Debug, Default, Clone, Reflect)]
29+
pub enum SpriteDrawMode {
30+
/// The entire texture scales when its dimensions change. This is the default option.
3031
#[default]
31-
Stretch,
32-
/// The sections will repeat
33-
Tile,
34-
}
35-
36-
/// Component for [9-sliced](https://en.wikipedia.org/wiki/9-slice_scaling) sprites.
37-
///
38-
/// When resizing a 9-sliced sprite the corners will remain unscaled while the other sections will be scaled or tiled
39-
#[derive(Component, Debug, Clone, Default, Reflect)]
40-
pub struct SpriteSlice {
41-
/// The sprite borders, defining the 9 sections of the image
42-
pub border: BorderRect,
43-
/// How do the the non corner sections scale
44-
pub scale_mode: SliceScaleMode,
32+
Simple,
33+
/// The texture will be cut in 9 slices, keeping the texture in proportions on resize
34+
Sliced(TextureSlicer),
35+
/// The texture will be repeated if stretched beyond `stretched_value`
36+
Tiled {
37+
/// Should the image repeat horizontally
38+
tile_x: bool,
39+
/// Should the image repeat vertically
40+
tile_y: bool,
41+
/// The texture will repeat when the ratio between the *drawing dimensions* of texture and the
42+
/// *original texture size* are above this value
43+
stretch_value: f32,
44+
},
4545
}
4646

4747
/// How a sprite is positioned relative to its [`Transform`](bevy_transform::components::Transform).
@@ -64,62 +64,9 @@ pub enum Anchor {
6464
Custom(Vec2),
6565
}
6666

67-
impl SpriteSlice {
68-
/// Computes the 9 [`Rect`] and size values for a [`Sprite`] given its `image_size`
69-
pub fn slice_rects(&self, image_size: Vec2) -> [Rect; 9] {
70-
// corners
71-
let bl_corner = Rect {
72-
min: Vec2::ZERO,
73-
max: Vec2::new(self.border.left, self.border.bottom),
74-
};
75-
let br_corner = Rect {
76-
min: Vec2::new(image_size.x - self.border.right, 0.0),
77-
max: Vec2::new(image_size.x, self.border.bottom),
78-
};
79-
let tl_corner = Rect {
80-
min: Vec2::new(0.0, image_size.y - self.border.top),
81-
max: Vec2::new(self.border.left, image_size.y),
82-
};
83-
let tr_corner = Rect {
84-
min: Vec2::new(
85-
image_size.x - self.border.right,
86-
image_size.y - self.border.top,
87-
),
88-
max: Vec2::new(image_size.x, image_size.y),
89-
};
90-
// Sides
91-
let left_side = Rect {
92-
min: Vec2::new(0.0, self.border.bottom),
93-
max: Vec2::new(self.border.left, image_size.y - self.border.top),
94-
};
95-
let right_side = Rect {
96-
min: Vec2::new(image_size.x - self.border.right, self.border.bottom),
97-
max: Vec2::new(image_size.x, image_size.y - self.border.top),
98-
};
99-
let bot_side = Rect {
100-
min: Vec2::new(self.border.left, 0.0),
101-
max: Vec2::new(image_size.x - self.border.right, self.border.bottom),
102-
};
103-
let top_side = Rect {
104-
min: Vec2::new(self.border.left, image_size.y - self.border.top),
105-
max: Vec2::new(image_size.x - self.border.right, image_size.y),
106-
};
107-
// Center
108-
let center = Rect {
109-
min: Vec2::new(self.border.left, self.border.bottom),
110-
max: Vec2::new(
111-
image_size.x - self.border.right,
112-
image_size.y - self.border.top,
113-
),
114-
};
115-
[
116-
bl_corner, br_corner, tl_corner, tr_corner, left_side, right_side, bot_side, top_side,
117-
center,
118-
]
119-
}
120-
}
121-
12267
impl Anchor {
68+
#[inline]
69+
#[must_use]
12370
pub fn as_vec(&self) -> Vec2 {
12471
match self {
12572
Anchor::Center => Vec2::ZERO,

0 commit comments

Comments
 (0)