diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 4b2d206420..6ac9742443 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -169,9 +169,9 @@ pub fn calculate_bounds_2d( atlases: Res>, meshes_without_aabb: Query<(Entity, &Mesh2d), (Without, Without)>, sprites_to_recalculate_aabb: Query< - (Entity, &Sprite), + (Entity, &Sprite, &Anchor), ( - Or<(Without, Changed)>, + Or<(Without, Changed, Changed)>, Without, ), >, @@ -183,7 +183,7 @@ pub fn calculate_bounds_2d( } } } - for (entity, sprite) in &sprites_to_recalculate_aabb { + for (entity, sprite, anchor) in &sprites_to_recalculate_aabb { if let Some(size) = sprite .custom_size .or_else(|| sprite.rect.map(|rect| rect.size())) @@ -197,7 +197,7 @@ pub fn calculate_bounds_2d( }) { let aabb = Aabb { - center: (-sprite.anchor.as_vec() * size).extend(0.0).into(), + center: (-anchor.as_vec() * size).extend(0.0).into(), half_extents: (0.5 * size).extend(0.0).into(), }; commands.entity(entity).try_insert(aabb); @@ -334,12 +334,14 @@ mod test { // Add entities let entity = app .world_mut() - .spawn(Sprite { - rect: Some(Rect::new(0., 0., 0.5, 1.)), - anchor: Anchor::TOP_RIGHT, - image: image_handle, - ..default() - }) + .spawn(( + Sprite { + rect: Some(Rect::new(0., 0., 0.5, 1.)), + image: image_handle, + ..default() + }, + Anchor::TOP_RIGHT, + )) .id(); // Create AABB diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index 56579c9c0a..57c1acc6bd 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -10,7 +10,7 @@ //! - The `position` reported in `HitData` in in world space, and the `normal` is a normalized //! vector provided by the target's `GlobalTransform::back()`. -use crate::Sprite; +use crate::{Anchor, Sprite}; use bevy_app::prelude::*; use bevy_asset::prelude::*; use bevy_color::Alpha; @@ -100,6 +100,7 @@ fn sprite_picking( Entity, &Sprite, &GlobalTransform, + &Anchor, &Pickable, &ViewVisibility, )>, @@ -107,9 +108,9 @@ fn sprite_picking( ) { let mut sorted_sprites: Vec<_> = sprite_query .iter() - .filter_map(|(entity, sprite, transform, pickable, vis)| { + .filter_map(|(entity, sprite, transform, anchor, pickable, vis)| { if !transform.affine().is_nan() && vis.get() { - Some((entity, sprite, transform, pickable)) + Some((entity, sprite, transform, anchor, pickable)) } else { None } @@ -117,7 +118,7 @@ fn sprite_picking( .collect(); // radsort is a stable radix sort that performed better than `slice::sort_by_key` - radsort::sort_by_key(&mut sorted_sprites, |(_, _, transform, _)| { + radsort::sort_by_key(&mut sorted_sprites, |(_, _, transform, _, _)| { -transform.translation().z }); @@ -159,7 +160,7 @@ fn sprite_picking( let picks: Vec<(Entity, HitData)> = sorted_sprites .iter() .copied() - .filter_map(|(entity, sprite, sprite_transform, pickable)| { + .filter_map(|(entity, sprite, sprite_transform, anchor, pickable)| { if blocked { return None; } @@ -192,6 +193,7 @@ fn sprite_picking( let Ok(cursor_pixel_space) = sprite.compute_pixel_space_point( cursor_pos_sprite, + *anchor, &images, &texture_atlas_layout, ) else { diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index de57f43536..a81f9048f9 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -1,6 +1,6 @@ use core::ops::Range; -use crate::{ComputedTextureSlices, ScalingMode, Sprite, SPRITE_SHADER_HANDLE}; +use crate::{Anchor, ComputedTextureSlices, ScalingMode, Sprite, SPRITE_SHADER_HANDLE}; use bevy_asset::{AssetEvent, AssetId, Assets}; use bevy_color::{ColorToComponents, LinearRgba}; use bevy_core_pipeline::{ @@ -394,13 +394,14 @@ pub fn extract_sprites( &ViewVisibility, &Sprite, &GlobalTransform, + &Anchor, Option<&ComputedTextureSlices>, )>, >, ) { extracted_sprites.sprites.clear(); extracted_slices.slices.clear(); - for (main_entity, render_entity, view_visibility, sprite, transform, slices) in + for (main_entity, render_entity, view_visibility, sprite, transform, anchor, slices) in sprite_query.iter() { if !view_visibility.get() { @@ -411,7 +412,7 @@ pub fn extract_sprites( let start = extracted_slices.slices.len(); extracted_slices .slices - .extend(slices.extract_slices(sprite)); + .extend(slices.extract_slices(sprite, anchor.as_vec())); let end = extracted_slices.slices.len(); extracted_sprites.sprites.push(ExtractedSprite { main_entity, @@ -451,7 +452,7 @@ pub fn extract_sprites( flip_y: sprite.flip_y, image_handle_id: sprite.image.id(), kind: ExtractedSpriteKind::Single { - anchor: sprite.anchor.as_vec(), + anchor: anchor.as_vec(), rect, scaling_mode: sprite.image_mode.scale(), // Pass the custom size diff --git a/crates/bevy_sprite/src/sprite.rs b/crates/bevy_sprite/src/sprite.rs index 32b8ebb49e..61461ab640 100644 --- a/crates/bevy_sprite/src/sprite.rs +++ b/crates/bevy_sprite/src/sprite.rs @@ -15,7 +15,7 @@ use crate::TextureSlicer; /// Describes a sprite to be rendered to a 2D camera #[derive(Component, Debug, Default, Clone, Reflect)] -#[require(Transform, Visibility, SyncToRenderWorld, VisibilityClass)] +#[require(Transform, Visibility, SyncToRenderWorld, VisibilityClass, Anchor)] #[reflect(Component, Default, Debug, Clone)] #[component(on_add = view::add_visibility_class::)] pub struct Sprite { @@ -38,8 +38,6 @@ pub struct Sprite { /// When used with a [`TextureAtlas`], the rect /// is offset by the atlas's minimal (top-left) corner position. pub rect: Option, - /// [`Anchor`] point of the sprite in the world - pub anchor: Anchor, /// How the sprite's image will be scaled. pub image_mode: SpriteImageMode, } @@ -86,6 +84,7 @@ impl Sprite { pub fn compute_pixel_space_point( &self, point_relative_to_sprite: Vec2, + anchor: Anchor, images: &Assets, texture_atlases: &Assets, ) -> Result { @@ -112,7 +111,7 @@ impl Sprite { }; let sprite_size = self.custom_size.unwrap_or_else(|| texture_rect.size()); - let sprite_center = -self.anchor.as_vec() * sprite_size; + let sprite_center = -anchor.as_vec() * sprite_size; let mut point_relative_to_sprite_center = point_relative_to_sprite - sprite_center; @@ -315,8 +314,14 @@ mod tests { ..Default::default() }; - let compute = - |point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); + let compute = |point| { + sprite.compute_pixel_space_point( + point, + Anchor::default(), + &image_assets, + &texture_atlas_assets, + ) + }; assert_eq!(compute(Vec2::new(-2.0, -4.5)), Ok(Vec2::new(0.5, 9.5))); assert_eq!(compute(Vec2::new(0.0, 0.0)), Ok(Vec2::new(2.5, 5.0))); assert_eq!(compute(Vec2::new(0.0, 4.5)), Ok(Vec2::new(2.5, 0.5))); @@ -334,7 +339,12 @@ mod tests { let compute = |point| { sprite - .compute_pixel_space_point(point, &image_assets, &texture_atlas_assets) + .compute_pixel_space_point( + point, + Anchor::default(), + &image_assets, + &texture_atlas_assets, + ) // Round to remove floating point errors. .map(|x| (x * 1e5).round() / 1e5) .map_err(|x| (x * 1e5).round() / 1e5) @@ -355,12 +365,13 @@ mod tests { let sprite = Sprite { image, - anchor: Anchor::BOTTOM_LEFT, ..Default::default() }; + let anchor = Anchor::BOTTOM_LEFT; - let compute = - |point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); + let compute = |point| { + sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets) + }; assert_eq!(compute(Vec2::new(0.5, 9.5)), Ok(Vec2::new(0.5, 0.5))); assert_eq!(compute(Vec2::new(2.5, 5.0)), Ok(Vec2::new(2.5, 5.0))); assert_eq!(compute(Vec2::new(2.5, 9.5)), Ok(Vec2::new(2.5, 0.5))); @@ -377,12 +388,13 @@ mod tests { let sprite = Sprite { image, - anchor: Anchor::TOP_RIGHT, ..Default::default() }; + let anchor = Anchor::TOP_RIGHT; - let compute = - |point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); + let compute = |point| { + sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets) + }; assert_eq!(compute(Vec2::new(-4.5, -0.5)), Ok(Vec2::new(0.5, 0.5))); assert_eq!(compute(Vec2::new(-2.5, -5.0)), Ok(Vec2::new(2.5, 5.0))); assert_eq!(compute(Vec2::new(-2.5, -0.5)), Ok(Vec2::new(2.5, 0.5))); @@ -399,13 +411,14 @@ mod tests { let sprite = Sprite { image, - anchor: Anchor::BOTTOM_LEFT, flip_x: true, ..Default::default() }; + let anchor = Anchor::BOTTOM_LEFT; - let compute = - |point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); + let compute = |point| { + sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets) + }; assert_eq!(compute(Vec2::new(0.5, 9.5)), Ok(Vec2::new(4.5, 0.5))); assert_eq!(compute(Vec2::new(2.5, 5.0)), Ok(Vec2::new(2.5, 5.0))); assert_eq!(compute(Vec2::new(2.5, 9.5)), Ok(Vec2::new(2.5, 0.5))); @@ -422,13 +435,14 @@ mod tests { let sprite = Sprite { image, - anchor: Anchor::TOP_RIGHT, flip_y: true, ..Default::default() }; + let anchor = Anchor::TOP_RIGHT; - let compute = - |point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); + let compute = |point| { + sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets) + }; assert_eq!(compute(Vec2::new(-4.5, -0.5)), Ok(Vec2::new(0.5, 9.5))); assert_eq!(compute(Vec2::new(-2.5, -5.0)), Ok(Vec2::new(2.5, 5.0))); assert_eq!(compute(Vec2::new(-2.5, -0.5)), Ok(Vec2::new(2.5, 9.5))); @@ -446,12 +460,13 @@ mod tests { let sprite = Sprite { image, rect: Some(Rect::new(1.5, 3.0, 3.0, 9.5)), - anchor: Anchor::BOTTOM_LEFT, ..Default::default() }; + let anchor = Anchor::BOTTOM_LEFT; - let compute = - |point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); + let compute = |point| { + sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets) + }; assert_eq!(compute(Vec2::new(0.5, 0.5)), Ok(Vec2::new(2.0, 9.0))); // The pixel is outside the rect, but is still a valid pixel in the image. assert_eq!(compute(Vec2::new(2.0, 2.5)), Err(Vec2::new(3.5, 7.0))); @@ -470,16 +485,17 @@ mod tests { let sprite = Sprite { image, - anchor: Anchor::BOTTOM_LEFT, texture_atlas: Some(TextureAtlas { layout: texture_atlas, index: 0, }), ..Default::default() }; + let anchor = Anchor::BOTTOM_LEFT; - let compute = - |point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); + let compute = |point| { + sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets) + }; assert_eq!(compute(Vec2::new(0.5, 0.5)), Ok(Vec2::new(1.5, 3.5))); // The pixel is outside the texture atlas, but is still a valid pixel in the image. assert_eq!(compute(Vec2::new(4.0, 2.5)), Err(Vec2::new(5.0, 1.5))); @@ -498,7 +514,6 @@ mod tests { let sprite = Sprite { image, - anchor: Anchor::BOTTOM_LEFT, texture_atlas: Some(TextureAtlas { layout: texture_atlas, index: 0, @@ -507,9 +522,11 @@ mod tests { rect: Some(Rect::new(1.5, 1.5, 3.0, 3.0)), ..Default::default() }; + let anchor = Anchor::BOTTOM_LEFT; - let compute = - |point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); + let compute = |point| { + sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets) + }; assert_eq!(compute(Vec2::new(0.5, 0.5)), Ok(Vec2::new(3.0, 3.5))); // The pixel is outside the texture atlas, but is still a valid pixel in the image. assert_eq!(compute(Vec2::new(4.0, 2.5)), Err(Vec2::new(6.5, 1.5))); @@ -529,8 +546,14 @@ mod tests { ..Default::default() }; - let compute = - |point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); + let compute = |point| { + sprite.compute_pixel_space_point( + point, + Anchor::default(), + &image_assets, + &texture_atlas_assets, + ) + }; assert_eq!(compute(Vec2::new(30.0, 15.0)), Ok(Vec2::new(4.0, 1.0))); assert_eq!(compute(Vec2::new(-10.0, -15.0)), Ok(Vec2::new(2.0, 4.0))); // The pixel is outside the texture atlas, but is still a valid pixel in the image. diff --git a/crates/bevy_sprite/src/texture_slice/computed_slices.rs b/crates/bevy_sprite/src/texture_slice/computed_slices.rs index f36cf4bfac..d4972f0384 100644 --- a/crates/bevy_sprite/src/texture_slice/computed_slices.rs +++ b/crates/bevy_sprite/src/texture_slice/computed_slices.rs @@ -1,6 +1,5 @@ -use crate::{ExtractedSlice, Sprite, SpriteImageMode, TextureAtlasLayout}; - use super::TextureSlice; +use crate::{ExtractedSlice, Sprite, SpriteImageMode, TextureAtlasLayout}; use bevy_asset::{AssetEvent, Assets}; use bevy_ecs::prelude::*; use bevy_image::Image; @@ -23,6 +22,7 @@ impl ComputedTextureSlices { pub(crate) fn extract_slices<'a>( &'a self, sprite: &'a Sprite, + anchor: Vec2, ) -> impl ExactSizeIterator + 'a { let mut flip = Vec2::ONE; if sprite.flip_x { @@ -31,7 +31,7 @@ impl ComputedTextureSlices { if sprite.flip_y { flip.y *= -1.0; } - let anchor = sprite.anchor.as_vec() + let anchor = anchor * sprite .custom_size .unwrap_or(sprite.rect.unwrap_or_default().size()); diff --git a/examples/picking/sprite_picking.rs b/examples/picking/sprite_picking.rs index dea1435ac7..83ff5f3bd1 100644 --- a/examples/picking/sprite_picking.rs +++ b/examples/picking/sprite_picking.rs @@ -72,9 +72,9 @@ fn setup(mut commands: Commands, asset_server: Res) { image: asset_server.load("branding/bevy_bird_dark.png"), custom_size: Some(sprite_size), color: Color::srgb(1.0, 0.0, 0.0), - anchor: anchor.to_owned(), ..default() }, + anchor.to_owned(), // 3x3 grid of anchor examples by changing transform Transform::from_xyz(i * len - len, j * len - len, 0.0) .with_scale(Vec3::splat(1.0 + (i - 1.0) * 0.2)) diff --git a/examples/testbed/2d.rs b/examples/testbed/2d.rs index 3e76f4a2e3..7d80aae91f 100644 --- a/examples/testbed/2d.rs +++ b/examples/testbed/2d.rs @@ -246,10 +246,10 @@ mod text { Sprite { color: palettes::tailwind::GRAY_900.into(), custom_size: Some(Vec2::new(bounds.width.unwrap(), bounds.height.unwrap())), - anchor, ..Default::default() }, Transform::from_translation(dest - Vec3::Z), + anchor, DespawnOnExitState(super::Scene::Text), )); } @@ -273,12 +273,12 @@ mod sprite { commands.spawn(( Sprite { image: asset_server.load("branding/bevy_logo_dark.png"), - anchor, flip_x, flip_y, color, ..default() }, + anchor, DespawnOnExitState(super::Scene::Sprite), )); } diff --git a/release-content/migration-guides/anchor_is_removed_from_sprite.md b/release-content/migration-guides/anchor_is_removed_from_sprite.md new file mode 100644 index 0000000000..321935deb7 --- /dev/null +++ b/release-content/migration-guides/anchor_is_removed_from_sprite.md @@ -0,0 +1,6 @@ +--- +title: `Anchor` is now a required component on `Sprite` +pull_requests: [18393] +--- + +The `anchor` field has been removed from `Sprite`. Instead the `Anchor` component is now a required component on `Sprite`.