Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify sprites and sprite sheets #5072

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 5 additions & 37 deletions crates/bevy_sprite/src/bundle.rs
Original file line number Diff line number Diff line change
@@ -1,47 +1,15 @@
use crate::{
texture_atlas::{TextureAtlas, TextureAtlasSprite},
Sprite,
};
use bevy_asset::Handle;
use crate::{Sprite, SpriteImage};
use bevy_ecs::bundle::Bundle;
use bevy_render::{
texture::{Image, DEFAULT_IMAGE_HANDLE},
view::Visibility,
};
use bevy_render::view::Visibility;
use bevy_transform::components::{GlobalTransform, Transform};

#[derive(Bundle, Clone)]
#[derive(Bundle, Clone, Default)]
pub struct SpriteBundle {
pub sprite: Sprite,
pub transform: Transform,
pub global_transform: GlobalTransform,
pub texture: Handle<Image>,
/// User indication of whether an entity is visible
pub visibility: Visibility,
}

impl Default for SpriteBundle {
fn default() -> Self {
Self {
sprite: Default::default(),
transform: Default::default(),
global_transform: Default::default(),
texture: DEFAULT_IMAGE_HANDLE.typed(),
visibility: Default::default(),
}
}
}
/// A Bundle of components for drawing a single sprite from a sprite sheet (also referred
/// to as a `TextureAtlas`)
#[derive(Bundle, Clone, Default)]
pub struct SpriteSheetBundle {
/// The specific sprite from the texture atlas to be drawn
pub sprite: TextureAtlasSprite,
/// A handle to the texture atlas that holds the sprite images
pub texture_atlas: Handle<TextureAtlas>,
/// Data pertaining to how the sprite is drawn on the screen
pub transform: Transform,
pub global_transform: GlobalTransform,
/// The sprite texture
pub texture: SpriteImage,
/// User indication of whether an entity is visible
pub visibility: Visibility,
}
31 changes: 31 additions & 0 deletions crates/bevy_sprite/src/image.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use crate::TextureAtlas;
use bevy_asset::Handle;
use bevy_ecs::component::Component;
use bevy_reflect::Reflect;
use bevy_render::texture::{Image, DEFAULT_IMAGE_HANDLE};

/// The sprite texture
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These docs are not useful to users who do not know what a sprite is.

#[derive(Component, Clone, Debug, Reflect)]
pub enum SpriteImage {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps just Sprite? I'm curious about others opinion here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well Sprite is already a component

/// Single texture
Image(Handle<Image>),
/// Texture atlas.
TextureAtlas {
/// Texture atlas handle
handle: Handle<TextureAtlas>,
/// Texture atlas index
index: usize,
},
}

impl Default for SpriteImage {
fn default() -> Self {
Self::Image(DEFAULT_IMAGE_HANDLE.typed())
}
}

impl From<Handle<Image>> for SpriteImage {
fn from(handle: Handle<Image>) -> Self {
Self::Image(handle)
}
}
6 changes: 3 additions & 3 deletions crates/bevy_sprite/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod bundle;
mod dynamic_texture_atlas_builder;
mod image;
mod mesh2d;
mod rect;
mod render;
Expand All @@ -12,15 +13,14 @@ pub mod collide_aabb;
pub mod prelude {
#[doc(hidden)]
pub use crate::{
bundle::{SpriteBundle, SpriteSheetBundle},
sprite::Sprite,
texture_atlas::{TextureAtlas, TextureAtlasSprite},
bundle::SpriteBundle, image::SpriteImage, sprite::Sprite, texture_atlas::TextureAtlas,
ColorMaterial, ColorMesh2dBundle, TextureAtlasBuilder,
};
}

pub use bundle::*;
pub use dynamic_texture_atlas_builder::*;
pub use image::*;
pub use mesh2d::*;
pub use rect::*;
pub use render::*;
Expand Down
54 changes: 20 additions & 34 deletions crates/bevy_sprite/src/render/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
use std::cmp::Ordering;

