From 7d40e3ec874f91fd877c2c30bcaf152a2092b97b Mon Sep 17 00:00:00 2001 From: Emerson Coskey <56370779+ecoskey@users.noreply.github.com> Date: Wed, 9 Oct 2024 09:17:26 -0700 Subject: [PATCH] Migrate bevy_sprite to required components (#15489) # Objective Continue migration of bevy APIs to required components, following guidance of https://hackmd.io/@bevy/required_components/ ## Solution - Make `Sprite` require `Transform` and `Visibility` and `SyncToRenderWorld` - move image and texture atlas handles into `Sprite` - deprecate `SpriteBundle` - remove engine uses of `SpriteBundle` ## Testing ran cargo tests on bevy_sprite and tested several sprite examples. --- ## Migration Guide Replace all uses of `SpriteBundle` with `Sprite`. There are several new convenience constructors: `Sprite::from_image`, `Sprite::from_atlas_image`, `Sprite::from_color`. WARNING: use of `Handle` and `TextureAtlas` as components on sprite entities will NO LONGER WORK. Use the fields on `Sprite` instead. I would have removed the `Component` impls from `TextureAtlas` and `Handle` except it is still used within ui. We should fix this moving forward with the migration. --- crates/bevy_ecs/src/bundle.rs | 2 - crates/bevy_sprite/src/bundle.rs | 5 + crates/bevy_sprite/src/lib.rs | 15 +- crates/bevy_sprite/src/picking_backend.rs | 157 +++++++++--------- crates/bevy_sprite/src/render/mod.rs | 21 ++- crates/bevy_sprite/src/sprite.rs | 52 +++++- .../bevy_sprite/src/texture_atlas_builder.rs | 5 +- .../src/texture_slice/computed_slices.rs | 60 ++----- examples/2d/bloom_2d.rs | 11 +- examples/2d/cpu_draw.rs | 6 +- examples/2d/move_sprite.rs | 7 +- examples/2d/pixel_grid_snap.rs | 23 +-- examples/2d/rotation.rs | 33 +--- examples/2d/sprite.rs | 7 +- examples/2d/sprite_animation.rs | 56 +++---- examples/2d/sprite_flipping.rs | 17 +- examples/2d/sprite_sheet.rs | 32 ++-- examples/2d/sprite_slice.rs | 11 +- examples/2d/sprite_tile.rs | 5 +- examples/2d/text2d.rs | 26 +-- examples/2d/texture_atlas.rs | 33 ++-- examples/2d/transparency_2d.rs | 27 ++- examples/animation/color_animation.rs | 20 +-- examples/animation/easing_functions.rs | 26 +-- examples/asset/alter_sprite.rs | 17 +- examples/asset/asset_decompression.rs | 19 +-- examples/asset/asset_settings.rs | 43 +++-- examples/asset/custom_asset_reader.rs | 5 +- examples/asset/embedded_asset.rs | 5 +- examples/asset/extra_source.rs | 5 +- examples/audio/spatial_audio_2d.rs | 26 +-- examples/ecs/fallible_params.rs | 24 +-- examples/ecs/hierarchy.rs | 31 ++-- examples/ecs/parallel_query.rs | 7 +- examples/ecs/removal_detection.rs | 5 +- examples/games/breakout.rs | 58 +++---- examples/games/contributors.rs | 15 +- examples/games/desk_toy.rs | 5 +- .../movement/physics_in_fixed_timestep.rs | 7 +- examples/picking/sprite_picking.rs | 45 +++-- .../shader/compute_shader_game_of_life.rs | 11 +- examples/state/computed_states.rs | 5 +- examples/state/custom_transitions.rs | 5 +- examples/state/states.rs | 5 +- examples/state/sub_states.rs | 5 +- examples/stress_tests/bevymark.rs | 8 +- .../stress_tests/many_animated_sprites.rs | 19 +-- examples/stress_tests/many_sprites.rs | 19 +-- examples/time/virtual_time.rs | 26 ++- examples/tools/gamepad_viewer.rs | 38 ++--- examples/window/transparent_window.rs | 5 +- tests/window/minimising.rs | 12 +- tests/window/resizing.rs | 12 +- 53 files changed, 462 insertions(+), 682 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 5075642da6d82..8dacc98a911d7 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -77,8 +77,6 @@ use core::{any::TypeId, ptr::NonNull}; /// Additionally, [Tuples](`tuple`) of bundles are also [`Bundle`] (with up to 15 bundles). /// These bundles contain the items of the 'inner' bundles. /// This is a convenient shorthand which is primarily used when spawning entities. -/// For example, spawning an entity using the bundle `(SpriteBundle {...}, PlayerMarker)` -/// will spawn an entity with components required for a 2d sprite, and the `PlayerMarker` component. /// /// [`unit`], otherwise known as [`()`](`unit`), is a [`Bundle`] containing no components (since it /// can also be considered as the empty tuple). diff --git a/crates/bevy_sprite/src/bundle.rs b/crates/bevy_sprite/src/bundle.rs index 8d4cf1365f5de..fbbdf2efe372c 100644 --- a/crates/bevy_sprite/src/bundle.rs +++ b/crates/bevy_sprite/src/bundle.rs @@ -1,3 +1,4 @@ +#![expect(deprecated)] use crate::Sprite; use bevy_asset::Handle; use bevy_ecs::bundle::Bundle; @@ -16,6 +17,10 @@ use bevy_transform::components::{GlobalTransform, Transform}; /// - [`ImageScaleMode`](crate::ImageScaleMode) to enable either slicing or tiling of the texture /// - [`TextureAtlas`](crate::TextureAtlas) to draw a specific section of the texture #[derive(Bundle, Clone, Debug, Default)] +#[deprecated( + since = "0.15.0", + note = "Use the `Sprite` component instead. Inserting it will now also insert `Transform` and `Visibility` automatically." +)] pub struct SpriteBundle { /// Specifies the rendering properties of the sprite, such as color tint and flip. pub sprite: Sprite, diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index d1067cf6a6915..62634f859910a 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -185,9 +185,9 @@ pub fn calculate_bounds_2d( atlases: Res>, meshes_without_aabb: Query<(Entity, &Mesh2d), (Without, Without)>, sprites_to_recalculate_aabb: Query< - (Entity, &Sprite, &Handle, Option<&TextureAtlas>), + (Entity, &Sprite), ( - Or<(Without, Changed, Changed)>, + Or<(Without, Changed)>, Without, ), >, @@ -199,13 +199,13 @@ pub fn calculate_bounds_2d( } } } - for (entity, sprite, texture_handle, atlas) in &sprites_to_recalculate_aabb { + for (entity, sprite) in &sprites_to_recalculate_aabb { if let Some(size) = sprite .custom_size .or_else(|| sprite.rect.map(|rect| rect.size())) - .or_else(|| match atlas { + .or_else(|| match &sprite.texture_atlas { // We default to the texture size for regular sprites - None => images.get(texture_handle).map(Image::size_f32), + None => images.get(&sprite.image).map(Image::size_f32), // We default to the drawn rect for atlas sprites Some(atlas) => atlas .texture_rect(&atlases) @@ -259,10 +259,7 @@ mod test { app.add_systems(Update, calculate_bounds_2d); // Add entities - let entity = app - .world_mut() - .spawn((Sprite::default(), image_handle)) - .id(); + let entity = app.world_mut().spawn(Sprite::from_image(image_handle)).id(); // Verify that the entity does not have an AABB assert!(!app diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index f9f906536c722..c2f3344b52f1e 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -4,7 +4,7 @@ use core::cmp::Reverse; -use crate::{Sprite, TextureAtlas, TextureAtlasLayout}; +use crate::{Sprite, TextureAtlasLayout}; use bevy_app::prelude::*; use bevy_asset::prelude::*; use bevy_ecs::prelude::*; @@ -32,8 +32,6 @@ pub fn sprite_picking( sprite_query: Query<( Entity, &Sprite, - Option<&TextureAtlas>, - &Handle, &GlobalTransform, Option<&PickingBehavior>, &ViewVisibility, @@ -42,9 +40,9 @@ pub fn sprite_picking( ) { let mut sorted_sprites: Vec<_> = sprite_query .iter() - .filter(|x| !x.4.affine().is_nan()) + .filter(|x| !x.2.affine().is_nan()) .collect(); - sorted_sprites.sort_by_key(|x| Reverse(FloatOrd(x.4.translation().z))); + sorted_sprites.sort_by_key(|x| Reverse(FloatOrd(x.2.translation().z))); let primary_window = primary_window.get_single().ok(); @@ -77,82 +75,79 @@ pub fn sprite_picking( .iter() .copied() .filter(|(.., visibility)| visibility.get()) - .filter_map( - |(entity, sprite, atlas, image, sprite_transform, picking_behavior, ..)| { - if blocked { - return None; - } - - // Hit box in sprite coordinate system - let extents = match (sprite.custom_size, atlas) { - (Some(custom_size), _) => custom_size, - (None, None) => images.get(image)?.size().as_vec2(), - (None, Some(atlas)) => texture_atlas_layout - .get(&atlas.layout) - .and_then(|layout| layout.textures.get(atlas.index)) - // Dropped atlas layouts and indexes out of bounds are rendered as a sprite - .map_or(images.get(image)?.size().as_vec2(), |rect| { - rect.size().as_vec2() - }), - }; - let anchor = sprite.anchor.as_vec(); - let center = -anchor * extents; - let rect = Rect::from_center_half_size(center, extents / 2.0); - - // Transform cursor line segment to sprite coordinate system - let world_to_sprite = sprite_transform.affine().inverse(); - let cursor_start_sprite = - world_to_sprite.transform_point3(cursor_ray_world.origin); - let cursor_end_sprite = world_to_sprite.transform_point3(cursor_ray_end); - - // Find where the cursor segment intersects the plane Z=0 (which is the sprite's - // plane in sprite-local space). It may not intersect if, for example, we're - // viewing the sprite side-on - if cursor_start_sprite.z == cursor_end_sprite.z { - // Cursor ray is parallel to the sprite and misses it - return None; - } - let lerp_factor = - f32::inverse_lerp(cursor_start_sprite.z, cursor_end_sprite.z, 0.0); - if !(0.0..=1.0).contains(&lerp_factor) { - // Lerp factor is out of range, meaning that while an infinite line cast by - // the cursor would intersect the sprite, the sprite is not between the - // camera's near and far planes - return None; - } - // Otherwise we can interpolate the xy of the start and end positions by the - // lerp factor to get the cursor position in sprite space! - let cursor_pos_sprite = cursor_start_sprite - .lerp(cursor_end_sprite, lerp_factor) - .xy(); - - let is_cursor_in_sprite = rect.contains(cursor_pos_sprite); - - blocked = is_cursor_in_sprite - && picking_behavior.map(|p| p.should_block_lower) != Some(false); - - is_cursor_in_sprite.then(|| { - let hit_pos_world = - sprite_transform.transform_point(cursor_pos_sprite.extend(0.0)); - // Transform point from world to camera space to get the Z distance - let hit_pos_cam = cam_transform - .affine() - .inverse() - .transform_point3(hit_pos_world); - // HitData requires a depth as calculated from the camera's near clipping plane - let depth = -cam_ortho.near - hit_pos_cam.z; - ( - entity, - HitData::new( - cam_entity, - depth, - Some(hit_pos_world), - Some(*sprite_transform.back()), - ), - ) - }) - }, - ) + .filter_map(|(entity, sprite, sprite_transform, picking_behavior, ..)| { + if blocked { + return None; + } + + // Hit box in sprite coordinate system + let extents = match (sprite.custom_size, &sprite.texture_atlas) { + (Some(custom_size), _) => custom_size, + (None, None) => images.get(&sprite.image)?.size().as_vec2(), + (None, Some(atlas)) => texture_atlas_layout + .get(&atlas.layout) + .and_then(|layout| layout.textures.get(atlas.index)) + // Dropped atlas layouts and indexes out of bounds are rendered as a sprite + .map_or(images.get(&sprite.image)?.size().as_vec2(), |rect| { + rect.size().as_vec2() + }), + }; + let anchor = sprite.anchor.as_vec(); + let center = -anchor * extents; + let rect = Rect::from_center_half_size(center, extents / 2.0); + + // Transform cursor line segment to sprite coordinate system + let world_to_sprite = sprite_transform.affine().inverse(); + let cursor_start_sprite = world_to_sprite.transform_point3(cursor_ray_world.origin); + let cursor_end_sprite = world_to_sprite.transform_point3(cursor_ray_end); + + // Find where the cursor segment intersects the plane Z=0 (which is the sprite's + // plane in sprite-local space). It may not intersect if, for example, we're + // viewing the sprite side-on + if cursor_start_sprite.z == cursor_end_sprite.z { + // Cursor ray is parallel to the sprite and misses it + return None; + } + let lerp_factor = + f32::inverse_lerp(cursor_start_sprite.z, cursor_end_sprite.z, 0.0); + if !(0.0..=1.0).contains(&lerp_factor) { + // Lerp factor is out of range, meaning that while an infinite line cast by + // the cursor would intersect the sprite, the sprite is not between the + // camera's near and far planes + return None; + } + // Otherwise we can interpolate the xy of the start and end positions by the + // lerp factor to get the cursor position in sprite space! + let cursor_pos_sprite = cursor_start_sprite + .lerp(cursor_end_sprite, lerp_factor) + .xy(); + + let is_cursor_in_sprite = rect.contains(cursor_pos_sprite); + + blocked = is_cursor_in_sprite + && picking_behavior.map(|p| p.should_block_lower) != Some(false); + + is_cursor_in_sprite.then(|| { + let hit_pos_world = + sprite_transform.transform_point(cursor_pos_sprite.extend(0.0)); + // Transform point from world to camera space to get the Z distance + let hit_pos_cam = cam_transform + .affine() + .inverse() + .transform_point3(hit_pos_world); + // HitData requires a depth as calculated from the camera's near clipping plane + let depth = -cam_ortho.near - hit_pos_cam.z; + ( + entity, + HitData::new( + cam_entity, + depth, + Some(hit_pos_world), + Some(*sprite_transform.back()), + ), + ) + }) + }) .collect(); let order = camera.order as f32; diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index d3bef973748b4..c3e9ba9b5b96c 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -1,10 +1,10 @@ use core::ops::Range; use crate::{ - texture_atlas::{TextureAtlas, TextureAtlasLayout}, - ComputedTextureSlices, Sprite, WithSprite, SPRITE_SHADER_HANDLE, + texture_atlas::TextureAtlasLayout, ComputedTextureSlices, Sprite, WithSprite, + SPRITE_SHADER_HANDLE, }; -use bevy_asset::{AssetEvent, AssetId, Assets, Handle}; +use bevy_asset::{AssetEvent, AssetId, Assets}; use bevy_color::{ColorToComponents, LinearRgba}; use bevy_core_pipeline::{ core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT}, @@ -377,15 +377,12 @@ pub fn extract_sprites( &ViewVisibility, &Sprite, &GlobalTransform, - &Handle, - Option<&TextureAtlas>, Option<&ComputedTextureSlices>, )>, >, ) { extracted_sprites.sprites.clear(); - for (original_entity, entity, view_visibility, sprite, transform, handle, sheet, slices) in - sprite_query.iter() + for (original_entity, entity, view_visibility, sprite, transform, slices) in sprite_query.iter() { if !view_visibility.get() { continue; @@ -394,12 +391,14 @@ pub fn extract_sprites( if let Some(slices) = slices { extracted_sprites.sprites.extend( slices - .extract_sprites(transform, original_entity, sprite, handle) + .extract_sprites(transform, original_entity, sprite) .map(|e| (commands.spawn(TemporaryRenderEntity).id(), e)), ); } else { - let atlas_rect = - sheet.and_then(|s| s.texture_rect(&texture_atlases).map(|r| r.as_rect())); + let atlas_rect = sprite + .texture_atlas + .as_ref() + .and_then(|s| s.texture_rect(&texture_atlases).map(|r| r.as_rect())); let rect = match (atlas_rect, sprite.rect) { (None, None) => None, (None, Some(sprite_rect)) => Some(sprite_rect), @@ -423,7 +422,7 @@ pub fn extract_sprites( custom_size: sprite.custom_size, flip_x: sprite.flip_x, flip_y: sprite.flip_y, - image_handle_id: handle.id(), + image_handle_id: sprite.image.id(), anchor: sprite.anchor.as_vec(), original_entity: Some(original_entity), }, diff --git a/crates/bevy_sprite/src/sprite.rs b/crates/bevy_sprite/src/sprite.rs index 13e37ee56cadd..f6a4da9949442 100644 --- a/crates/bevy_sprite/src/sprite.rs +++ b/crates/bevy_sprite/src/sprite.rs @@ -1,16 +1,22 @@ +use bevy_asset::Handle; use bevy_color::Color; use bevy_ecs::{component::Component, reflect::ReflectComponent}; use bevy_math::{Rect, Vec2}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +use bevy_render::{sync_world::SyncToRenderWorld, texture::Image, view::Visibility}; +use bevy_transform::components::Transform; -use crate::TextureSlicer; +use crate::{TextureAtlas, TextureSlicer}; -/// Specifies the rendering properties of a sprite. -/// -/// This is commonly used as a component within [`SpriteBundle`](crate::bundle::SpriteBundle). +/// Describes a sprite to be rendered to a 2D camera #[derive(Component, Debug, Default, Clone, Reflect)] +#[require(Transform, Visibility, SyncToRenderWorld)] #[reflect(Component, Default, Debug)] pub struct Sprite { + /// The image used to render the sprite + pub image: Handle, + /// The (optional) texture atlas used to render the sprite + pub texture_atlas: Option, /// The sprite's color tint pub color: Color, /// Flip the sprite along the `X` axis @@ -21,9 +27,9 @@ pub struct Sprite { /// of the sprite's image pub custom_size: Option, /// 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 [`TextureAtlas`](crate::TextureAtlas). + /// the full image. This is an easy one-off alternative to using a [`TextureAtlas`]. /// - /// When used with a [`TextureAtlas`](crate::TextureAtlas), the rect + /// 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 @@ -38,6 +44,38 @@ impl Sprite { ..Default::default() } } + + /// Create a sprite from an image + pub fn from_image(image: Handle) -> Self { + Self { + image, + ..Default::default() + } + } + + /// Create a sprite from an image, with an associated texture atlas + pub fn from_atlas_image(image: Handle, atlas: TextureAtlas) -> Self { + Self { + image, + texture_atlas: Some(atlas), + ..Default::default() + } + } + + /// Create a sprite from a solid color + pub fn from_color(color: impl Into, size: Vec2) -> Self { + Self { + color: color.into(), + custom_size: Some(size), + ..Default::default() + } + } +} + +impl From> for Sprite { + fn from(image: Handle) -> Self { + Self::from_image(image) + } } /// Controls how the image is altered when scaled. @@ -58,7 +96,7 @@ pub enum ImageScaleMode { }, } -/// How a sprite is positioned relative to its [`Transform`](bevy_transform::components::Transform). +/// How a sprite is positioned relative to its [`Transform`]. /// It defaults to `Anchor::Center`. #[derive(Component, Debug, Clone, Copy, PartialEq, Default, Reflect)] #[reflect(Component, Default, Debug, PartialEq)] diff --git a/crates/bevy_sprite/src/texture_atlas_builder.rs b/crates/bevy_sprite/src/texture_atlas_builder.rs index e99dd20efc3f8..2428fd4b15298 100644 --- a/crates/bevy_sprite/src/texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/texture_atlas_builder.rs @@ -190,10 +190,7 @@ impl<'a> TextureAtlasBuilder<'a> { /// let texture = textures.add(texture); /// let layout = layouts.add(atlas_layout); /// // Spawn your sprite - /// commands.spawn(( - /// SpriteBundle { texture, ..Default::default() }, - /// TextureAtlas::from(layout), - /// )); + /// commands.spawn(Sprite::from_atlas_image(texture, TextureAtlas::from(layout))); /// } /// ``` /// diff --git a/crates/bevy_sprite/src/texture_slice/computed_slices.rs b/crates/bevy_sprite/src/texture_slice/computed_slices.rs index c49987e1bb93a..25b23c7025225 100644 --- a/crates/bevy_sprite/src/texture_slice/computed_slices.rs +++ b/crates/bevy_sprite/src/texture_slice/computed_slices.rs @@ -1,7 +1,7 @@ -use crate::{ExtractedSprite, ImageScaleMode, Sprite, TextureAtlas, TextureAtlasLayout}; +use crate::{ExtractedSprite, ImageScaleMode, Sprite, TextureAtlasLayout}; use super::TextureSlice; -use bevy_asset::{AssetEvent, Assets, Handle}; +use bevy_asset::{AssetEvent, Assets}; use bevy_ecs::prelude::*; use bevy_math::{Rect, Vec2}; use bevy_render::texture::Image; @@ -29,7 +29,6 @@ impl ComputedTextureSlices { transform: &'a GlobalTransform, original_entity: Entity, sprite: &'a Sprite, - handle: &'a Handle, ) -> impl ExactSizeIterator + 'a { let mut flip = Vec2::ONE; let [mut flip_x, mut flip_y] = [false; 2]; @@ -52,7 +51,7 @@ impl ComputedTextureSlices { custom_size: Some(slice.draw_size), flip_x, flip_y, - image_handle_id: handle.id(), + image_handle_id: sprite.image.id(), anchor: Self::redepend_anchor_from_sprite_to_slice(sprite, slice), } }) @@ -88,12 +87,10 @@ impl ComputedTextureSlices { fn compute_sprite_slices( sprite: &Sprite, scale_mode: &ImageScaleMode, - image_handle: &Handle, images: &Assets, - atlas: Option<&TextureAtlas>, atlas_layouts: &Assets, ) -> Option { - let (image_size, texture_rect) = match atlas { + let (image_size, texture_rect) = match &sprite.texture_atlas { Some(a) => { let layout = atlas_layouts.get(&a.layout)?; ( @@ -102,7 +99,7 @@ fn compute_sprite_slices( ) } None => { - let image = images.get(image_handle)?; + let image = images.get(&sprite.image)?; let size = Vec2::new( image.texture_descriptor.size.width as f32, image.texture_descriptor.size.height as f32, @@ -139,13 +136,7 @@ pub(crate) fn compute_slices_on_asset_event( mut events: EventReader>, images: Res>, atlas_layouts: Res>, - sprites: Query<( - Entity, - &ImageScaleMode, - &Sprite, - &Handle, - Option<&TextureAtlas>, - )>, + sprites: Query<(Entity, &ImageScaleMode, &Sprite)>, ) { // We store the asset ids of added/modified image assets let added_handles: HashSet<_> = events @@ -159,18 +150,11 @@ pub(crate) fn compute_slices_on_asset_event( return; } // We recompute the sprite slices for sprite entities with a matching asset handle id - for (entity, scale_mode, sprite, image_handle, atlas) in &sprites { - if !added_handles.contains(&image_handle.id()) { + for (entity, scale_mode, sprite) in &sprites { + if !added_handles.contains(&sprite.image.id()) { continue; } - if let Some(slices) = compute_sprite_slices( - sprite, - scale_mode, - image_handle, - &images, - atlas, - &atlas_layouts, - ) { + if let Some(slices) = compute_sprite_slices(sprite, scale_mode, &images, &atlas_layouts) { commands.entity(entity).insert(slices); } } @@ -183,30 +167,12 @@ pub(crate) fn compute_slices_on_sprite_change( images: Res>, atlas_layouts: Res>, changed_sprites: Query< - ( - Entity, - &ImageScaleMode, - &Sprite, - &Handle, - Option<&TextureAtlas>, - ), - Or<( - Changed, - Changed>, - Changed, - Changed, - )>, + (Entity, &ImageScaleMode, &Sprite), + Or<(Changed, Changed)>, >, ) { - for (entity, scale_mode, sprite, image_handle, atlas) in &changed_sprites { - if let Some(slices) = compute_sprite_slices( - sprite, - scale_mode, - image_handle, - &images, - atlas, - &atlas_layouts, - ) { + for (entity, scale_mode, sprite) in &changed_sprites { + if let Some(slices) = compute_sprite_slices(sprite, scale_mode, &images, &atlas_layouts) { commands.entity(entity).insert(slices); } } diff --git a/examples/2d/bloom_2d.rs b/examples/2d/bloom_2d.rs index fcd5534632f68..3f12ea772ef1e 100644 --- a/examples/2d/bloom_2d.rs +++ b/examples/2d/bloom_2d.rs @@ -33,13 +33,10 @@ fn setup( )); // Sprite - commands.spawn(SpriteBundle { - texture: asset_server.load("branding/bevy_bird_dark.png"), - sprite: Sprite { - color: Color::srgb(5.0, 5.0, 5.0), // 4. Put something bright in a dark environment to see the effect - custom_size: Some(Vec2::splat(160.0)), - ..default() - }, + commands.spawn(Sprite { + image: asset_server.load("branding/bevy_bird_dark.png"), + color: Color::srgb(5.0, 5.0, 5.0), // 4. Put something bright in a dark environment to see the effect + custom_size: Some(Vec2::splat(160.0)), ..default() }); diff --git a/examples/2d/cpu_draw.rs b/examples/2d/cpu_draw.rs index 0fa6e81a62bcf..8e9982dbee6f0 100644 --- a/examples/2d/cpu_draw.rs +++ b/examples/2d/cpu_draw.rs @@ -78,11 +78,7 @@ fn setup(mut commands: Commands, mut images: ResMut>) { let handle = images.add(image); // create a sprite entity using our image - commands.spawn(SpriteBundle { - texture: handle.clone(), - ..Default::default() - }); - + commands.spawn(Sprite::from_image(handle.clone())); commands.insert_resource(MyProcGenImage(handle)); } diff --git a/examples/2d/move_sprite.rs b/examples/2d/move_sprite.rs index 9bbbe496584c5..976389404b9e3 100644 --- a/examples/2d/move_sprite.rs +++ b/examples/2d/move_sprite.rs @@ -19,11 +19,8 @@ enum Direction { fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(Camera2d); commands.spawn(( - SpriteBundle { - texture: asset_server.load("branding/icon.png"), - transform: Transform::from_xyz(100., 0., 0.), - ..default() - }, + Sprite::from_image(asset_server.load("branding/icon.png")), + Transform::from_xyz(100., 0., 0.), Direction::Up, )); } diff --git a/examples/2d/pixel_grid_snap.rs b/examples/2d/pixel_grid_snap.rs index 7e2fcffb5c5b0..d1b24ab5f3746 100644 --- a/examples/2d/pixel_grid_snap.rs +++ b/examples/2d/pixel_grid_snap.rs @@ -52,22 +52,16 @@ struct Rotate; fn setup_sprite(mut commands: Commands, asset_server: Res) { // the sample sprite that will be rendered to the pixel-perfect canvas commands.spawn(( - SpriteBundle { - texture: asset_server.load("pixel/bevy_pixel_dark.png"), - transform: Transform::from_xyz(-40., 20., 2.), - ..default() - }, + Sprite::from_image(asset_server.load("pixel/bevy_pixel_dark.png")), + Transform::from_xyz(-40., 20., 2.), Rotate, PIXEL_PERFECT_LAYERS, )); // the sample sprite that will be rendered to the high-res "outer world" commands.spawn(( - SpriteBundle { - texture: asset_server.load("pixel/bevy_pixel_light.png"), - transform: Transform::from_xyz(-40., -20., 2.), - ..default() - }, + Sprite::from_image(asset_server.load("pixel/bevy_pixel_light.png")), + Transform::from_xyz(-40., -20., 2.), Rotate, HIGH_RES_LAYERS, )); @@ -132,14 +126,7 @@ fn setup_camera(mut commands: Commands, mut images: ResMut>) { )); // spawn the canvas - commands.spawn(( - SpriteBundle { - texture: image_handle, - ..default() - }, - Canvas, - HIGH_RES_LAYERS, - )); + commands.spawn((Sprite::from_image(image_handle), Canvas, HIGH_RES_LAYERS)); // the "outer" camera renders whatever is on `HIGH_RES_LAYERS` to the screen. // here, the canvas and one of the sample sprites will be rendered by this camera diff --git a/examples/2d/rotation.rs b/examples/2d/rotation.rs index 85a8542a98306..1468dc6011da5 100644 --- a/examples/2d/rotation.rs +++ b/examples/2d/rotation.rs @@ -62,10 +62,7 @@ fn setup(mut commands: Commands, asset_server: Res) { // player controlled ship commands.spawn(( - SpriteBundle { - texture: ship_handle, - ..default() - }, + Sprite::from_image(ship_handle), Player { movement_speed: 500.0, // meters per second rotation_speed: f32::to_radians(360.0), // degrees per second @@ -74,39 +71,27 @@ fn setup(mut commands: Commands, asset_server: Res) { // enemy that snaps to face the player spawns on the bottom and left commands.spawn(( - SpriteBundle { - texture: enemy_a_handle.clone(), - transform: Transform::from_xyz(0.0 - horizontal_margin, 0.0, 0.0), - ..default() - }, + Sprite::from_image(enemy_a_handle.clone()), + Transform::from_xyz(0.0 - horizontal_margin, 0.0, 0.0), SnapToPlayer, )); commands.spawn(( - SpriteBundle { - texture: enemy_a_handle, - transform: Transform::from_xyz(0.0, 0.0 - vertical_margin, 0.0), - ..default() - }, + Sprite::from_image(enemy_a_handle), + Transform::from_xyz(0.0, 0.0 - vertical_margin, 0.0), SnapToPlayer, )); // enemy that rotates to face the player enemy spawns on the top and right commands.spawn(( - SpriteBundle { - texture: enemy_b_handle.clone(), - transform: Transform::from_xyz(0.0 + horizontal_margin, 0.0, 0.0), - ..default() - }, + Sprite::from_image(enemy_b_handle.clone()), + Transform::from_xyz(0.0 + horizontal_margin, 0.0, 0.0), RotateToPlayer { rotation_speed: f32::to_radians(45.0), // degrees per second }, )); commands.spawn(( - SpriteBundle { - texture: enemy_b_handle, - transform: Transform::from_xyz(0.0, 0.0 + vertical_margin, 0.0), - ..default() - }, + Sprite::from_image(enemy_b_handle), + Transform::from_xyz(0.0, 0.0 + vertical_margin, 0.0), RotateToPlayer { rotation_speed: f32::to_radians(90.0), // degrees per second }, diff --git a/examples/2d/sprite.rs b/examples/2d/sprite.rs index 1866f2d4fbf19..4c6f6c92f1dad 100644 --- a/examples/2d/sprite.rs +++ b/examples/2d/sprite.rs @@ -11,8 +11,7 @@ fn main() { fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(Camera2d); - commands.spawn(SpriteBundle { - texture: asset_server.load("branding/bevy_bird_dark.png"), - ..default() - }); + commands.spawn(Sprite::from_image( + asset_server.load("branding/bevy_bird_dark.png"), + )); } diff --git a/examples/2d/sprite_animation.rs b/examples/2d/sprite_animation.rs index 57d9870750d20..5a7849ed266ab 100644 --- a/examples/2d/sprite_animation.rs +++ b/examples/2d/sprite_animation.rs @@ -56,24 +56,23 @@ impl AnimationConfig { // This system loops through all the sprites in the `TextureAtlas`, from `first_sprite_index` to // `last_sprite_index` (both defined in `AnimationConfig`). -fn execute_animations( - time: Res