bevy/crates/bevy_sprite/src/sprite.rs
Félix Lescaudey de Maneville 01139b3472
Sprite slicing and tiling (#10588)
> Replaces #5213

# Objective

Implement sprite tiling and [9 slice
scaling](https://en.wikipedia.org/wiki/9-slice_scaling) for
`bevy_sprite`.
Allowing slice scaling and texture tiling.

Basic scaling vs 9 slice scaling:


![Traditional_scaling_vs_9-slice_scaling](https://user-images.githubusercontent.com/26703856/177335801-27f6fa27-c569-4ce6-b0e6-4f54e8f4e80a.svg)

Slicing example:

<img width="481" alt="Screenshot 2022-07-05 at 15 05 49"
src="https://user-images.githubusercontent.com/26703856/177336112-9e961af0-c0af-4197-aec9-430c1170a79d.png">

Tiling example:

<img width="1329" alt="Screenshot 2023-11-16 at 13 53 32"
src="https://github.com/bevyengine/bevy/assets/26703856/14db39b7-d9e0-4bc3-ba0e-b1f2db39ae8f">

# Solution

- `SpriteBundlue` now has a `scale_mode` component storing a
`SpriteScaleMode` enum with three variants:
  - `Stretched` (default) 
  - `Tiled` to have sprites tile horizontally and/or vertically
- `Sliced` allowing 9 slicing the texture and optionally tile some
sections with a `Textureslicer`.
- `bevy_sprite` has two extra systems to compute a
`ComputedTextureSlices` if necessary,:
- One system react to changes on `Sprite`, `Handle<Image>` or
`SpriteScaleMode`
- The other listens to `AssetEvent<Image>` to compute slices on sprites
when the texture is ready or changed
- I updated the `bevy_sprite` extraction stage to extract potentially
multiple textures instead of one, depending on the presence of
`ComputedTextureSlices`
- I added two examples showcasing the slicing and tiling feature.

The addition of `ComputedTextureSlices` as a cache is to avoid querying
the image data, to retrieve its dimensions, every frame in a extract or
prepare stage. Also it reacts to changes so we can have stuff like this
(tiling example):


https://github.com/bevyengine/bevy/assets/26703856/a349a9f3-33c3-471f-8ef4-a0e5dfce3b01

# Related 

- [ ] Once #5103 or #10099 is merged I can enable tiling and slicing for
texture sheets as ui

# To discuss

There is an other option, to consider slice/tiling as part of the asset,
using the new asset preprocessing but I have no clue on how to do it.

Also, instead of retrieving the Image dimensions, we could use the same
system as the sprite sheet and have the user give the image dimensions
directly (grid). But I think it's less user friendly

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
Co-authored-by: Alice Cecile <alice.i.cecil@gmail.com>
2024-01-15 15:40:06 +00:00

88 lines
3.1 KiB
Rust

use bevy_ecs::{component::Component, reflect::ReflectComponent};
use bevy_math::{Rect, Vec2};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::color::Color;
use crate::TextureSlicer;
/// Specifies the rendering properties of a sprite.
///
/// This is commonly used as a component within [`SpriteBundle`](crate::bundle::SpriteBundle).
#[derive(Component, Debug, Default, Clone, Reflect)]
#[reflect(Component, Default)]
#[repr(C)]
pub struct Sprite {
/// The sprite's color tint
pub color: Color,
/// Flip the sprite along the `X` axis
pub flip_x: bool,
/// Flip the sprite along 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
pub custom_size: Option<Vec2>,
/// An optional rectangle representing the region of the sprite's image to render, instead of
/// rendering the full image. This is an easy one-off alternative to using a texture atlas.
pub rect: Option<Rect>,
/// [`Anchor`] point of the sprite in the world
pub anchor: Anchor,
}
/// Controls how the image is altered when scaled.
#[derive(Component, Debug, Default, Clone, Reflect)]
#[reflect(Component, Default)]
pub enum ImageScaleMode {
/// The entire texture stretches when its dimensions change. This is the default option.
#[default]
Stretched,
/// The texture will be cut in 9 slices, keeping the texture in proportions on resize
Sliced(TextureSlicer),
/// The texture will be repeated if stretched beyond `stretched_value`
Tiled {
/// Should the image repeat horizontally
tile_x: bool,
/// Should the image repeat vertically
tile_y: bool,
/// The texture will repeat when the ratio between the *drawing dimensions* of texture and the
/// *original texture size* are above this value.
stretch_value: f32,
},
}
/// How a sprite is positioned relative to its [`Transform`](bevy_transform::components::Transform).
/// It defaults to `Anchor::Center`.
#[derive(Component, Debug, Clone, Copy, PartialEq, Default, Reflect)]
#[doc(alias = "pivot")]
pub enum Anchor {
#[default]
Center,
BottomLeft,
BottomCenter,
BottomRight,
CenterLeft,
CenterRight,
TopLeft,
TopCenter,
TopRight,
/// Custom anchor point. Top left is `(-0.5, 0.5)`, center is `(0.0, 0.0)`. The value will
/// be scaled with the sprite size.
Custom(Vec2),
}
impl Anchor {
pub fn as_vec(&self) -> Vec2 {
match self {
Anchor::Center => Vec2::ZERO,
Anchor::BottomLeft => Vec2::new(-0.5, -0.5),
Anchor::BottomCenter => Vec2::new(0.0, -0.5),
Anchor::BottomRight => Vec2::new(0.5, -0.5),
Anchor::CenterLeft => Vec2::new(-0.5, 0.0),
Anchor::CenterRight => Vec2::new(0.5, 0.0),
Anchor::TopLeft => Vec2::new(-0.5, 0.5),
Anchor::TopCenter => Vec2::new(0.0, 0.5),
Anchor::TopRight => Vec2::new(0.5, 0.5),
Anchor::Custom(point) => *point,
}
}
}