Texture Atlas rework (#5103)
# Objective > Old MR: #5072 > ~~Associated UI MR: #5070~~ > Adresses #1618 Unify sprite management ## Solution - Remove the `Handle<Image>` field in `TextureAtlas` which is the main cause for all the boilerplate - Remove the redundant `TextureAtlasSprite` component - Renamed `TextureAtlas` asset to `TextureAtlasLayout` ([suggestion](https://github.com/bevyengine/bevy/pull/5103#discussion_r917281844)) - Add a `TextureAtlas` component, containing the atlas layout handle and the section index The difference between this solution and #5072 is that instead of the `enum` approach is that we can more easily manipulate texture sheets without any breaking changes for classic `SpriteBundle`s (@mockersf [comment](https://github.com/bevyengine/bevy/pull/5072#issuecomment-1165836139)) Also, this approach is more *data oriented* extracting the `Handle<Image>` and avoiding complex texture atlas manipulations to retrieve the texture in both applicative and engine code. With this method, the only difference between a `SpriteBundle` and a `SpriteSheetBundle` is an **additional** component storing the atlas handle and the index. ~~This solution can be applied to `bevy_ui` as well (see #5070).~~ EDIT: I also applied this solution to Bevy UI ## Changelog - (**BREAKING**) Removed `TextureAtlasSprite` - (**BREAKING**) Renamed `TextureAtlas` to `TextureAtlasLayout` - (**BREAKING**) `SpriteSheetBundle`: - Uses a `Sprite` instead of a `TextureAtlasSprite` component - Has a `texture` field containing a `Handle<Image>` like the `SpriteBundle` - Has a new `TextureAtlas` component instead of a `Handle<TextureAtlasLayout>` - (**BREAKING**) `DynamicTextureAtlasBuilder::add_texture` takes an additional `&Handle<Image>` parameter - (**BREAKING**) `TextureAtlasLayout::from_grid` no longer takes a `Handle<Image>` parameter - (**BREAKING**) `TextureAtlasBuilder::finish` now returns a `Result<(TextureAtlasLayout, Handle<Image>), _>` - `bevy_text`: - `GlyphAtlasInfo` stores the texture `Handle<Image>` - `FontAtlas` stores the texture `Handle<Image>` - `bevy_ui`: - (**BREAKING**) Removed `UiAtlasImage` , the atlas bundle is now identical to the `ImageBundle` with an additional `TextureAtlas` ## Migration Guide * Sprites ```diff fn my_system( mut images: ResMut<Assets<Image>>, - mut atlases: ResMut<Assets<TextureAtlas>>, + mut atlases: ResMut<Assets<TextureAtlasLayout>>, asset_server: Res<AssetServer> ) { let texture_handle: asset_server.load("my_texture.png"); - let layout = TextureAtlas::from_grid(texture_handle, Vec2::new(25.0, 25.0), 5, 5, None, None); + let layout = TextureAtlasLayout::from_grid(Vec2::new(25.0, 25.0), 5, 5, None, None); let layout_handle = atlases.add(layout); commands.spawn(SpriteSheetBundle { - sprite: TextureAtlasSprite::new(0), - texture_atlas: atlas_handle, + atlas: TextureAtlas { + layout: layout_handle, + index: 0 + }, + texture: texture_handle, ..Default::default() }); } ``` * UI ```diff fn my_system( mut images: ResMut<Assets<Image>>, - mut atlases: ResMut<Assets<TextureAtlas>>, + mut atlases: ResMut<Assets<TextureAtlasLayout>>, asset_server: Res<AssetServer> ) { let texture_handle: asset_server.load("my_texture.png"); - let layout = TextureAtlas::from_grid(texture_handle, Vec2::new(25.0, 25.0), 5, 5, None, None); + let layout = TextureAtlasLayout::from_grid(Vec2::new(25.0, 25.0), 5, 5, None, None); let layout_handle = atlases.add(layout); commands.spawn(AtlasImageBundle { - texture_atlas_image: UiTextureAtlasImage { - index: 0, - flip_x: false, - flip_y: false, - }, - texture_atlas: atlas_handle, + atlas: TextureAtlas { + layout: layout_handle, + index: 0 + }, + image: UiImage { + texture: texture_handle, + flip_x: false, + flip_y: false, + }, ..Default::default() }); } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: François <mockersf@gmail.com> Co-authored-by: IceSentry <IceSentry@users.noreply.github.com>
This commit is contained in:
parent
9f8db0de0d
commit
135c7240f1
@ -1,7 +1,4 @@
|
||||
use crate::{
|
||||
texture_atlas::{TextureAtlas, TextureAtlasSprite},
|
||||
ImageScaleMode, Sprite,
|
||||
};
|
||||
use crate::{texture_atlas::TextureAtlas, ImageScaleMode, Sprite};
|
||||
use bevy_asset::Handle;
|
||||
use bevy_ecs::bundle::Bundle;
|
||||
use bevy_render::{
|
||||
@ -32,18 +29,25 @@ pub struct SpriteBundle {
|
||||
}
|
||||
|
||||
/// A [`Bundle`] of components for drawing a single sprite from a sprite sheet (also referred
|
||||
/// to as a `TextureAtlas`).
|
||||
/// to as a `TextureAtlas`) or for animated sprites.
|
||||
///
|
||||
/// Note:
|
||||
/// This bundle is identical to [`SpriteBundle`] with an additional [`TextureAtlas`] component.
|
||||
///
|
||||
/// Check the following examples for usage:
|
||||
/// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)
|
||||
/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
|
||||
#[derive(Bundle, Clone, Default)]
|
||||
pub struct SpriteSheetBundle {
|
||||
/// The specific sprite from the texture atlas to be drawn, defaulting to the sprite at index 0.
|
||||
pub sprite: TextureAtlasSprite,
|
||||
pub sprite: Sprite,
|
||||
/// Controls how the image is altered when scaled.
|
||||
pub scale_mode: ImageScaleMode,
|
||||
/// 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 sheet base texture
|
||||
pub texture: Handle<Image>,
|
||||
/// The sprite sheet texture atlas, allowing to draw a custom section of `texture`.
|
||||
pub atlas: TextureAtlas,
|
||||
/// User indication of whether an entity is visible
|
||||
pub visibility: Visibility,
|
||||
pub inherited_visibility: InheritedVisibility,
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::TextureAtlas;
|
||||
use bevy_asset::Assets;
|
||||
use crate::TextureAtlasLayout;
|
||||
use bevy_asset::{Assets, Handle};
|
||||
use bevy_math::{IVec2, Rect, Vec2};
|
||||
use bevy_render::{
|
||||
render_asset::RenderAssetPersistencePolicy,
|
||||
@ -7,10 +7,10 @@ use bevy_render::{
|
||||
};
|
||||
use guillotiere::{size2, Allocation, AtlasAllocator};
|
||||
|
||||
/// Helper utility to update [`TextureAtlas`] on the fly.
|
||||
/// Helper utility to update [`TextureAtlasLayout`] on the fly.
|
||||
///
|
||||
/// Helpful in cases when texture is created procedurally,
|
||||
/// e.g: in a font glyph [`TextureAtlas`], only add the [`Image`] texture for letters to be rendered.
|
||||
/// e.g: in a font glyph [`TextureAtlasLayout`], only add the [`Image`] texture for letters to be rendered.
|
||||
pub struct DynamicTextureAtlasBuilder {
|
||||
atlas_allocator: AtlasAllocator,
|
||||
padding: i32,
|
||||
@ -30,22 +30,30 @@ impl DynamicTextureAtlasBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a new texture to [`TextureAtlas`].
|
||||
/// It is user's responsibility to pass in the correct [`TextureAtlas`],
|
||||
/// and that [`TextureAtlas::texture`] has [`Image::cpu_persistent_access`]
|
||||
/// Add a new texture to `atlas_layout`
|
||||
/// It is the user's responsibility to pass in the correct [`TextureAtlasLayout`]
|
||||
/// and that `atlas_texture_handle` has [`Image::cpu_persistent_access`]
|
||||
/// set to [`RenderAssetPersistencePolicy::Keep`]
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `altas_layout` - The atlas to add the texture to
|
||||
/// * `textures` - The texture assets container
|
||||
/// * `texture` - The new texture to add to the atlas
|
||||
/// * `atlas_texture_handle` - The atlas texture to edit
|
||||
pub fn add_texture(
|
||||
&mut self,
|
||||
texture_atlas: &mut TextureAtlas,
|
||||
atlas_layout: &mut TextureAtlasLayout,
|
||||
textures: &mut Assets<Image>,
|
||||
texture: &Image,
|
||||
atlas_texture_handle: &Handle<Image>,
|
||||
) -> Option<usize> {
|
||||
let allocation = self.atlas_allocator.allocate(size2(
|
||||
texture.width() as i32 + self.padding,
|
||||
texture.height() as i32 + self.padding,
|
||||
));
|
||||
if let Some(allocation) = allocation {
|
||||
let atlas_texture = textures.get_mut(&texture_atlas.texture).unwrap();
|
||||
let atlas_texture = textures.get_mut(atlas_texture_handle).unwrap();
|
||||
assert_eq!(
|
||||
atlas_texture.cpu_persistent_access,
|
||||
RenderAssetPersistencePolicy::Keep
|
||||
@ -54,7 +62,7 @@ impl DynamicTextureAtlasBuilder {
|
||||
self.place_texture(atlas_texture, allocation, texture);
|
||||
let mut rect: Rect = to_rect(allocation.rectangle);
|
||||
rect.max -= self.padding as f32;
|
||||
Some(texture_atlas.add_texture(rect))
|
||||
Some(atlas_layout.add_texture(rect))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ pub mod prelude {
|
||||
pub use crate::{
|
||||
bundle::{SpriteBundle, SpriteSheetBundle},
|
||||
sprite::{ImageScaleMode, Sprite},
|
||||
texture_atlas::{TextureAtlas, TextureAtlasSprite},
|
||||
texture_atlas::{TextureAtlas, TextureAtlasLayout},
|
||||
texture_slice::{BorderRect, SliceScaleMode, TextureSlicer},
|
||||
ColorMaterial, ColorMesh2dBundle, TextureAtlasBuilder,
|
||||
};
|
||||
@ -65,13 +65,13 @@ impl Plugin for SpritePlugin {
|
||||
"render/sprite.wgsl",
|
||||
Shader::from_wgsl
|
||||
);
|
||||
app.init_asset::<TextureAtlas>()
|
||||
.register_asset_reflect::<TextureAtlas>()
|
||||
app.init_asset::<TextureAtlasLayout>()
|
||||
.register_asset_reflect::<TextureAtlasLayout>()
|
||||
.register_type::<Sprite>()
|
||||
.register_type::<ImageScaleMode>()
|
||||
.register_type::<TextureSlicer>()
|
||||
.register_type::<TextureAtlasSprite>()
|
||||
.register_type::<Anchor>()
|
||||
.register_type::<TextureAtlas>()
|
||||
.register_type::<Mesh2dHandle>()
|
||||
.add_plugins((Mesh2dRenderPlugin, ColorMaterialPlugin))
|
||||
.add_systems(
|
||||
@ -131,19 +131,15 @@ pub fn calculate_bounds_2d(
|
||||
mut commands: Commands,
|
||||
meshes: Res<Assets<Mesh>>,
|
||||
images: Res<Assets<Image>>,
|
||||
atlases: Res<Assets<TextureAtlas>>,
|
||||
atlases: Res<Assets<TextureAtlasLayout>>,
|
||||
meshes_without_aabb: Query<(Entity, &Mesh2dHandle), (Without<Aabb>, Without<NoFrustumCulling>)>,
|
||||
sprites_to_recalculate_aabb: Query<
|
||||
(Entity, &Sprite, &Handle<Image>),
|
||||
(Entity, &Sprite, &Handle<Image>, Option<&TextureAtlas>),
|
||||
(
|
||||
Or<(Without<Aabb>, Changed<Sprite>)>,
|
||||
Without<NoFrustumCulling>,
|
||||
),
|
||||
>,
|
||||
atlases_without_aabb: Query<
|
||||
(Entity, &TextureAtlasSprite, &Handle<TextureAtlas>),
|
||||
(Without<Aabb>, Without<NoFrustumCulling>),
|
||||
>,
|
||||
) {
|
||||
for (entity, mesh_handle) in &meshes_without_aabb {
|
||||
if let Some(mesh) = meshes.get(&mesh_handle.0) {
|
||||
@ -152,27 +148,15 @@ pub fn calculate_bounds_2d(
|
||||
}
|
||||
}
|
||||
}
|
||||
for (entity, sprite, texture_handle) in &sprites_to_recalculate_aabb {
|
||||
if let Some(size) = sprite
|
||||
.custom_size
|
||||
.or_else(|| images.get(texture_handle).map(|image| image.size_f32()))
|
||||
{
|
||||
let aabb = Aabb {
|
||||
center: (-sprite.anchor.as_vec() * size).extend(0.0).into(),
|
||||
half_extents: (0.5 * size).extend(0.0).into(),
|
||||
};
|
||||
commands.entity(entity).try_insert(aabb);
|
||||
}
|
||||
}
|
||||
for (entity, atlas_sprite, atlas_handle) in &atlases_without_aabb {
|
||||
if let Some(size) = atlas_sprite.custom_size.or_else(|| {
|
||||
atlases
|
||||
.get(atlas_handle)
|
||||
.and_then(|atlas| atlas.textures.get(atlas_sprite.index))
|
||||
.map(|rect| (rect.min - rect.max).abs())
|
||||
for (entity, sprite, texture_handle, atlas) in &sprites_to_recalculate_aabb {
|
||||
if let Some(size) = sprite.custom_size.or_else(|| match atlas {
|
||||
// We default to the texture size for regular sprites
|
||||
None => images.get(texture_handle).map(|image| image.size_f32()),
|
||||
// We default to the drawn rect for atlas sprites
|
||||
Some(atlas) => atlas.texture_rect(&atlases).map(|rect| rect.size()),
|
||||
}) {
|
||||
let aabb = Aabb {
|
||||
center: (-atlas_sprite.anchor.as_vec() * size).extend(0.0).into(),
|
||||
center: (-sprite.anchor.as_vec() * size).extend(0.0).into(),
|
||||
half_extents: (0.5 * size).extend(0.0).into(),
|
||||
};
|
||||
commands.entity(entity).try_insert(aabb);
|
||||
@ -199,7 +183,7 @@ mod test {
|
||||
app.insert_resource(image_assets);
|
||||
let mesh_assets = Assets::<Mesh>::default();
|
||||
app.insert_resource(mesh_assets);
|
||||
let texture_atlas_assets = Assets::<TextureAtlas>::default();
|
||||
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
|
||||
app.insert_resource(texture_atlas_assets);
|
||||
|
||||
// Add system
|
||||
@ -237,7 +221,7 @@ mod test {
|
||||
app.insert_resource(image_assets);
|
||||
let mesh_assets = Assets::<Mesh>::default();
|
||||
app.insert_resource(mesh_assets);
|
||||
let texture_atlas_assets = Assets::<TextureAtlas>::default();
|
||||
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
|
||||
app.insert_resource(texture_atlas_assets);
|
||||
|
||||
// Add system
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::{
|
||||
texture_atlas::{TextureAtlas, TextureAtlasSprite},
|
||||
texture_atlas::{TextureAtlas, TextureAtlasLayout},
|
||||
ComputedTextureSlices, Sprite, SPRITE_SHADER_HANDLE,
|
||||
};
|
||||
use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
|
||||
@ -335,7 +335,7 @@ pub fn extract_sprite_events(
|
||||
pub fn extract_sprites(
|
||||
mut commands: Commands,
|
||||
mut extracted_sprites: ResMut<ExtractedSprites>,
|
||||
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
|
||||
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
|
||||
sprite_query: Extract<
|
||||
Query<(
|
||||
Entity,
|
||||
@ -343,25 +343,17 @@ pub fn extract_sprites(
|
||||
&Sprite,
|
||||
&GlobalTransform,
|
||||
&Handle<Image>,
|
||||
Option<&TextureAtlas>,
|
||||
Option<&ComputedTextureSlices>,
|
||||
)>,
|
||||
>,
|
||||
atlas_query: Extract<
|
||||
Query<(
|
||||
Entity,
|
||||
&ViewVisibility,
|
||||
&TextureAtlasSprite,
|
||||
&GlobalTransform,
|
||||
&Handle<TextureAtlas>,
|
||||
)>,
|
||||
>,
|
||||
) {
|
||||
extracted_sprites.sprites.clear();
|
||||
|
||||
for (entity, view_visibility, sprite, transform, handle, slices) in sprite_query.iter() {
|
||||
for (entity, view_visibility, sprite, transform, handle, sheet, slices) in sprite_query.iter() {
|
||||
if !view_visibility.get() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(slices) = slices {
|
||||
extracted_sprites.sprites.extend(
|
||||
slices
|
||||
@ -369,13 +361,14 @@ pub fn extract_sprites(
|
||||
.map(|e| (commands.spawn_empty().id(), e)),
|
||||
);
|
||||
} else {
|
||||
let rect = sheet.and_then(|s| s.texture_rect(&texture_atlases));
|
||||
// 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.insert(
|
||||
entity,
|
||||
ExtractedSprite {
|
||||
color: sprite.color,
|
||||
transform: *transform,
|
||||
rect: sprite.rect,
|
||||
rect,
|
||||
// Pass the custom size
|
||||
custom_size: sprite.custom_size,
|
||||
flip_x: sprite.flip_x,
|
||||
@ -387,43 +380,6 @@ pub fn extract_sprites(
|
||||
);
|
||||
}
|
||||
}
|
||||
for (entity, view_visibility, atlas_sprite, transform, texture_atlas_handle) in
|
||||
atlas_query.iter()
|
||||
{
|
||||
if !view_visibility.get() {
|
||||
continue;
|
||||
}
|
||||
if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) {
|
||||
let rect = Some(
|
||||
*texture_atlas
|
||||
.textures
|
||||
.get(atlas_sprite.index)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Sprite index {:?} does not exist for texture atlas handle {:?}.",
|
||||
atlas_sprite.index,
|
||||
texture_atlas_handle.id(),
|
||||
)
|
||||
}),
|
||||
);
|
||||
extracted_sprites.sprites.insert(
|
||||
entity,
|
||||
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(),
|
||||
original_entity: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
@ -1,97 +1,84 @@
|
||||
use crate::Anchor;
|
||||
use bevy_asset::{Asset, AssetId, Handle};
|
||||
use bevy_ecs::{component::Component, reflect::ReflectComponent};
|
||||
use bevy_asset::{Asset, AssetId, Assets, Handle};
|
||||
use bevy_ecs::component::Component;
|
||||
use bevy_math::{Rect, Vec2};
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_render::{color::Color, texture::Image};
|
||||
use bevy_render::texture::Image;
|
||||
use bevy_utils::HashMap;
|
||||
|
||||
/// An atlas containing multiple textures (like a spritesheet or a tilemap).
|
||||
/// Stores a map used to lookup the position of a texture in a [`TextureAtlas`].
|
||||
/// This can be used to either use and look up a specific section of a texture, or animate frame-by-frame as a sprite sheet.
|
||||
///
|
||||
/// Optionaly it can store a mapping from sub texture handles to the related area index (see
|
||||
/// [`TextureAtlasBuilder`]).
|
||||
///
|
||||
/// [Example usage animating sprite.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)
|
||||
/// [Example usage loading sprite sheet.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
|
||||
///
|
||||
/// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder
|
||||
#[derive(Asset, Reflect, Debug, Clone)]
|
||||
#[reflect(Debug)]
|
||||
pub struct TextureAtlas {
|
||||
/// The handle to the texture in which the sprites are stored
|
||||
pub texture: Handle<Image>,
|
||||
pub struct TextureAtlasLayout {
|
||||
// TODO: add support to Uniforms derive to write dimensions and sprites to the same buffer
|
||||
pub size: Vec2,
|
||||
/// The specific areas of the atlas where each texture can be found
|
||||
pub textures: Vec<Rect>,
|
||||
/// Mapping from texture handle to index
|
||||
/// Maps from a specific image handle to the index in `textures` where they can be found.
|
||||
///
|
||||
/// This field is set by [`TextureAtlasBuilder`].
|
||||
///
|
||||
/// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder
|
||||
pub(crate) texture_handles: Option<HashMap<AssetId<Image>, usize>>,
|
||||
}
|
||||
|
||||
/// Specifies the rendering properties of a sprite from a sprite sheet.
|
||||
/// Component used to draw a specific section of a texture.
|
||||
///
|
||||
/// This is commonly used as a component within [`SpriteSheetBundle`](crate::bundle::SpriteSheetBundle).
|
||||
#[derive(Component, Debug, Clone, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct TextureAtlasSprite {
|
||||
/// The tint color used to draw the sprite, defaulting to [`Color::WHITE`]
|
||||
pub color: Color,
|
||||
/// Texture index in [`TextureAtlas`]
|
||||
/// It stores a handle to [`TextureAtlasLayout`] and the index of the current section of the atlas.
|
||||
/// The texture atlas contains various *sections* of a given texture, allowing users to have a single
|
||||
/// image file for either sprite animation or global mapping.
|
||||
/// You can change the texture [`index`](Self::index) of the atlas to animate the sprite or dsplay only a *section* of the texture
|
||||
/// for efficient rendering of related game objects.
|
||||
///
|
||||
/// Check the following examples for usage:
|
||||
/// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)
|
||||
/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
|
||||
#[derive(Component, Default, Debug, Clone, Reflect)]
|
||||
pub struct TextureAtlas {
|
||||
/// Texture atlas handle
|
||||
pub layout: Handle<TextureAtlasLayout>,
|
||||
/// Texture atlas section index
|
||||
pub index: usize,
|
||||
/// Whether to flip the sprite in the X axis
|
||||
pub flip_x: bool,
|
||||
/// Whether to flip the sprite in 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 in the atlas
|
||||
pub custom_size: Option<Vec2>,
|
||||
/// [`Anchor`] point of the sprite in the world
|
||||
pub anchor: Anchor,
|
||||
}
|
||||
|
||||
impl Default for TextureAtlasSprite {
|
||||
fn default() -> Self {
|
||||
impl TextureAtlasLayout {
|
||||
/// Create a new empty layout with custom `dimensions`
|
||||
pub fn new_empty(dimensions: Vec2) -> Self {
|
||||
Self {
|
||||
index: 0,
|
||||
color: Color::WHITE,
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
custom_size: None,
|
||||
anchor: Anchor::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextureAtlasSprite {
|
||||
/// Create a new [`TextureAtlasSprite`] with a sprite index,
|
||||
/// it should be valid in the corresponding [`TextureAtlas`]
|
||||
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
|
||||
pub fn new_empty(texture: Handle<Image>, dimensions: Vec2) -> Self {
|
||||
Self {
|
||||
texture,
|
||||
size: dimensions,
|
||||
texture_handles: None,
|
||||
textures: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a [`TextureAtlas`] by splitting a texture into a grid where each
|
||||
/// `tile_size` by `tile_size` grid-cell is one of the textures in the
|
||||
/// Generate a [`TextureAtlasLayout`] as a grid where each
|
||||
/// `tile_size` by `tile_size` grid-cell is one of the *section* in the
|
||||
/// atlas. Grid cells are separated by some `padding`, and the grid starts
|
||||
/// at `offset` pixels from the top left corner. The resulting [`TextureAtlas`] is
|
||||
/// at `offset` pixels from the top left corner. Resulting layout is
|
||||
/// indexed left to right, top to bottom.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `tile_size` - Each layout grid cell size
|
||||
/// * `columns` - Grid column count
|
||||
/// * `rows` - Grid row count
|
||||
/// * `padding` - Optional padding between cells
|
||||
/// * `offset` - Optional global grid offset
|
||||
pub fn from_grid(
|
||||
texture: Handle<Image>,
|
||||
tile_size: Vec2,
|
||||
columns: usize,
|
||||
rows: usize,
|
||||
padding: Option<Vec2>,
|
||||
offset: Option<Vec2>,
|
||||
) -> TextureAtlas {
|
||||
) -> Self {
|
||||
let padding = padding.unwrap_or_default();
|
||||
let offset = offset.unwrap_or_default();
|
||||
let mut sprites = Vec::new();
|
||||
@ -119,37 +106,40 @@ impl TextureAtlas {
|
||||
|
||||
let grid_size = Vec2::new(columns as f32, rows as f32);
|
||||
|
||||
TextureAtlas {
|
||||
Self {
|
||||
size: ((tile_size + current_padding) * grid_size) - current_padding,
|
||||
textures: sprites,
|
||||
texture,
|
||||
texture_handles: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a sprite to the list of textures in the [`TextureAtlas`]
|
||||
/// returns an index to the texture which can be used with [`TextureAtlasSprite`]
|
||||
/// Add a *section* to the list in the layout and returns its index
|
||||
/// which can be used with [`TextureAtlas`]
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `rect` - The section of the atlas that contains the texture to be added,
|
||||
/// from the top-left corner of the texture to the bottom-right corner
|
||||
/// * `rect` - The section of the texture to be added
|
||||
///
|
||||
/// [`TextureAtlas`]: crate::TextureAtlas
|
||||
pub fn add_texture(&mut self, rect: Rect) -> usize {
|
||||
self.textures.push(rect);
|
||||
self.textures.len() - 1
|
||||
}
|
||||
|
||||
/// The number of textures in the [`TextureAtlas`]
|
||||
/// The number of textures in the [`TextureAtlasLayout`]
|
||||
pub fn len(&self) -> usize {
|
||||
self.textures.len()
|
||||
}
|
||||
|
||||
/// Returns `true` if there are no textures in the [`TextureAtlas`]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.textures.is_empty()
|
||||
}
|
||||
|
||||
/// Returns the index of the texture corresponding to the given image handle in the [`TextureAtlas`]
|
||||
/// Retrieves the texture *section* index of the given `texture` handle.
|
||||
///
|
||||
/// This requires the layout to have been built using a [`TextureAtlasBuilder`]
|
||||
///
|
||||
/// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder
|
||||
pub fn get_texture_index(&self, texture: impl Into<AssetId<Image>>) -> Option<usize> {
|
||||
let id = texture.into();
|
||||
self.texture_handles
|
||||
@ -157,3 +147,20 @@ impl TextureAtlas {
|
||||
.and_then(|texture_handles| texture_handles.get(&id).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
impl TextureAtlas {
|
||||
/// Retrieves the current texture [`Rect`] of the sprite sheet according to the section `index`
|
||||
pub fn texture_rect(&self, texture_atlases: &Assets<TextureAtlasLayout>) -> Option<Rect> {
|
||||
let atlas = texture_atlases.get(&self.layout)?;
|
||||
atlas.textures.get(self.index).copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Handle<TextureAtlasLayout>> for TextureAtlas {
|
||||
fn from(texture_atlas: Handle<TextureAtlasLayout>) -> Self {
|
||||
Self {
|
||||
layout: texture_atlas,
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use bevy_asset::Handle;
|
||||
use bevy_asset::{AssetId, Assets};
|
||||
use bevy_log::{debug, error, warn};
|
||||
use bevy_math::{Rect, UVec2, Vec2};
|
||||
@ -13,7 +14,7 @@ use rectangle_pack::{
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::texture_atlas::TextureAtlas;
|
||||
use crate::TextureAtlasLayout;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum TextureAtlasBuilderError {
|
||||
@ -146,12 +147,41 @@ impl TextureAtlasBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes the builder and returns a result with a new texture atlas.
|
||||
/// Consumes the builder, and returns the newly created texture handle and
|
||||
/// the assciated atlas layout.
|
||||
///
|
||||
/// Internally it copies all rectangles from the textures and copies them
|
||||
/// into a new texture which the texture atlas will use. It is not useful to
|
||||
/// hold a strong handle to the texture afterwards else it will exist twice
|
||||
/// in memory.
|
||||
/// into a new texture.
|
||||
/// It is not useful to hold a strong handle to the texture afterwards else
|
||||
/// it will exist twice in memory.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// ```rust
|
||||
/// # use bevy_sprite::prelude::*;
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_asset::*;
|
||||
/// # use bevy_render::prelude::*;
|
||||
///
|
||||
/// fn my_system(mut commands: Commands, mut textures: ResMut<Assets<Image>>, mut layouts: ResMut<Assets<TextureAtlasLayout>>) {
|
||||
/// // Declare your builder
|
||||
/// let mut builder = TextureAtlasBuilder::default();
|
||||
/// // Customize it
|
||||
/// // ...
|
||||
/// // Build your texture and the atlas layout
|
||||
/// let (atlas_layout, texture) = builder.finish(&mut textures).unwrap();
|
||||
/// let layout = layouts.add(atlas_layout);
|
||||
/// // Spawn your sprite
|
||||
/// commands.spawn(SpriteSheetBundle {
|
||||
/// texture,
|
||||
/// atlas: TextureAtlas {
|
||||
/// layout,
|
||||
/// index: 0
|
||||
/// },
|
||||
/// ..Default::default()
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@ -160,7 +190,7 @@ impl TextureAtlasBuilder {
|
||||
pub fn finish(
|
||||
self,
|
||||
textures: &mut Assets<Image>,
|
||||
) -> Result<TextureAtlas, TextureAtlasBuilderError> {
|
||||
) -> Result<(TextureAtlasLayout, Handle<Image>), TextureAtlasBuilderError> {
|
||||
let initial_width = self.initial_size.x as u32;
|
||||
let initial_height = self.initial_size.y as u32;
|
||||
let max_width = self.max_size.x as u32;
|
||||
@ -248,11 +278,14 @@ impl TextureAtlasBuilder {
|
||||
}
|
||||
self.copy_converted_texture(&mut atlas_texture, texture, packed_location);
|
||||
}
|
||||
Ok(TextureAtlas {
|
||||
|
||||
Ok((
|
||||
TextureAtlasLayout {
|
||||
size: atlas_texture.size_f32(),
|
||||
texture: textures.add(atlas_texture),
|
||||
textures: texture_rects,
|
||||
texture_handles: Some(texture_ids),
|
||||
})
|
||||
},
|
||||
textures.add(atlas_texture),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use bevy_render::{
|
||||
render_resource::{Extent3d, TextureDimension, TextureFormat},
|
||||
texture::Image,
|
||||
};
|
||||
use bevy_sprite::{DynamicTextureAtlasBuilder, TextureAtlas};
|
||||
use bevy_sprite::{DynamicTextureAtlasBuilder, TextureAtlasLayout};
|
||||
use bevy_utils::HashMap;
|
||||
|
||||
#[cfg(feature = "subpixel_glyph_atlas")]
|
||||
@ -43,16 +43,17 @@ impl From<Point> for SubpixelOffset {
|
||||
pub struct FontAtlas {
|
||||
pub dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder,
|
||||
pub glyph_to_atlas_index: HashMap<(GlyphId, SubpixelOffset), usize>,
|
||||
pub texture_atlas: Handle<TextureAtlas>,
|
||||
pub texture_atlas: Handle<TextureAtlasLayout>,
|
||||
pub texture: Handle<Image>,
|
||||
}
|
||||
|
||||
impl FontAtlas {
|
||||
pub fn new(
|
||||
textures: &mut Assets<Image>,
|
||||
texture_atlases: &mut Assets<TextureAtlas>,
|
||||
texture_atlases: &mut Assets<TextureAtlasLayout>,
|
||||
size: Vec2,
|
||||
) -> FontAtlas {
|
||||
let atlas_texture = textures.add(Image::new_fill(
|
||||
let texture = textures.add(Image::new_fill(
|
||||
Extent3d {
|
||||
width: size.x as u32,
|
||||
height: size.y as u32,
|
||||
@ -64,11 +65,12 @@ impl FontAtlas {
|
||||
// Need to keep this image CPU persistent in order to add additional glyphs later on
|
||||
RenderAssetPersistencePolicy::Keep,
|
||||
));
|
||||
let texture_atlas = TextureAtlas::new_empty(atlas_texture, size);
|
||||
let texture_atlas = TextureAtlasLayout::new_empty(size);
|
||||
Self {
|
||||
texture_atlas: texture_atlases.add(texture_atlas),
|
||||
glyph_to_atlas_index: HashMap::default(),
|
||||
dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder::new(size, 0),
|
||||
texture,
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,16 +92,18 @@ impl FontAtlas {
|
||||
pub fn add_glyph(
|
||||
&mut self,
|
||||
textures: &mut Assets<Image>,
|
||||
texture_atlases: &mut Assets<TextureAtlas>,
|
||||
texture_atlases: &mut Assets<TextureAtlasLayout>,
|
||||
glyph_id: GlyphId,
|
||||
subpixel_offset: SubpixelOffset,
|
||||
texture: &Image,
|
||||
) -> bool {
|
||||
let texture_atlas = texture_atlases.get_mut(&self.texture_atlas).unwrap();
|
||||
if let Some(index) =
|
||||
self.dynamic_texture_atlas_builder
|
||||
.add_texture(texture_atlas, textures, texture)
|
||||
{
|
||||
if let Some(index) = self.dynamic_texture_atlas_builder.add_texture(
|
||||
texture_atlas,
|
||||
textures,
|
||||
texture,
|
||||
&self.texture,
|
||||
) {
|
||||
self.glyph_to_atlas_index
|
||||
.insert((glyph_id, subpixel_offset), index);
|
||||
true
|
||||
|
@ -6,7 +6,7 @@ use bevy_ecs::prelude::*;
|
||||
use bevy_math::Vec2;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_render::texture::Image;
|
||||
use bevy_sprite::TextureAtlas;
|
||||
use bevy_sprite::TextureAtlasLayout;
|
||||
use bevy_utils::FloatOrd;
|
||||
use bevy_utils::HashMap;
|
||||
|
||||
@ -43,7 +43,8 @@ pub struct FontAtlasSet {
|
||||
|
||||
#[derive(Debug, Clone, Reflect)]
|
||||
pub struct GlyphAtlasInfo {
|
||||
pub texture_atlas: Handle<TextureAtlas>,
|
||||
pub texture_atlas: Handle<TextureAtlasLayout>,
|
||||
pub texture: Handle<Image>,
|
||||
pub glyph_index: usize,
|
||||
}
|
||||
|
||||
@ -72,7 +73,7 @@ impl FontAtlasSet {
|
||||
|
||||
pub fn add_glyph_to_atlas(
|
||||
&mut self,
|
||||
texture_atlases: &mut Assets<TextureAtlas>,
|
||||
texture_atlases: &mut Assets<TextureAtlasLayout>,
|
||||
textures: &mut Assets<Image>,
|
||||
outlined_glyph: OutlinedGlyph,
|
||||
) -> Result<GlyphAtlasInfo, TextError> {
|
||||
@ -145,10 +146,17 @@ impl FontAtlasSet {
|
||||
.find_map(|atlas| {
|
||||
atlas
|
||||
.get_glyph_index(glyph_id, position.into())
|
||||
.map(|glyph_index| (glyph_index, atlas.texture_atlas.clone_weak()))
|
||||
.map(|glyph_index| {
|
||||
(
|
||||
glyph_index,
|
||||
atlas.texture_atlas.clone_weak(),
|
||||
atlas.texture.clone_weak(),
|
||||
)
|
||||
})
|
||||
.map(|(glyph_index, texture_atlas)| GlyphAtlasInfo {
|
||||
})
|
||||
.map(|(glyph_index, texture_atlas, texture)| GlyphAtlasInfo {
|
||||
texture_atlas,
|
||||
texture,
|
||||
glyph_index,
|
||||
})
|
||||
})
|
||||
|
@ -3,7 +3,7 @@ use bevy_asset::{AssetId, Assets};
|
||||
use bevy_math::{Rect, Vec2};
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_render::texture::Image;
|
||||
use bevy_sprite::TextureAtlas;
|
||||
use bevy_sprite::TextureAtlasLayout;
|
||||
use bevy_utils::tracing::warn;
|
||||
use glyph_brush_layout::{
|
||||
BuiltInLineBreaker, FontId, GlyphPositioner, Layout, SectionGeometry, SectionGlyph,
|
||||
@ -60,7 +60,7 @@ impl GlyphBrush {
|
||||
sections: &[SectionText],
|
||||
font_atlas_sets: &mut FontAtlasSets,
|
||||
fonts: &Assets<Font>,
|
||||
texture_atlases: &mut Assets<TextureAtlas>,
|
||||
texture_atlases: &mut Assets<TextureAtlasLayout>,
|
||||
textures: &mut Assets<Image>,
|
||||
text_settings: &TextSettings,
|
||||
font_atlas_warning: &mut FontAtlasWarning,
|
||||
|
@ -12,7 +12,7 @@ use bevy_math::Vec2;
|
||||
use bevy_reflect::prelude::ReflectDefault;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_render::texture::Image;
|
||||
use bevy_sprite::TextureAtlas;
|
||||
use bevy_sprite::TextureAtlasLayout;
|
||||
use bevy_utils::HashMap;
|
||||
use glyph_brush_layout::{FontId, GlyphPositioner, SectionGeometry, SectionText, ToSectionText};
|
||||
|
||||
@ -51,7 +51,7 @@ impl TextPipeline {
|
||||
linebreak_behavior: BreakLineOn,
|
||||
bounds: Vec2,
|
||||
font_atlas_sets: &mut FontAtlasSets,
|
||||
texture_atlases: &mut Assets<TextureAtlas>,
|
||||
texture_atlases: &mut Assets<TextureAtlasLayout>,
|
||||
textures: &mut Assets<Image>,
|
||||
text_settings: &TextSettings,
|
||||
font_atlas_warning: &mut FontAtlasWarning,
|
||||
|
@ -21,7 +21,7 @@ use bevy_render::{
|
||||
view::{InheritedVisibility, ViewVisibility, Visibility},
|
||||
Extract,
|
||||
};
|
||||
use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, TextureAtlas};
|
||||
use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, TextureAtlasLayout};
|
||||
use bevy_transform::prelude::{GlobalTransform, Transform};
|
||||
use bevy_utils::HashSet;
|
||||
use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged};
|
||||
@ -83,7 +83,7 @@ pub struct Text2dBundle {
|
||||
pub fn extract_text2d_sprite(
|
||||
mut commands: Commands,
|
||||
mut extracted_sprites: ResMut<ExtractedSprites>,
|
||||
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
|
||||
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
|
||||
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
|
||||
text2d_query: Extract<
|
||||
Query<(
|
||||
@ -138,7 +138,7 @@ pub fn extract_text2d_sprite(
|
||||
color,
|
||||
rect: Some(atlas.textures[atlas_info.glyph_index]),
|
||||
custom_size: None,
|
||||
image_handle_id: atlas.texture.id(),
|
||||
image_handle_id: atlas_info.texture.id(),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
anchor: Anchor::Center.as_vec(),
|
||||
@ -166,7 +166,7 @@ pub fn update_text2d_layout(
|
||||
mut font_atlas_warning: ResMut<FontAtlasWarning>,
|
||||
windows: Query<&Window, With<PrimaryWindow>>,
|
||||
mut scale_factor_changed: EventReader<WindowScaleFactorChanged>,
|
||||
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
||||
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
|
||||
mut font_atlas_sets: ResMut<FontAtlasSets>,
|
||||
mut text_pipeline: ResMut<TextPipeline>,
|
||||
mut text_query: Query<(Entity, Ref<Text>, Ref<Text2dBounds>, &mut TextLayoutInfo)>,
|
||||
|
@ -118,7 +118,6 @@ impl Plugin for UiPlugin {
|
||||
.register_type::<UiImageSize>()
|
||||
.register_type::<UiRect>()
|
||||
.register_type::<UiScale>()
|
||||
.register_type::<UiTextureAtlasImage>()
|
||||
.register_type::<Val>()
|
||||
.register_type::<BorderColor>()
|
||||
.register_type::<widget::Button>()
|
||||
@ -151,8 +150,7 @@ impl Plugin for UiPlugin {
|
||||
.ambiguous_with(bevy_text::update_text2d_layout)
|
||||
// We assume Text is on disjoint UI entities to UiImage and UiTextureAtlasImage
|
||||
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
|
||||
.ambiguous_with(widget::update_image_content_size_system)
|
||||
.ambiguous_with(widget::update_atlas_content_size_system),
|
||||
.ambiguous_with(widget::update_image_content_size_system),
|
||||
widget::text_system
|
||||
.after(UiSystem::Layout)
|
||||
.after(bevy_text::remove_dropped_font_atlas_sets)
|
||||
@ -163,11 +161,7 @@ impl Plugin for UiPlugin {
|
||||
#[cfg(feature = "bevy_text")]
|
||||
app.add_plugins(accessibility::AccessibilityPlugin);
|
||||
app.add_systems(PostUpdate, {
|
||||
let system = widget::update_image_content_size_system
|
||||
.before(UiSystem::Layout)
|
||||
// We assume UiImage, UiTextureAtlasImage are disjoint UI entities
|
||||
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
|
||||
.ambiguous_with(widget::update_atlas_content_size_system);
|
||||
let system = widget::update_image_content_size_system.before(UiSystem::Layout);
|
||||
// Potential conflicts: `Assets<Image>`
|
||||
// They run independently since `widget::image_node_system` will only ever observe
|
||||
// its own UiImage, and `widget::text_system` & `bevy_text::update_text2d_layout`
|
||||
@ -183,11 +177,7 @@ impl Plugin for UiPlugin {
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
(
|
||||
(
|
||||
widget::update_atlas_content_size_system,
|
||||
update_target_camera_system,
|
||||
)
|
||||
.before(UiSystem::Layout),
|
||||
update_target_camera_system.before(UiSystem::Layout),
|
||||
apply_deferred
|
||||
.after(update_target_camera_system)
|
||||
.before(UiSystem::Layout),
|
||||
|
@ -5,7 +5,7 @@ use crate::widget::TextFlags;
|
||||
use crate::{
|
||||
widget::{Button, UiImageSize},
|
||||
BackgroundColor, BorderColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage,
|
||||
UiMaterial, UiTextureAtlasImage, ZIndex,
|
||||
UiMaterial, ZIndex,
|
||||
};
|
||||
use bevy_asset::Handle;
|
||||
use bevy_ecs::bundle::Bundle;
|
||||
@ -115,6 +115,8 @@ pub struct ImageBundle {
|
||||
}
|
||||
|
||||
/// A UI node that is a texture atlas sprite
|
||||
///
|
||||
/// This bundle is identical to [`ImageBundle`] with an additional [`TextureAtlas`] component.
|
||||
#[derive(Bundle, Debug, Default)]
|
||||
pub struct AtlasImageBundle {
|
||||
/// Describes the logical size of the node
|
||||
@ -128,10 +130,10 @@ pub struct AtlasImageBundle {
|
||||
///
|
||||
/// Combines with `UiImage` to tint the provided image.
|
||||
pub background_color: BackgroundColor,
|
||||
/// The image of the node
|
||||
pub image: UiImage,
|
||||
/// A handle to the texture atlas to use for this Ui Node
|
||||
pub texture_atlas: Handle<TextureAtlas>,
|
||||
/// The descriptor for which sprite to use from the given texture atlas
|
||||
pub texture_atlas_image: UiTextureAtlasImage,
|
||||
pub texture_atlas: TextureAtlas,
|
||||
/// Whether this node should block interaction with lower nodes
|
||||
pub focus_policy: FocusPolicy,
|
||||
/// The size of the image in pixels
|
||||
|
@ -8,13 +8,13 @@ use bevy_render::{
|
||||
render_phase::PhaseItem, render_resource::BindGroupEntries, view::ViewVisibility,
|
||||
ExtractSchedule, Render,
|
||||
};
|
||||
use bevy_sprite::{SpriteAssetEvents, TextureAtlas};
|
||||
pub use pipeline::*;
|
||||
pub use render_pass::*;
|
||||
pub use ui_material_pipeline::*;
|
||||
|
||||
use crate::{
|
||||
BackgroundColor, BorderColor, CalculatedClip, ContentSize, Node, Style, UiImage, UiScale,
|
||||
UiTextureAtlasImage, Val,
|
||||
BackgroundColor, BorderColor, CalculatedClip, ContentSize, Node, Style, UiImage, UiScale, Val,
|
||||
};
|
||||
use crate::{DefaultUiCamera, Outline, TargetCamera};
|
||||
|
||||
@ -34,7 +34,8 @@ use bevy_render::{
|
||||
view::{ExtractedView, ViewUniforms},
|
||||
Extract, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_sprite::{SpriteAssetEvents, TextureAtlas};
|
||||
#[cfg(feature = "bevy_text")]
|
||||
use bevy_sprite::TextureAtlasLayout;
|
||||
#[cfg(feature = "bevy_text")]
|
||||
use bevy_text::{PositionedGlyph, Text, TextLayoutInfo};
|
||||
use bevy_transform::components::GlobalTransform;
|
||||
@ -82,9 +83,6 @@ pub fn build_ui_render(app: &mut App) {
|
||||
extract_default_ui_camera_view::<Camera2d>,
|
||||
extract_default_ui_camera_view::<Camera3d>,
|
||||
extract_uinodes.in_set(RenderUiSystem::ExtractNode),
|
||||
extract_atlas_uinodes
|
||||
.in_set(RenderUiSystem::ExtractAtlasNode)
|
||||
.after(RenderUiSystem::ExtractNode),
|
||||
extract_uinode_borders.after(RenderUiSystem::ExtractAtlasNode),
|
||||
#[cfg(feature = "bevy_text")]
|
||||
extract_text_uinodes.after(RenderUiSystem::ExtractAtlasNode),
|
||||
@ -174,99 +172,6 @@ pub struct ExtractedUiNodes {
|
||||
pub uinodes: EntityHashMap<Entity, ExtractedUiNode>,
|
||||
}
|
||||
|
||||
pub fn extract_atlas_uinodes(
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
images: Extract<Res<Assets<Image>>>,
|
||||
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
|
||||
default_ui_camera: Extract<DefaultUiCamera>,
|
||||
uinode_query: Extract<
|
||||
Query<
|
||||
(
|
||||
Entity,
|
||||
&Node,
|
||||
&GlobalTransform,
|
||||
&BackgroundColor,
|
||||
&ViewVisibility,
|
||||
Option<&CalculatedClip>,
|
||||
&Handle<TextureAtlas>,
|
||||
&UiTextureAtlasImage,
|
||||
Option<&TargetCamera>,
|
||||
),
|
||||
Without<UiImage>,
|
||||
>,
|
||||
>,
|
||||
) {
|
||||
for (
|
||||
entity,
|
||||
uinode,
|
||||
transform,
|
||||
color,
|
||||
view_visibility,
|
||||
clip,
|
||||
texture_atlas_handle,
|
||||
atlas_image,
|
||||
camera,
|
||||
) in uinode_query.iter()
|
||||
{
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
// Skip invisible and completely transparent nodes
|
||||
if !view_visibility.get() || color.0.is_fully_transparent() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let (mut atlas_rect, mut atlas_size, image) =
|
||||
if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) {
|
||||
let atlas_rect = *texture_atlas
|
||||
.textures
|
||||
.get(atlas_image.index)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Atlas index {:?} does not exist for texture atlas handle {:?}.",
|
||||
atlas_image.index,
|
||||
texture_atlas_handle.id(),
|
||||
)
|
||||
});
|
||||
(
|
||||
atlas_rect,
|
||||
texture_atlas.size,
|
||||
texture_atlas.texture.clone(),
|
||||
)
|
||||
} else {
|
||||
// Atlas not present in assets resource (should this warn the user?)
|
||||
continue;
|
||||
};
|
||||
|
||||
// Skip loading images
|
||||
if !images.contains(&image) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let scale = uinode.size() / atlas_rect.size();
|
||||
atlas_rect.min *= scale;
|
||||
atlas_rect.max *= scale;
|
||||
atlas_size *= scale;
|
||||
|
||||
extracted_uinodes.uinodes.insert(
|
||||
entity,
|
||||
ExtractedUiNode {
|
||||
stack_index: uinode.stack_index,
|
||||
transform: transform.compute_matrix(),
|
||||
color: color.0,
|
||||
rect: atlas_rect,
|
||||
clip: clip.map(|clip| clip.clip),
|
||||
image: image.id(),
|
||||
atlas_size: Some(atlas_size),
|
||||
flip_x: atlas_image.flip_x,
|
||||
flip_y: atlas_image.flip_y,
|
||||
camera_entity,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_border_thickness(value: Val, parent_width: f32, viewport_size: Vec2) -> f32 {
|
||||
match value {
|
||||
Val::Auto => 0.,
|
||||
@ -495,10 +400,10 @@ pub fn extract_uinode_outlines(
|
||||
pub fn extract_uinodes(
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
images: Extract<Res<Assets<Image>>>,
|
||||
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
|
||||
default_ui_camera: Extract<DefaultUiCamera>,
|
||||
uinode_query: Extract<
|
||||
Query<
|
||||
(
|
||||
Query<(
|
||||
Entity,
|
||||
&Node,
|
||||
&GlobalTransform,
|
||||
@ -506,13 +411,12 @@ pub fn extract_uinodes(
|
||||
Option<&UiImage>,
|
||||
&ViewVisibility,
|
||||
Option<&CalculatedClip>,
|
||||
Option<&TextureAtlas>,
|
||||
Option<&TargetCamera>,
|
||||
),
|
||||
Without<UiTextureAtlasImage>,
|
||||
>,
|
||||
)>,
|
||||
>,
|
||||
) {
|
||||
for (entity, uinode, transform, color, maybe_image, view_visibility, clip, camera) in
|
||||
for (entity, uinode, transform, color, maybe_image, view_visibility, clip, atlas, camera) in
|
||||
uinode_query.iter()
|
||||
{
|
||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
||||
@ -534,19 +438,39 @@ pub fn extract_uinodes(
|
||||
(AssetId::default(), false, false)
|
||||
};
|
||||
|
||||
let (rect, atlas_size) = match atlas {
|
||||
Some(atlas) => {
|
||||
let Some(layout) = texture_atlases.get(&atlas.layout) else {
|
||||
// Atlas not present in assets resource (should this warn the user?)
|
||||
continue;
|
||||
};
|
||||
let mut atlas_rect = layout.textures[atlas.index];
|
||||
let mut atlas_size = layout.size;
|
||||
let scale = uinode.size() / atlas_rect.size();
|
||||
atlas_rect.min *= scale;
|
||||
atlas_rect.max *= scale;
|
||||
atlas_size *= scale;
|
||||
(atlas_rect, Some(atlas_size))
|
||||
}
|
||||
None => (
|
||||
Rect {
|
||||
min: Vec2::ZERO,
|
||||
max: uinode.calculated_size,
|
||||
},
|
||||
None,
|
||||
),
|
||||
};
|
||||
|
||||
extracted_uinodes.uinodes.insert(
|
||||
entity,
|
||||
ExtractedUiNode {
|
||||
stack_index: uinode.stack_index,
|
||||
transform: transform.compute_matrix(),
|
||||
color: color.0,
|
||||
rect: Rect {
|
||||
min: Vec2::ZERO,
|
||||
max: uinode.calculated_size,
|
||||
},
|
||||
rect,
|
||||
clip: clip.map(|clip| clip.clip),
|
||||
image,
|
||||
atlas_size: None,
|
||||
atlas_size,
|
||||
flip_x,
|
||||
flip_y,
|
||||
camera_entity,
|
||||
@ -635,7 +559,7 @@ pub fn extract_text_uinodes(
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
camera_query: Extract<Query<(Entity, &Camera)>>,
|
||||
default_ui_camera: Extract<DefaultUiCamera>,
|
||||
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
|
||||
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
|
||||
ui_scale: Extract<Res<UiScale>>,
|
||||
uinode_query: Extract<
|
||||
Query<(
|
||||
@ -708,7 +632,7 @@ pub fn extract_text_uinodes(
|
||||
* Mat4::from_translation(position.extend(0.) * inverse_scale_factor),
|
||||
color,
|
||||
rect,
|
||||
image: atlas.texture.id(),
|
||||
image: atlas_info.texture.id(),
|
||||
atlas_size: Some(atlas.size * inverse_scale_factor),
|
||||
clip: clip.map(|clip| clip.clip),
|
||||
flip_x: false,
|
||||
|
@ -1608,18 +1608,6 @@ impl From<Color> for BackgroundColor {
|
||||
}
|
||||
}
|
||||
|
||||
/// The atlas sprite to be used in a UI Texture Atlas Node
|
||||
#[derive(Component, Clone, Debug, Reflect, Default)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct UiTextureAtlasImage {
|
||||
/// Texture index in the TextureAtlas
|
||||
pub index: usize,
|
||||
/// Whether to flip the sprite in the X axis
|
||||
pub flip_x: bool,
|
||||
/// Whether to flip the sprite in the Y axis
|
||||
pub flip_y: bool,
|
||||
}
|
||||
|
||||
/// The border color of the UI node.
|
||||
#[derive(Component, Copy, Clone, Debug, Reflect)]
|
||||
#[reflect(Component, Default)]
|
||||
|
@ -1,7 +1,5 @@
|
||||
use crate::{
|
||||
measurement::AvailableSpace, ContentSize, Measure, Node, UiImage, UiScale, UiTextureAtlasImage,
|
||||
};
|
||||
use bevy_asset::{Assets, Handle};
|
||||
use crate::{measurement::AvailableSpace, ContentSize, Measure, Node, UiImage, UiScale};
|
||||
use bevy_asset::Assets;
|
||||
|
||||
use bevy_ecs::change_detection::DetectChanges;
|
||||
use bevy_ecs::query::Without;
|
||||
@ -14,7 +12,7 @@ use bevy_ecs::{
|
||||
use bevy_math::Vec2;
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_render::texture::Image;
|
||||
use bevy_sprite::TextureAtlas;
|
||||
use bevy_sprite::{TextureAtlas, TextureAtlasLayout};
|
||||
use bevy_window::{PrimaryWindow, Window};
|
||||
|
||||
/// The size of the image's texture
|
||||
@ -82,48 +80,15 @@ pub fn update_image_content_size_system(
|
||||
windows: Query<&Window, With<PrimaryWindow>>,
|
||||
ui_scale: Res<UiScale>,
|
||||
textures: Res<Assets<Image>>,
|
||||
mut query: Query<(&mut ContentSize, &UiImage, &mut UiImageSize), UpdateImageFilter>,
|
||||
) {
|
||||
let combined_scale_factor = windows
|
||||
.get_single()
|
||||
.map(|window| window.resolution.scale_factor())
|
||||
.unwrap_or(1.)
|
||||
* ui_scale.0;
|
||||
|
||||
for (mut content_size, image, mut image_size) in &mut query {
|
||||
if let Some(texture) = textures.get(&image.texture) {
|
||||
let size = texture.size_f32();
|
||||
// Update only if size or scale factor has changed to avoid needless layout calculations
|
||||
if size != image_size.size
|
||||
|| combined_scale_factor != *previous_combined_scale_factor
|
||||
|| content_size.is_added()
|
||||
{
|
||||
image_size.size = size;
|
||||
content_size.set(ImageMeasure {
|
||||
// multiply the image size by the scale factor to get the physical size
|
||||
size: size * combined_scale_factor,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*previous_combined_scale_factor = combined_scale_factor;
|
||||
}
|
||||
|
||||
/// Updates content size of the node based on the texture atlas sprite
|
||||
pub fn update_atlas_content_size_system(
|
||||
mut previous_combined_scale_factor: Local<f32>,
|
||||
windows: Query<&Window, With<PrimaryWindow>>,
|
||||
ui_scale: Res<UiScale>,
|
||||
atlases: Res<Assets<TextureAtlas>>,
|
||||
mut atlas_query: Query<
|
||||
atlases: Res<Assets<TextureAtlasLayout>>,
|
||||
mut query: Query<
|
||||
(
|
||||
&mut ContentSize,
|
||||
&Handle<TextureAtlas>,
|
||||
&UiTextureAtlasImage,
|
||||
&UiImage,
|
||||
&mut UiImageSize,
|
||||
Option<&TextureAtlas>,
|
||||
),
|
||||
(UpdateImageFilter, Without<UiImage>),
|
||||
UpdateImageFilter,
|
||||
>,
|
||||
) {
|
||||
let combined_scale_factor = windows
|
||||
@ -132,9 +97,11 @@ pub fn update_atlas_content_size_system(
|
||||
.unwrap_or(1.)
|
||||
* ui_scale.0;
|
||||
|
||||
for (mut content_size, atlas, atlas_image, mut image_size) in &mut atlas_query {
|
||||
if let Some(atlas) = atlases.get(atlas) {
|
||||
let size = atlas.textures[atlas_image.index].size();
|
||||
for (mut content_size, image, mut image_size, atlas_image) in &mut query {
|
||||
if let Some(size) = match atlas_image {
|
||||
Some(atlas) => atlas.texture_rect(&atlases).map(|t| t.size()),
|
||||
None => textures.get(&image.texture).map(|t| t.size_f32()),
|
||||
} {
|
||||
// Update only if size or scale factor has changed to avoid needless layout calculations
|
||||
if size != image_size.size
|
||||
|| combined_scale_factor != *previous_combined_scale_factor
|
||||
|
@ -10,7 +10,7 @@ use bevy_ecs::{
|
||||
use bevy_math::Vec2;
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_render::texture::Image;
|
||||
use bevy_sprite::TextureAtlas;
|
||||
use bevy_sprite::TextureAtlasLayout;
|
||||
use bevy_text::{
|
||||
scale_value, BreakLineOn, Font, FontAtlasSets, FontAtlasWarning, Text, TextError,
|
||||
TextLayoutInfo, TextMeasureInfo, TextPipeline, TextSettings, YAxisOrientation,
|
||||
@ -155,7 +155,7 @@ fn queue_text(
|
||||
text_pipeline: &mut TextPipeline,
|
||||
font_atlas_warning: &mut FontAtlasWarning,
|
||||
font_atlas_sets: &mut FontAtlasSets,
|
||||
texture_atlases: &mut Assets<TextureAtlas>,
|
||||
texture_atlases: &mut Assets<TextureAtlasLayout>,
|
||||
textures: &mut Assets<Image>,
|
||||
text_settings: &TextSettings,
|
||||
scale_factor: f32,
|
||||
@ -226,7 +226,7 @@ pub fn text_system(
|
||||
text_settings: Res<TextSettings>,
|
||||
mut font_atlas_warning: ResMut<FontAtlasWarning>,
|
||||
ui_scale: Res<UiScale>,
|
||||
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
||||
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
|
||||
mut font_atlas_sets: ResMut<FontAtlasSets>,
|
||||
mut text_pipeline: ResMut<TextPipeline>,
|
||||
mut text_query: Query<(Ref<Node>, &Text, &mut TextLayoutInfo, &mut TextFlags)>,
|
||||
|
@ -22,19 +22,15 @@ struct AnimationTimer(Timer);
|
||||
|
||||
fn animate_sprite(
|
||||
time: Res<Time>,
|
||||
mut query: Query<(
|
||||
&AnimationIndices,
|
||||
&mut AnimationTimer,
|
||||
&mut TextureAtlasSprite,
|
||||
)>,
|
||||
mut query: Query<(&AnimationIndices, &mut AnimationTimer, &mut TextureAtlas)>,
|
||||
) {
|
||||
for (indices, mut timer, mut sprite) in &mut query {
|
||||
for (indices, mut timer, mut atlas) in &mut query {
|
||||
timer.tick(time.delta());
|
||||
if timer.just_finished() {
|
||||
sprite.index = if sprite.index == indices.last {
|
||||
atlas.index = if atlas.index == indices.last {
|
||||
indices.first
|
||||
} else {
|
||||
sprite.index + 1
|
||||
atlas.index + 1
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -43,19 +39,21 @@ fn animate_sprite(
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
||||
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
|
||||
) {
|
||||
let texture_handle = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png");
|
||||
let texture_atlas =
|
||||
TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1, None, None);
|
||||
let texture_atlas_handle = texture_atlases.add(texture_atlas);
|
||||
let texture = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png");
|
||||
let atlas = TextureAtlasLayout::from_grid(Vec2::new(24.0, 24.0), 7, 1, None, None);
|
||||
let texture_atlas = texture_atlases.add(atlas);
|
||||
// Use only the subset of sprites in the sheet that make up the run animation
|
||||
let animation_indices = AnimationIndices { first: 1, last: 6 };
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
commands.spawn((
|
||||
SpriteSheetBundle {
|
||||
texture_atlas: texture_atlas_handle,
|
||||
sprite: TextureAtlasSprite::new(animation_indices.first),
|
||||
texture,
|
||||
atlas: TextureAtlas {
|
||||
layout: texture_atlas,
|
||||
index: animation_indices.first,
|
||||
},
|
||||
transform: Transform::from_scale(Vec3::splat(6.0)),
|
||||
..default()
|
||||
},
|
||||
|
@ -52,15 +52,15 @@ fn setup(
|
||||
mut commands: Commands,
|
||||
rpg_sprite_handles: Res<RpgSpriteFolder>,
|
||||
asset_server: Res<AssetServer>,
|
||||
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
|
||||
loaded_folders: Res<Assets<LoadedFolder>>,
|
||||
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
||||
mut textures: ResMut<Assets<Image>>,
|
||||
) {
|
||||
let loaded_folder = loaded_folders.get(&rpg_sprite_handles.0).unwrap();
|
||||
|
||||
// create texture atlases with different padding and sampling
|
||||
|
||||
let texture_atlas_linear = create_texture_atlas(
|
||||
let (texture_atlas_linear, linear_texture) = create_texture_atlas(
|
||||
loaded_folder,
|
||||
None,
|
||||
Some(ImageSampler::linear()),
|
||||
@ -68,7 +68,7 @@ fn setup(
|
||||
);
|
||||
let atlas_linear_handle = texture_atlases.add(texture_atlas_linear.clone());
|
||||
|
||||
let texture_atlas_nearest = create_texture_atlas(
|
||||
let (texture_atlas_nearest, nearest_texture) = create_texture_atlas(
|
||||
loaded_folder,
|
||||
None,
|
||||
Some(ImageSampler::nearest()),
|
||||
@ -76,7 +76,7 @@ fn setup(
|
||||
);
|
||||
let atlas_nearest_handle = texture_atlases.add(texture_atlas_nearest);
|
||||
|
||||
let texture_atlas_linear_padded = create_texture_atlas(
|
||||
let (texture_atlas_linear_padded, linear_padded_texture) = create_texture_atlas(
|
||||
loaded_folder,
|
||||
Some(UVec2::new(6, 6)),
|
||||
Some(ImageSampler::linear()),
|
||||
@ -84,7 +84,7 @@ fn setup(
|
||||
);
|
||||
let atlas_linear_padded_handle = texture_atlases.add(texture_atlas_linear_padded.clone());
|
||||
|
||||
let texture_atlas_nearest_padded = create_texture_atlas(
|
||||
let (texture_atlas_nearest_padded, nearest_padded_texture) = create_texture_atlas(
|
||||
loaded_folder,
|
||||
Some(UVec2::new(6, 6)),
|
||||
Some(ImageSampler::nearest()),
|
||||
@ -99,7 +99,7 @@ fn setup(
|
||||
|
||||
// draw unpadded texture atlas
|
||||
commands.spawn(SpriteBundle {
|
||||
texture: texture_atlas_linear_padded.texture.clone(),
|
||||
texture: linear_texture.clone(),
|
||||
transform: Transform {
|
||||
translation: Vec3::new(-250.0, -130.0, 0.0),
|
||||
scale: Vec3::splat(0.8),
|
||||
@ -110,7 +110,7 @@ fn setup(
|
||||
|
||||
// draw padded texture atlas
|
||||
commands.spawn(SpriteBundle {
|
||||
texture: texture_atlas_linear_padded.texture,
|
||||
texture: linear_padded_texture.clone(),
|
||||
transform: Transform {
|
||||
translation: Vec3::new(250.0, -130.0, 0.0),
|
||||
scale: Vec3::splat(0.8),
|
||||
@ -153,11 +153,21 @@ fn setup(
|
||||
.unwrap();
|
||||
|
||||
// configuration array to render sprites through iteration
|
||||
let configurations: [(&str, Handle<TextureAtlas>, f32); 4] = [
|
||||
("Linear", atlas_linear_handle, -350.0),
|
||||
("Nearest", atlas_nearest_handle, -150.0),
|
||||
("Linear", atlas_linear_padded_handle, 150.0),
|
||||
("Nearest", atlas_nearest_padded_handle, 350.0),
|
||||
let configurations: [(&str, Handle<TextureAtlasLayout>, Handle<Image>, f32); 4] = [
|
||||
("Linear", atlas_linear_handle, linear_texture, -350.0),
|
||||
("Nearest", atlas_nearest_handle, nearest_texture, -150.0),
|
||||
(
|
||||
"Linear",
|
||||
atlas_linear_padded_handle,
|
||||
linear_padded_texture,
|
||||
150.0,
|
||||
),
|
||||
(
|
||||
"Nearest",
|
||||
atlas_nearest_padded_handle,
|
||||
nearest_padded_texture,
|
||||
350.0,
|
||||
),
|
||||
];
|
||||
|
||||
// label text style
|
||||
@ -169,9 +179,15 @@ fn setup(
|
||||
|
||||
let base_y = 170.0; // y position of the sprites
|
||||
|
||||
for (sampling, atlas_handle, x) in configurations {
|
||||
for (sampling, atlas_handle, image_handle, x) in configurations {
|
||||
// render a sprite from the texture_atlas
|
||||
create_sprite_from_atlas(&mut commands, (x, base_y, 0.0), vendor_index, atlas_handle);
|
||||
create_sprite_from_atlas(
|
||||
&mut commands,
|
||||
(x, base_y, 0.0),
|
||||
vendor_index,
|
||||
atlas_handle,
|
||||
image_handle,
|
||||
);
|
||||
|
||||
// render a label to indicate the sampling setting
|
||||
create_label(
|
||||
@ -190,7 +206,7 @@ fn create_texture_atlas(
|
||||
padding: Option<UVec2>,
|
||||
sampling: Option<ImageSampler>,
|
||||
textures: &mut ResMut<Assets<Image>>,
|
||||
) -> TextureAtlas {
|
||||
) -> (TextureAtlasLayout, Handle<Image>) {
|
||||
// Build a `TextureAtlas` using the individual sprites
|
||||
let mut texture_atlas_builder =
|
||||
TextureAtlasBuilder::default().padding(padding.unwrap_or_default());
|
||||
@ -207,13 +223,13 @@ fn create_texture_atlas(
|
||||
texture_atlas_builder.add_texture(id, texture);
|
||||
}
|
||||
|
||||
let texture_atlas = texture_atlas_builder.finish(textures).unwrap();
|
||||
let (texture_atlas, texture) = texture_atlas_builder.finish(textures).unwrap();
|
||||
|
||||
// Update the sampling settings of the texture atlas
|
||||
let image = textures.get_mut(&texture_atlas.texture).unwrap();
|
||||
let image = textures.get_mut(&texture).unwrap();
|
||||
image.sampler = sampling.unwrap_or_default();
|
||||
|
||||
texture_atlas
|
||||
(texture_atlas, texture)
|
||||
}
|
||||
|
||||
/// Create and spawn a sprite from a texture atlas
|
||||
@ -221,7 +237,8 @@ fn create_sprite_from_atlas(
|
||||
commands: &mut Commands,
|
||||
translation: (f32, f32, f32),
|
||||
sprite_index: usize,
|
||||
atlas_handle: Handle<TextureAtlas>,
|
||||
atlas_handle: Handle<TextureAtlasLayout>,
|
||||
texture: Handle<Image>,
|
||||
) {
|
||||
commands.spawn(SpriteSheetBundle {
|
||||
transform: Transform {
|
||||
@ -229,8 +246,11 @@ fn create_sprite_from_atlas(
|
||||
scale: Vec3::splat(3.0),
|
||||
..default()
|
||||
},
|
||||
sprite: TextureAtlasSprite::new(sprite_index),
|
||||
texture_atlas: atlas_handle,
|
||||
texture,
|
||||
atlas: TextureAtlas {
|
||||
index: sprite_index,
|
||||
layout: atlas_handle,
|
||||
},
|
||||
..default()
|
||||
});
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ fn main() {
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
assets: Res<AssetServer>,
|
||||
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
||||
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
|
||||
) {
|
||||
warn!(include_str!("warning_string.txt"));
|
||||
|
||||
@ -61,8 +61,7 @@ fn setup(
|
||||
let half_y = (map_size.y / 2.0) as i32;
|
||||
|
||||
let texture_handle = assets.load("textures/rpg/chars/gabe/gabe-idle-run.png");
|
||||
let texture_atlas =
|
||||
TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1, None, None);
|
||||
let texture_atlas = TextureAtlasLayout::from_grid(Vec2::new(24.0, 24.0), 7, 1, None, None);
|
||||
let texture_atlas_handle = texture_atlases.add(texture_atlas);
|
||||
|
||||
// Spawns the camera
|
||||
@ -81,13 +80,17 @@ fn setup(
|
||||
|
||||
commands.spawn((
|
||||
SpriteSheetBundle {
|
||||
texture_atlas: texture_atlas_handle.clone(),
|
||||
texture: texture_handle.clone(),
|
||||
atlas: TextureAtlas {
|
||||
layout: texture_atlas_handle.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
transform: Transform {
|
||||
translation,
|
||||
rotation,
|
||||
scale,
|
||||
},
|
||||
sprite: TextureAtlasSprite {
|
||||
sprite: Sprite {
|
||||
custom_size: Some(tile_size),
|
||||
..default()
|
||||
},
|
||||
@ -112,18 +115,14 @@ struct AnimationTimer(Timer);
|
||||
|
||||
fn animate_sprite(
|
||||
time: Res<Time>,
|
||||
texture_atlases: Res<Assets<TextureAtlas>>,
|
||||
mut query: Query<(
|
||||
&mut AnimationTimer,
|
||||
&mut TextureAtlasSprite,
|
||||
&Handle<TextureAtlas>,
|
||||
)>,
|
||||
texture_atlases: Res<Assets<TextureAtlasLayout>>,
|
||||
mut query: Query<(&mut AnimationTimer, &mut TextureAtlas)>,
|
||||
) {
|
||||
for (mut timer, mut sprite, texture_atlas_handle) in query.iter_mut() {
|
||||
for (mut timer, mut sheet) 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();
|
||||
let texture_atlas = texture_atlases.get(&sheet.layout).unwrap();
|
||||
sheet.index = (sheet.index + 1) % texture_atlas.textures.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -138,11 +137,7 @@ impl Default for PrintingTimer {
|
||||
}
|
||||
|
||||
// System for printing the number of sprites on every tick of the timer
|
||||
fn print_sprite_count(
|
||||
time: Res<Time>,
|
||||
mut timer: Local<PrintingTimer>,
|
||||
sprites: Query<&TextureAtlasSprite>,
|
||||
) {
|
||||
fn print_sprite_count(time: Res<Time>, mut timer: Local<PrintingTimer>, sprites: Query<&Sprite>) {
|
||||
timer.tick(time.delta());
|
||||
|
||||
if timer.just_finished() {
|
||||
|
@ -34,7 +34,6 @@ fn atlas_render_system(
|
||||
mut commands: Commands,
|
||||
mut state: ResMut<State>,
|
||||
font_atlas_sets: Res<FontAtlasSets>,
|
||||
texture_atlases: Res<Assets<TextureAtlas>>,
|
||||
) {
|
||||
if let Some(set) = font_atlas_sets.get(&state.handle) {
|
||||
if let Some((_size, font_atlas)) = set.iter().next() {
|
||||
@ -42,12 +41,10 @@ fn atlas_render_system(
|
||||
if state.atlas_count == font_atlas.len() as u32 {
|
||||
return;
|
||||
}
|
||||
let texture_atlas = texture_atlases
|
||||
.get(&font_atlas[state.atlas_count as usize].texture_atlas)
|
||||
.unwrap();
|
||||
let font_atlas = &font_atlas[state.atlas_count as usize];
|
||||
state.atlas_count += 1;
|
||||
commands.spawn(ImageBundle {
|
||||
image: texture_atlas.texture.clone().into(),
|
||||
image: font_atlas.texture.clone().into(),
|
||||
style: Style {
|
||||
position_type: PositionType::Absolute,
|
||||
top: Val::ZERO,
|
||||
|
@ -20,7 +20,7 @@ fn main() {
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
||||
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
|
||||
) {
|
||||
// Camera
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
@ -31,8 +31,7 @@ fn setup(
|
||||
};
|
||||
|
||||
let texture_handle = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png");
|
||||
let texture_atlas =
|
||||
TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1, None, None);
|
||||
let texture_atlas = TextureAtlasLayout::from_grid(Vec2::new(24.0, 24.0), 7, 1, None, None);
|
||||
let texture_atlas_handle = texture_atlases.add(texture_atlas);
|
||||
|
||||
// root node
|
||||
@ -56,8 +55,8 @@ fn setup(
|
||||
height: Val::Px(256.),
|
||||
..default()
|
||||
},
|
||||
texture_atlas: texture_atlas_handle,
|
||||
texture_atlas_image: UiTextureAtlasImage::default(),
|
||||
texture_atlas: texture_atlas_handle.into(),
|
||||
image: UiImage::new(texture_handle),
|
||||
..default()
|
||||
});
|
||||
parent.spawn(TextBundle::from_sections([
|
||||
@ -75,7 +74,7 @@ fn setup(
|
||||
}
|
||||
|
||||
fn increment_atlas_index(
|
||||
mut atlas_images: Query<&mut UiTextureAtlasImage>,
|
||||
mut atlas_images: Query<&mut TextureAtlas>,
|
||||
keyboard: Res<ButtonInput<KeyCode>>,
|
||||
) {
|
||||
if keyboard.just_pressed(KeyCode::Space) {
|
||||
|
Loading…
Reference in New Issue
Block a user