Fix Anchor component inconsistancies (#18393)

## Objective

Fix the misleading 2d anchor API where `Anchor` is a component and
required by `Text2d` but is stored on a field for sprites.

Fixes #18367

## Solution

Remove the `anchor` field from `Sprite` and require `Anchor` instead.

## Migration Guide

The `anchor` field has been removed from `Sprite`. Instead the `Anchor`
component is now a required component on `Sprite`.
This commit is contained in:
ickshonpe 2025-05-21 16:32:04 +01:00 committed by GitHub
parent bf20c630a8
commit 2b59ab8e2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 89 additions and 55 deletions

View File

@ -169,9 +169,9 @@ pub fn calculate_bounds_2d(
atlases: Res<Assets<TextureAtlasLayout>>, atlases: Res<Assets<TextureAtlasLayout>>,
meshes_without_aabb: Query<(Entity, &Mesh2d), (Without<Aabb>, Without<NoFrustumCulling>)>, meshes_without_aabb: Query<(Entity, &Mesh2d), (Without<Aabb>, Without<NoFrustumCulling>)>,
sprites_to_recalculate_aabb: Query< sprites_to_recalculate_aabb: Query<
(Entity, &Sprite), (Entity, &Sprite, &Anchor),
( (
Or<(Without<Aabb>, Changed<Sprite>)>, Or<(Without<Aabb>, Changed<Sprite>, Changed<Anchor>)>,
Without<NoFrustumCulling>, Without<NoFrustumCulling>,
), ),
>, >,
@ -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 if let Some(size) = sprite
.custom_size .custom_size
.or_else(|| sprite.rect.map(|rect| rect.size())) .or_else(|| sprite.rect.map(|rect| rect.size()))
@ -197,7 +197,7 @@ pub fn calculate_bounds_2d(
}) })
{ {
let aabb = Aabb { 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(), half_extents: (0.5 * size).extend(0.0).into(),
}; };
commands.entity(entity).try_insert(aabb); commands.entity(entity).try_insert(aabb);
@ -334,12 +334,14 @@ mod test {
// Add entities // Add entities
let entity = app let entity = app
.world_mut() .world_mut()
.spawn(Sprite { .spawn((
rect: Some(Rect::new(0., 0., 0.5, 1.)), Sprite {
anchor: Anchor::TOP_RIGHT, rect: Some(Rect::new(0., 0., 0.5, 1.)),
image: image_handle, image: image_handle,
..default() ..default()
}) },
Anchor::TOP_RIGHT,
))
.id(); .id();
// Create AABB // Create AABB

View File

@ -10,7 +10,7 @@
//! - The `position` reported in `HitData` in in world space, and the `normal` is a normalized //! - The `position` reported in `HitData` in in world space, and the `normal` is a normalized
//! vector provided by the target's `GlobalTransform::back()`. //! vector provided by the target's `GlobalTransform::back()`.
use crate::Sprite; use crate::{Anchor, Sprite};
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_asset::prelude::*; use bevy_asset::prelude::*;
use bevy_color::Alpha; use bevy_color::Alpha;
@ -100,6 +100,7 @@ fn sprite_picking(
Entity, Entity,
&Sprite, &Sprite,
&GlobalTransform, &GlobalTransform,
&Anchor,
&Pickable, &Pickable,
&ViewVisibility, &ViewVisibility,
)>, )>,
@ -107,9 +108,9 @@ fn sprite_picking(
) { ) {
let mut sorted_sprites: Vec<_> = sprite_query let mut sorted_sprites: Vec<_> = sprite_query
.iter() .iter()
.filter_map(|(entity, sprite, transform, pickable, vis)| { .filter_map(|(entity, sprite, transform, anchor, pickable, vis)| {
if !transform.affine().is_nan() && vis.get() { if !transform.affine().is_nan() && vis.get() {
Some((entity, sprite, transform, pickable)) Some((entity, sprite, transform, anchor, pickable))
} else { } else {
None None
} }
@ -117,7 +118,7 @@ fn sprite_picking(
.collect(); .collect();
// radsort is a stable radix sort that performed better than `slice::sort_by_key` // 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 -transform.translation().z
}); });
@ -159,7 +160,7 @@ fn sprite_picking(
let picks: Vec<(Entity, HitData)> = sorted_sprites let picks: Vec<(Entity, HitData)> = sorted_sprites
.iter() .iter()
.copied() .copied()
.filter_map(|(entity, sprite, sprite_transform, pickable)| { .filter_map(|(entity, sprite, sprite_transform, anchor, pickable)| {
if blocked { if blocked {
return None; return None;
} }
@ -192,6 +193,7 @@ fn sprite_picking(
let Ok(cursor_pixel_space) = sprite.compute_pixel_space_point( let Ok(cursor_pixel_space) = sprite.compute_pixel_space_point(
cursor_pos_sprite, cursor_pos_sprite,
*anchor,
&images, &images,
&texture_atlas_layout, &texture_atlas_layout,
) else { ) else {

View File

@ -1,6 +1,6 @@
use core::ops::Range; 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_asset::{AssetEvent, AssetId, Assets};
use bevy_color::{ColorToComponents, LinearRgba}; use bevy_color::{ColorToComponents, LinearRgba};
use bevy_core_pipeline::{ use bevy_core_pipeline::{
@ -394,13 +394,14 @@ pub fn extract_sprites(
&ViewVisibility, &ViewVisibility,
&Sprite, &Sprite,
&GlobalTransform, &GlobalTransform,
&Anchor,
Option<&ComputedTextureSlices>, Option<&ComputedTextureSlices>,
)>, )>,
>, >,
) { ) {
extracted_sprites.sprites.clear(); extracted_sprites.sprites.clear();
extracted_slices.slices.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() sprite_query.iter()
{ {
if !view_visibility.get() { if !view_visibility.get() {
@ -411,7 +412,7 @@ pub fn extract_sprites(
let start = extracted_slices.slices.len(); let start = extracted_slices.slices.len();
extracted_slices extracted_slices
.slices .slices
.extend(slices.extract_slices(sprite)); .extend(slices.extract_slices(sprite, anchor.as_vec()));
let end = extracted_slices.slices.len(); let end = extracted_slices.slices.len();
extracted_sprites.sprites.push(ExtractedSprite { extracted_sprites.sprites.push(ExtractedSprite {
main_entity, main_entity,
@ -451,7 +452,7 @@ pub fn extract_sprites(
flip_y: sprite.flip_y, flip_y: sprite.flip_y,
image_handle_id: sprite.image.id(), image_handle_id: sprite.image.id(),
kind: ExtractedSpriteKind::Single { kind: ExtractedSpriteKind::Single {
anchor: sprite.anchor.as_vec(), anchor: anchor.as_vec(),
rect, rect,
scaling_mode: sprite.image_mode.scale(), scaling_mode: sprite.image_mode.scale(),
// Pass the custom size // Pass the custom size

View File

@ -15,7 +15,7 @@ use crate::TextureSlicer;
/// Describes a sprite to be rendered to a 2D camera /// Describes a sprite to be rendered to a 2D camera
#[derive(Component, Debug, Default, Clone, Reflect)] #[derive(Component, Debug, Default, Clone, Reflect)]
#[require(Transform, Visibility, SyncToRenderWorld, VisibilityClass)] #[require(Transform, Visibility, SyncToRenderWorld, VisibilityClass, Anchor)]
#[reflect(Component, Default, Debug, Clone)] #[reflect(Component, Default, Debug, Clone)]
#[component(on_add = view::add_visibility_class::<Sprite>)] #[component(on_add = view::add_visibility_class::<Sprite>)]
pub struct Sprite { pub struct Sprite {
@ -38,8 +38,6 @@ pub struct Sprite {
/// When used with a [`TextureAtlas`], the rect /// When used with a [`TextureAtlas`], the rect
/// is offset by the atlas's minimal (top-left) corner position. /// is offset by the atlas's minimal (top-left) corner position.
pub rect: Option<Rect>, pub rect: Option<Rect>,
/// [`Anchor`] point of the sprite in the world
pub anchor: Anchor,
/// How the sprite's image will be scaled. /// How the sprite's image will be scaled.
pub image_mode: SpriteImageMode, pub image_mode: SpriteImageMode,
} }
@ -86,6 +84,7 @@ impl Sprite {
pub fn compute_pixel_space_point( pub fn compute_pixel_space_point(
&self, &self,
point_relative_to_sprite: Vec2, point_relative_to_sprite: Vec2,
anchor: Anchor,
images: &Assets<Image>, images: &Assets<Image>,
texture_atlases: &Assets<TextureAtlasLayout>, texture_atlases: &Assets<TextureAtlasLayout>,
) -> Result<Vec2, Vec2> { ) -> Result<Vec2, Vec2> {
@ -112,7 +111,7 @@ impl Sprite {
}; };
let sprite_size = self.custom_size.unwrap_or_else(|| texture_rect.size()); 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; let mut point_relative_to_sprite_center = point_relative_to_sprite - sprite_center;
@ -315,8 +314,14 @@ mod tests {
..Default::default() ..Default::default()
}; };
let compute = let compute = |point| {
|point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); 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(-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, 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))); 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| { let compute = |point| {
sprite 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. // Round to remove floating point errors.
.map(|x| (x * 1e5).round() / 1e5) .map(|x| (x * 1e5).round() / 1e5)
.map_err(|x| (x * 1e5).round() / 1e5) .map_err(|x| (x * 1e5).round() / 1e5)
@ -355,12 +365,13 @@ mod tests {
let sprite = Sprite { let sprite = Sprite {
image, image,
anchor: Anchor::BOTTOM_LEFT,
..Default::default() ..Default::default()
}; };
let anchor = Anchor::BOTTOM_LEFT;
let compute = let compute = |point| {
|point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); 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(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, 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))); 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 { let sprite = Sprite {
image, image,
anchor: Anchor::TOP_RIGHT,
..Default::default() ..Default::default()
}; };
let anchor = Anchor::TOP_RIGHT;
let compute = let compute = |point| {
|point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); 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(-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, -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))); 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 { let sprite = Sprite {
image, image,
anchor: Anchor::BOTTOM_LEFT,
flip_x: true, flip_x: true,
..Default::default() ..Default::default()
}; };
let anchor = Anchor::BOTTOM_LEFT;
let compute = let compute = |point| {
|point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); 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(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, 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))); 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 { let sprite = Sprite {
image, image,
anchor: Anchor::TOP_RIGHT,
flip_y: true, flip_y: true,
..Default::default() ..Default::default()
}; };
let anchor = Anchor::TOP_RIGHT;
let compute = let compute = |point| {
|point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); 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(-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, -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))); 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 { let sprite = Sprite {
image, image,
rect: Some(Rect::new(1.5, 3.0, 3.0, 9.5)), rect: Some(Rect::new(1.5, 3.0, 3.0, 9.5)),
anchor: Anchor::BOTTOM_LEFT,
..Default::default() ..Default::default()
}; };
let anchor = Anchor::BOTTOM_LEFT;
let compute = let compute = |point| {
|point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); 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))); 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. // 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))); 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 { let sprite = Sprite {
image, image,
anchor: Anchor::BOTTOM_LEFT,
texture_atlas: Some(TextureAtlas { texture_atlas: Some(TextureAtlas {
layout: texture_atlas, layout: texture_atlas,
index: 0, index: 0,
}), }),
..Default::default() ..Default::default()
}; };
let anchor = Anchor::BOTTOM_LEFT;
let compute = let compute = |point| {
|point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); 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))); 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. // 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))); 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 { let sprite = Sprite {
image, image,
anchor: Anchor::BOTTOM_LEFT,
texture_atlas: Some(TextureAtlas { texture_atlas: Some(TextureAtlas {
layout: texture_atlas, layout: texture_atlas,
index: 0, index: 0,
@ -507,9 +522,11 @@ mod tests {
rect: Some(Rect::new(1.5, 1.5, 3.0, 3.0)), rect: Some(Rect::new(1.5, 1.5, 3.0, 3.0)),
..Default::default() ..Default::default()
}; };
let anchor = Anchor::BOTTOM_LEFT;
let compute = let compute = |point| {
|point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); 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))); 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. // 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))); assert_eq!(compute(Vec2::new(4.0, 2.5)), Err(Vec2::new(6.5, 1.5)));
@ -529,8 +546,14 @@ mod tests {
..Default::default() ..Default::default()
}; };
let compute = let compute = |point| {
|point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); 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(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))); 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. // The pixel is outside the texture atlas, but is still a valid pixel in the image.

View File

@ -1,6 +1,5 @@
use crate::{ExtractedSlice, Sprite, SpriteImageMode, TextureAtlasLayout};
use super::TextureSlice; use super::TextureSlice;
use crate::{ExtractedSlice, Sprite, SpriteImageMode, TextureAtlasLayout};
use bevy_asset::{AssetEvent, Assets}; use bevy_asset::{AssetEvent, Assets};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_image::Image; use bevy_image::Image;
@ -23,6 +22,7 @@ impl ComputedTextureSlices {
pub(crate) fn extract_slices<'a>( pub(crate) fn extract_slices<'a>(
&'a self, &'a self,
sprite: &'a Sprite, sprite: &'a Sprite,
anchor: Vec2,
) -> impl ExactSizeIterator<Item = ExtractedSlice> + 'a { ) -> impl ExactSizeIterator<Item = ExtractedSlice> + 'a {
let mut flip = Vec2::ONE; let mut flip = Vec2::ONE;
if sprite.flip_x { if sprite.flip_x {
@ -31,7 +31,7 @@ impl ComputedTextureSlices {
if sprite.flip_y { if sprite.flip_y {
flip.y *= -1.0; flip.y *= -1.0;
} }
let anchor = sprite.anchor.as_vec() let anchor = anchor
* sprite * sprite
.custom_size .custom_size
.unwrap_or(sprite.rect.unwrap_or_default().size()); .unwrap_or(sprite.rect.unwrap_or_default().size());

View File

@ -72,9 +72,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
image: asset_server.load("branding/bevy_bird_dark.png"), image: asset_server.load("branding/bevy_bird_dark.png"),
custom_size: Some(sprite_size), custom_size: Some(sprite_size),
color: Color::srgb(1.0, 0.0, 0.0), color: Color::srgb(1.0, 0.0, 0.0),
anchor: anchor.to_owned(),
..default() ..default()
}, },
anchor.to_owned(),
// 3x3 grid of anchor examples by changing transform // 3x3 grid of anchor examples by changing transform
Transform::from_xyz(i * len - len, j * len - len, 0.0) Transform::from_xyz(i * len - len, j * len - len, 0.0)
.with_scale(Vec3::splat(1.0 + (i - 1.0) * 0.2)) .with_scale(Vec3::splat(1.0 + (i - 1.0) * 0.2))

View File

@ -246,10 +246,10 @@ mod text {
Sprite { Sprite {
color: palettes::tailwind::GRAY_900.into(), color: palettes::tailwind::GRAY_900.into(),
custom_size: Some(Vec2::new(bounds.width.unwrap(), bounds.height.unwrap())), custom_size: Some(Vec2::new(bounds.width.unwrap(), bounds.height.unwrap())),
anchor,
..Default::default() ..Default::default()
}, },
Transform::from_translation(dest - Vec3::Z), Transform::from_translation(dest - Vec3::Z),
anchor,
DespawnOnExitState(super::Scene::Text), DespawnOnExitState(super::Scene::Text),
)); ));
} }
@ -273,12 +273,12 @@ mod sprite {
commands.spawn(( commands.spawn((
Sprite { Sprite {
image: asset_server.load("branding/bevy_logo_dark.png"), image: asset_server.load("branding/bevy_logo_dark.png"),
anchor,
flip_x, flip_x,
flip_y, flip_y,
color, color,
..default() ..default()
}, },
anchor,
DespawnOnExitState(super::Scene::Sprite), DespawnOnExitState(super::Scene::Sprite),
)); ));
} }

View File

@ -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`.