use crate::{
texture_atlas::{TextureAtlas, TextureAtlasSprite},
Rect, Sprite, SPRITE_SHADER_HANDLE,
};
use crate::{texture_atlas::TextureAtlas, Rect, Sprite, SpriteImage, SPRITE_SHADER_HANDLE};
use bevy_asset::{AssetEvent, Assets, Handle, HandleId};
use bevy_core_pipeline::core_2d::Transparent2d;
use bevy_ecs::{
Expand Down Expand Up @@ -224,54 +221,43 @@ pub fn extract_sprite_events(
pub fn extract_sprites(
mut render_world: ResMut<RenderWorld>,
texture_atlases: Res<Assets<TextureAtlas>>,
sprite_query: Query<(&Visibility, &Sprite, &GlobalTransform, &Handle<Image>)>,
atlas_query: Query<(
&Visibility,
&TextureAtlasSprite,
&GlobalTransform,
&Handle<TextureAtlas>,
)>,
sprite_query: Query<(&Visibility, &Sprite, &GlobalTransform, &SpriteImage)>,
) {
let mut extracted_sprites = render_world.resource_mut::<ExtractedSprites>();
extracted_sprites.sprites.clear();
for (visibility, sprite, transform, handle) in sprite_query.iter() {
for (visibility, sprite, transform, image) in sprite_query.iter() {
if !visibility.is_visible {
continue;
}

let (rect, image_handle_id) = match image {
SpriteImage::Image(handle) => (None, handle.id),
SpriteImage::TextureAtlas { handle, index } => {
if let Some(atlas) = texture_atlases.get(handle) {
let rect = atlas.textures.get(*index).copied().unwrap_or_else(|| {
panic!("TextureAtlas {:?} as no texture at index {}", atlas, index)
});
(Some(rect), atlas.texture.id)
} else {
// Skip loading images
continue;
}
}
};
// 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
extracted_sprites.sprites.alloc().init(ExtractedSprite {
color: sprite.color,
transform: *transform,
// Use the full texture
rect: None,
rect,
// Pass the custom size
custom_size: sprite.custom_size,
flip_x: sprite.flip_x,
flip_y: sprite.flip_y,
image_handle_id: handle.id,
image_handle_id,
anchor: sprite.anchor.as_vec(),
});
}
for (visibility, atlas_sprite, transform, texture_atlas_handle) in atlas_query.iter() {
if !visibility.is_visible {
continue;
}
if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) {
let rect = Some(texture_atlas.textures[atlas_sprite.index as usize]);
extracted_sprites.sprites.alloc().init(ExtractedSprite {
color: atlas_sprite.color,
transform: *transform,
// Select the area in the texture atlas
rect,
// Pass the custom size
custom_size: atlas_sprite.custom_size,
flip_x: atlas_sprite.flip_x,
flip_y: atlas_sprite.flip_y,
image_handle_id: texture_atlas.texture.id,
anchor: atlas_sprite.anchor.as_vec(),
});
}
}
}

#[repr(C)]
Expand Down
41 changes: 3 additions & 38 deletions crates/bevy_sprite/src/texture_atlas.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use crate::{Anchor, Rect};
use crate::Rect;
use bevy_asset::Handle;
use bevy_ecs::component::Component;
use bevy_math::Vec2;
use bevy_reflect::{Reflect, TypeUuid};
use bevy_render::{color::Color, texture::Image};
use bevy_reflect::TypeUuid;
use bevy_render::texture::Image;
use bevy_utils::HashMap;

/// An atlas containing multiple textures (like a spritesheet or a tilemap).
Expand All @@ -21,40 +20,6 @@ pub struct TextureAtlas {
pub texture_handles: Option<HashMap<Handle<Image>, usize>>,
}

#[derive(Component, Debug, Clone, Reflect)]
pub struct TextureAtlasSprite {
pub color: Color,
pub index: usize,
pub flip_x: bool,
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 in the atlas
pub custom_size: Option<Vec2>,
pub anchor: Anchor,
}

impl Default for TextureAtlasSprite {
fn default() -> Self {
Self {
index: 0,
color: Color::WHITE,
flip_x: false,
flip_y: false,
custom_size: None,
anchor: Anchor::default(),
}
}
}

impl TextureAtlasSprite {
pub fn new(index: usize) -> TextureAtlasSprite {
Self {
index,
..Default::default()
}
}
}

impl TextureAtlas {
/// Create a new `TextureAtlas` that has a texture, but does not have
/// any individual sprites specified
Expand Down
2 changes: 1 addition & 1 deletion examples/2d/move_sprite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn_bundle(Camera2dBundle::default());
commands
.spawn_bundle(SpriteBundle {
texture: asset_server.load("branding/icon.png"),
texture: asset_server.load("branding/icon.png").into(),
transform: Transform::from_xyz(100., 0., 0.),
..default()
})
Expand Down
10 changes: 5 additions & 5 deletions examples/2d/rotation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// player controlled ship
commands
.spawn_bundle(SpriteBundle {
texture: ship_handle,
texture: ship_handle.into(),
..default()
})
.insert(Player {
Expand All @@ -78,14 +78,14 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// enemy that snaps to face the player spawns on the bottom and left
commands
.spawn_bundle(SpriteBundle {
texture: enemy_a_handle.clone(),
texture: enemy_a_handle.clone().into(),
transform: Transform::from_xyz(0.0 - horizontal_margin, 0.0, 0.0),
..default()
})
.insert(SnapToPlayer);
commands
.spawn_bundle(SpriteBundle {
texture: enemy_a_handle,
texture: enemy_a_handle.into(),
transform: Transform::from_xyz(0.0, 0.0 - vertical_margin, 0.0),
..default()
})
Expand All @@ -94,7 +94,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// enemy that rotates to face the player enemy spawns on the top and right
commands
.spawn_bundle(SpriteBundle {
texture: enemy_b_handle.clone(),
texture: enemy_b_handle.clone().into(),
transform: Transform::from_xyz(0.0 + horizontal_margin, 0.0, 0.0),
..default()
})
Expand All @@ -103,7 +103,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
});
commands
.spawn_bundle(SpriteBundle {
texture: enemy_b_handle,
texture: enemy_b_handle.into(),
transform: Transform::from_xyz(0.0, 0.0 + vertical_margin, 0.0),
..default()
})
Expand Down
2 changes: 1 addition & 1 deletion examples/2d/sprite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fn main() {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn_bundle(Camera2dBundle::default());
commands.spawn_bundle(SpriteBundle {
texture: asset_server.load("branding/icon.png"),
texture: asset_server.load("branding/icon.png").into(),
..default()
});
}
2 changes: 1 addition & 1 deletion examples/2d/sprite_flipping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fn main() {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn_bundle(Camera2dBundle::default());
commands.spawn_bundle(SpriteBundle {
texture: asset_server.load("branding/icon.png"),
texture: asset_server.load("branding/icon.png").into(),
sprite: Sprite {
// Flip the logo to the left
flip_x: true,
Expand Down
22 changes: 12 additions & 10 deletions examples/2d/sprite_sheet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//! into a texture atlas, and changing the displayed image periodically.
use bevy::{prelude::*, render::texture::ImageSettings};
use std::ops::DerefMut;

fn main() {
App::new()
Expand All @@ -18,17 +19,15 @@ struct AnimationTimer(Timer);
fn animate_sprite(
time: Res<Time>,
texture_atlases: Res<Assets<TextureAtlas>>,
mut query: Query<(
&mut AnimationTimer,
&mut TextureAtlasSprite,
&Handle<TextureAtlas>,
)>,
mut query: Query<(&mut AnimationTimer, &mut SpriteImage)>,
) {
for (mut timer, mut sprite, texture_atlas_handle) in query.iter_mut() {
for (mut timer, mut image) in query.iter_mut() {
timer.tick(time.delta());
if timer.just_finished() {
let texture_atlas = texture_atlases.get(texture_atlas_handle).unwrap();
sprite.index = (sprite.index + 1) % texture_atlas.textures.len();
if let SpriteImage::TextureAtlas { index, handle } = image.deref_mut() {
let texture_atlas = texture_atlases.get(handle).unwrap();
*index = (*index + 1) % texture_atlas.textures.len();
}
}
}
}
Expand All @@ -43,8 +42,11 @@ fn setup(
let texture_atlas_handle = texture_atlases.add(texture_atlas);
commands.spawn_bundle(Camera2dBundle::default());
commands
.spawn_bundle(SpriteSheetBundle {
texture_atlas: texture_atlas_handle,
.spawn_bundle(SpriteBundle {
texture: SpriteImage::TextureAtlas {
handle: texture_atlas_handle,
index: 0,
},
transform: Transform::from_scale(Vec3::splat(6.0)),
..default()
})
Expand Down
10 changes: 6 additions & 4 deletions examples/2d/texture_atlas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,21 @@ fn setup(
// set up a scene to display our texture atlas
commands.spawn_bundle(Camera2dBundle::default());
// draw a sprite from the atlas
commands.spawn_bundle(SpriteSheetBundle {
commands.spawn_bundle(SpriteBundle {
transform: Transform {
translation: Vec3::new(150.0, 0.0, 0.0),
scale: Vec3::splat(4.0),
..default()
},
sprite: TextureAtlasSprite::new(vendor_index),
texture_atlas: atlas_handle,
texture: SpriteImage::TextureAtlas {
handle: atlas_handle,
index: vendor_index,
},
..default()
});
// draw the atlas itself
commands.spawn_bundle(SpriteBundle {
texture: texture_atlas_texture,
texture: texture_atlas_texture.into(),
transform: Transform::from_xyz(-300.0, 0.0, 0.0),
..default()
});
Expand Down
Loading