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::{
|
use crate::{texture_atlas::TextureAtlas, ImageScaleMode, Sprite};
|
||||||
texture_atlas::{TextureAtlas, TextureAtlasSprite},
|
|
||||||
ImageScaleMode, Sprite,
|
|
||||||
};
|
|
||||||
use bevy_asset::Handle;
|
use bevy_asset::Handle;
|
||||||
use bevy_ecs::bundle::Bundle;
|
use bevy_ecs::bundle::Bundle;
|
||||||
use bevy_render::{
|
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
|
/// 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)]
|
#[derive(Bundle, Clone, Default)]
|
||||||
pub struct SpriteSheetBundle {
|
pub struct SpriteSheetBundle {
|
||||||
/// The specific sprite from the texture atlas to be drawn, defaulting to the sprite at index 0.
|
pub sprite: Sprite,
|
||||||
pub sprite: TextureAtlasSprite,
|
|
||||||
/// Controls how the image is altered when scaled.
|
/// Controls how the image is altered when scaled.
|
||||||
pub scale_mode: ImageScaleMode,
|
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 transform: Transform,
|
||||||
pub global_transform: GlobalTransform,
|
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
|
/// User indication of whether an entity is visible
|
||||||
pub visibility: Visibility,
|
pub visibility: Visibility,
|
||||||
pub inherited_visibility: InheritedVisibility,
|
pub inherited_visibility: InheritedVisibility,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::TextureAtlas;
|
use crate::TextureAtlasLayout;
|
||||||
use bevy_asset::Assets;
|
use bevy_asset::{Assets, Handle};
|
||||||
use bevy_math::{IVec2, Rect, Vec2};
|
use bevy_math::{IVec2, Rect, Vec2};
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
render_asset::RenderAssetPersistencePolicy,
|
render_asset::RenderAssetPersistencePolicy,
|
||||||
@ -7,10 +7,10 @@ use bevy_render::{
|
|||||||
};
|
};
|
||||||
use guillotiere::{size2, Allocation, AtlasAllocator};
|
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,
|
/// 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 {
|
pub struct DynamicTextureAtlasBuilder {
|
||||||
atlas_allocator: AtlasAllocator,
|
atlas_allocator: AtlasAllocator,
|
||||||
padding: i32,
|
padding: i32,
|
||||||
@ -30,22 +30,30 @@ impl DynamicTextureAtlasBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a new texture to [`TextureAtlas`].
|
/// Add a new texture to `atlas_layout`
|
||||||
/// It is user's responsibility to pass in the correct [`TextureAtlas`],
|
/// It is the user's responsibility to pass in the correct [`TextureAtlasLayout`]
|
||||||
/// and that [`TextureAtlas::texture`] has [`Image::cpu_persistent_access`]
|
/// and that `atlas_texture_handle` has [`Image::cpu_persistent_access`]
|
||||||
/// set to [`RenderAssetPersistencePolicy::Keep`]
|
/// 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(
|
pub fn add_texture(
|
||||||
&mut self,
|
&mut self,
|
||||||
texture_atlas: &mut TextureAtlas,
|
atlas_layout: &mut TextureAtlasLayout,
|
||||||
textures: &mut Assets<Image>,
|
textures: &mut Assets<Image>,
|
||||||
texture: &Image,
|
texture: &Image,
|
||||||
|
atlas_texture_handle: &Handle<Image>,
|
||||||
) -> Option<usize> {
|
) -> Option<usize> {
|
||||||
let allocation = self.atlas_allocator.allocate(size2(
|
let allocation = self.atlas_allocator.allocate(size2(
|
||||||
texture.width() as i32 + self.padding,
|
texture.width() as i32 + self.padding,
|
||||||
texture.height() as i32 + self.padding,
|
texture.height() as i32 + self.padding,
|
||||||
));
|
));
|
||||||
if let Some(allocation) = allocation {
|
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!(
|
assert_eq!(
|
||||||
atlas_texture.cpu_persistent_access,
|
atlas_texture.cpu_persistent_access,
|
||||||
RenderAssetPersistencePolicy::Keep
|
RenderAssetPersistencePolicy::Keep
|
||||||
@ -54,7 +62,7 @@ impl DynamicTextureAtlasBuilder {
|
|||||||
self.place_texture(atlas_texture, allocation, texture);
|
self.place_texture(atlas_texture, allocation, texture);
|
||||||
let mut rect: Rect = to_rect(allocation.rectangle);
|
let mut rect: Rect = to_rect(allocation.rectangle);
|
||||||
rect.max -= self.padding as f32;
|
rect.max -= self.padding as f32;
|
||||||
Some(texture_atlas.add_texture(rect))
|
Some(atlas_layout.add_texture(rect))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ pub mod prelude {
|
|||||||
pub use crate::{
|
pub use crate::{
|
||||||
bundle::{SpriteBundle, SpriteSheetBundle},
|
bundle::{SpriteBundle, SpriteSheetBundle},
|
||||||
sprite::{ImageScaleMode, Sprite},
|
sprite::{ImageScaleMode, Sprite},
|
||||||
texture_atlas::{TextureAtlas, TextureAtlasSprite},
|
texture_atlas::{TextureAtlas, TextureAtlasLayout},
|
||||||
texture_slice::{BorderRect, SliceScaleMode, TextureSlicer},
|
texture_slice::{BorderRect, SliceScaleMode, TextureSlicer},
|
||||||
ColorMaterial, ColorMesh2dBundle, TextureAtlasBuilder,
|
ColorMaterial, ColorMesh2dBundle, TextureAtlasBuilder,
|
||||||
};
|
};
|
||||||
@ -65,13 +65,13 @@ impl Plugin for SpritePlugin {
|
|||||||
"render/sprite.wgsl",
|
"render/sprite.wgsl",
|
||||||
Shader::from_wgsl
|
Shader::from_wgsl
|
||||||
);
|
);
|
||||||
app.init_asset::<TextureAtlas>()
|
app.init_asset::<TextureAtlasLayout>()
|
||||||
.register_asset_reflect::<TextureAtlas>()
|
.register_asset_reflect::<TextureAtlasLayout>()
|
||||||
.register_type::<Sprite>()
|
.register_type::<Sprite>()
|
||||||
.register_type::<ImageScaleMode>()
|
.register_type::<ImageScaleMode>()
|
||||||
.register_type::<TextureSlicer>()
|
.register_type::<TextureSlicer>()
|
||||||
.register_type::<TextureAtlasSprite>()
|
|
||||||
.register_type::<Anchor>()
|
.register_type::<Anchor>()
|
||||||
|
.register_type::<TextureAtlas>()
|
||||||
.register_type::<Mesh2dHandle>()
|
.register_type::<Mesh2dHandle>()
|
||||||
.add_plugins((Mesh2dRenderPlugin, ColorMaterialPlugin))
|
.add_plugins((Mesh2dRenderPlugin, ColorMaterialPlugin))
|
||||||
.add_systems(
|
.add_systems(
|
||||||
@ -131,19 +131,15 @@ pub fn calculate_bounds_2d(
|
|||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
meshes: Res<Assets<Mesh>>,
|
meshes: Res<Assets<Mesh>>,
|
||||||
images: Res<Assets<Image>>,
|
images: Res<Assets<Image>>,
|
||||||
atlases: Res<Assets<TextureAtlas>>,
|
atlases: Res<Assets<TextureAtlasLayout>>,
|
||||||
meshes_without_aabb: Query<(Entity, &Mesh2dHandle), (Without<Aabb>, Without<NoFrustumCulling>)>,
|
meshes_without_aabb: Query<(Entity, &Mesh2dHandle), (Without<Aabb>, Without<NoFrustumCulling>)>,
|
||||||
sprites_to_recalculate_aabb: Query<
|
sprites_to_recalculate_aabb: Query<
|
||||||
(Entity, &Sprite, &Handle<Image>),
|
(Entity, &Sprite, &Handle<Image>, Option<&TextureAtlas>),
|
||||||
(
|
(
|
||||||
Or<(Without<Aabb>, Changed<Sprite>)>,
|
Or<(Without<Aabb>, Changed<Sprite>)>,
|
||||||
Without<NoFrustumCulling>,
|
Without<NoFrustumCulling>,
|
||||||
),
|
),
|
||||||
>,
|
>,
|
||||||
atlases_without_aabb: Query<
|
|
||||||
(Entity, &TextureAtlasSprite, &Handle<TextureAtlas>),
|
|
||||||
(Without<Aabb>, Without<NoFrustumCulling>),
|
|
||||||
>,
|
|
||||||
) {
|
) {
|
||||||
for (entity, mesh_handle) in &meshes_without_aabb {
|
for (entity, mesh_handle) in &meshes_without_aabb {
|
||||||
if let Some(mesh) = meshes.get(&mesh_handle.0) {
|
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 {
|
for (entity, sprite, texture_handle, atlas) in &sprites_to_recalculate_aabb {
|
||||||
if let Some(size) = sprite
|
if let Some(size) = sprite.custom_size.or_else(|| match atlas {
|
||||||
.custom_size
|
// We default to the texture size for regular sprites
|
||||||
.or_else(|| images.get(texture_handle).map(|image| image.size_f32()))
|
None => images.get(texture_handle).map(|image| image.size_f32()),
|
||||||
{
|
// We default to the drawn rect for atlas sprites
|
||||||
let aabb = Aabb {
|
Some(atlas) => atlas.texture_rect(&atlases).map(|rect| rect.size()),
|
||||||
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())
|
|
||||||
}) {
|
}) {
|
||||||
let aabb = Aabb {
|
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(),
|
half_extents: (0.5 * size).extend(0.0).into(),
|
||||||
};
|
};
|
||||||
commands.entity(entity).try_insert(aabb);
|
commands.entity(entity).try_insert(aabb);
|
||||||
@ -199,7 +183,7 @@ mod test {
|
|||||||
app.insert_resource(image_assets);
|
app.insert_resource(image_assets);
|
||||||
let mesh_assets = Assets::<Mesh>::default();
|
let mesh_assets = Assets::<Mesh>::default();
|
||||||
app.insert_resource(mesh_assets);
|
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);
|
app.insert_resource(texture_atlas_assets);
|
||||||
|
|
||||||
// Add system
|
// Add system
|
||||||
@ -237,7 +221,7 @@ mod test {
|
|||||||
app.insert_resource(image_assets);
|
app.insert_resource(image_assets);
|
||||||
let mesh_assets = Assets::<Mesh>::default();
|
let mesh_assets = Assets::<Mesh>::default();
|
||||||
app.insert_resource(mesh_assets);
|
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);
|
app.insert_resource(texture_atlas_assets);
|
||||||
|
|
||||||
// Add system
|
// Add system
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
texture_atlas::{TextureAtlas, TextureAtlasSprite},
|
texture_atlas::{TextureAtlas, TextureAtlasLayout},
|
||||||
ComputedTextureSlices, Sprite, SPRITE_SHADER_HANDLE,
|
ComputedTextureSlices, Sprite, SPRITE_SHADER_HANDLE,
|
||||||
};
|
};
|
||||||
use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
|
use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
|
||||||
@ -335,7 +335,7 @@ pub fn extract_sprite_events(
|
|||||||
pub fn extract_sprites(
|
pub fn extract_sprites(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut extracted_sprites: ResMut<ExtractedSprites>,
|
mut extracted_sprites: ResMut<ExtractedSprites>,
|
||||||
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
|
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
|
||||||
sprite_query: Extract<
|
sprite_query: Extract<
|
||||||
Query<(
|
Query<(
|
||||||
Entity,
|
Entity,
|
||||||
@ -343,25 +343,17 @@ pub fn extract_sprites(
|
|||||||
&Sprite,
|
&Sprite,
|
||||||
&GlobalTransform,
|
&GlobalTransform,
|
||||||
&Handle<Image>,
|
&Handle<Image>,
|
||||||
|
Option<&TextureAtlas>,
|
||||||
Option<&ComputedTextureSlices>,
|
Option<&ComputedTextureSlices>,
|
||||||
)>,
|
)>,
|
||||||
>,
|
>,
|
||||||
atlas_query: Extract<
|
|
||||||
Query<(
|
|
||||||
Entity,
|
|
||||||
&ViewVisibility,
|
|
||||||
&TextureAtlasSprite,
|
|
||||||
&GlobalTransform,
|
|
||||||
&Handle<TextureAtlas>,
|
|
||||||
)>,
|
|
||||||
>,
|
|
||||||
) {
|
) {
|
||||||
extracted_sprites.sprites.clear();
|
extracted_sprites.sprites.clear();
|
||||||
|
for (entity, view_visibility, sprite, transform, handle, sheet, slices) in sprite_query.iter() {
|
||||||
for (entity, view_visibility, sprite, transform, handle, slices) in sprite_query.iter() {
|
|
||||||
if !view_visibility.get() {
|
if !view_visibility.get() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(slices) = slices {
|
if let Some(slices) = slices {
|
||||||
extracted_sprites.sprites.extend(
|
extracted_sprites.sprites.extend(
|
||||||
slices
|
slices
|
||||||
@ -369,13 +361,14 @@ pub fn extract_sprites(
|
|||||||
.map(|e| (commands.spawn_empty().id(), e)),
|
.map(|e| (commands.spawn_empty().id(), e)),
|
||||||
);
|
);
|
||||||
} else {
|
} 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
|
// 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(
|
extracted_sprites.sprites.insert(
|
||||||
entity,
|
entity,
|
||||||
ExtractedSprite {
|
ExtractedSprite {
|
||||||
color: sprite.color,
|
color: sprite.color,
|
||||||
transform: *transform,
|
transform: *transform,
|
||||||
rect: sprite.rect,
|
rect,
|
||||||
// Pass the custom size
|
// Pass the custom size
|
||||||
custom_size: sprite.custom_size,
|
custom_size: sprite.custom_size,
|
||||||
flip_x: sprite.flip_x,
|
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)]
|
#[repr(C)]
|
||||||
|
@ -1,97 +1,84 @@
|
|||||||
use crate::Anchor;
|
use bevy_asset::{Asset, AssetId, Assets, Handle};
|
||||||
use bevy_asset::{Asset, AssetId, Handle};
|
use bevy_ecs::component::Component;
|
||||||
use bevy_ecs::{component::Component, reflect::ReflectComponent};
|
|
||||||
use bevy_math::{Rect, Vec2};
|
use bevy_math::{Rect, Vec2};
|
||||||
use bevy_reflect::Reflect;
|
use bevy_reflect::Reflect;
|
||||||
use bevy_render::{color::Color, texture::Image};
|
use bevy_render::texture::Image;
|
||||||
use bevy_utils::HashMap;
|
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 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)
|
/// [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)]
|
#[derive(Asset, Reflect, Debug, Clone)]
|
||||||
#[reflect(Debug)]
|
#[reflect(Debug)]
|
||||||
pub struct TextureAtlas {
|
pub struct TextureAtlasLayout {
|
||||||
/// The handle to the texture in which the sprites are stored
|
|
||||||
pub texture: Handle<Image>,
|
|
||||||
// TODO: add support to Uniforms derive to write dimensions and sprites to the same buffer
|
// TODO: add support to Uniforms derive to write dimensions and sprites to the same buffer
|
||||||
pub size: Vec2,
|
pub size: Vec2,
|
||||||
/// The specific areas of the atlas where each texture can be found
|
/// The specific areas of the atlas where each texture can be found
|
||||||
pub textures: Vec<Rect>,
|
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>>,
|
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).
|
/// It stores a handle to [`TextureAtlasLayout`] and the index of the current section of the atlas.
|
||||||
#[derive(Component, Debug, Clone, Reflect)]
|
/// The texture atlas contains various *sections* of a given texture, allowing users to have a single
|
||||||
#[reflect(Component)]
|
/// image file for either sprite animation or global mapping.
|
||||||
pub struct TextureAtlasSprite {
|
/// You can change the texture [`index`](Self::index) of the atlas to animate the sprite or dsplay only a *section* of the texture
|
||||||
/// The tint color used to draw the sprite, defaulting to [`Color::WHITE`]
|
/// for efficient rendering of related game objects.
|
||||||
pub color: Color,
|
///
|
||||||
/// Texture index in [`TextureAtlas`]
|
/// 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,
|
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 {
|
impl TextureAtlasLayout {
|
||||||
fn default() -> Self {
|
/// Create a new empty layout with custom `dimensions`
|
||||||
|
pub fn new_empty(dimensions: Vec2) -> Self {
|
||||||
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,
|
size: dimensions,
|
||||||
texture_handles: None,
|
texture_handles: None,
|
||||||
textures: Vec::new(),
|
textures: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a [`TextureAtlas`] by splitting a texture into a grid where each
|
/// Generate a [`TextureAtlasLayout`] as a grid where each
|
||||||
/// `tile_size` by `tile_size` grid-cell is one of the textures in the
|
/// `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
|
/// 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.
|
/// 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(
|
pub fn from_grid(
|
||||||
texture: Handle<Image>,
|
|
||||||
tile_size: Vec2,
|
tile_size: Vec2,
|
||||||
columns: usize,
|
columns: usize,
|
||||||
rows: usize,
|
rows: usize,
|
||||||
padding: Option<Vec2>,
|
padding: Option<Vec2>,
|
||||||
offset: Option<Vec2>,
|
offset: Option<Vec2>,
|
||||||
) -> TextureAtlas {
|
) -> Self {
|
||||||
let padding = padding.unwrap_or_default();
|
let padding = padding.unwrap_or_default();
|
||||||
let offset = offset.unwrap_or_default();
|
let offset = offset.unwrap_or_default();
|
||||||
let mut sprites = Vec::new();
|
let mut sprites = Vec::new();
|
||||||
@ -119,37 +106,40 @@ impl TextureAtlas {
|
|||||||
|
|
||||||
let grid_size = Vec2::new(columns as f32, rows as f32);
|
let grid_size = Vec2::new(columns as f32, rows as f32);
|
||||||
|
|
||||||
TextureAtlas {
|
Self {
|
||||||
size: ((tile_size + current_padding) * grid_size) - current_padding,
|
size: ((tile_size + current_padding) * grid_size) - current_padding,
|
||||||
textures: sprites,
|
textures: sprites,
|
||||||
texture,
|
|
||||||
texture_handles: None,
|
texture_handles: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a sprite to the list of textures in the [`TextureAtlas`]
|
/// Add a *section* to the list in the layout and returns its index
|
||||||
/// returns an index to the texture which can be used with [`TextureAtlasSprite`]
|
/// which can be used with [`TextureAtlas`]
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `rect` - The section of the atlas that contains the texture to be added,
|
/// * `rect` - The section of the texture to be added
|
||||||
/// from the top-left corner of the texture to the bottom-right corner
|
///
|
||||||
|
/// [`TextureAtlas`]: crate::TextureAtlas
|
||||||
pub fn add_texture(&mut self, rect: Rect) -> usize {
|
pub fn add_texture(&mut self, rect: Rect) -> usize {
|
||||||
self.textures.push(rect);
|
self.textures.push(rect);
|
||||||
self.textures.len() - 1
|
self.textures.len() - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The number of textures in the [`TextureAtlas`]
|
/// The number of textures in the [`TextureAtlasLayout`]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.textures.len()
|
self.textures.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if there are no textures in the [`TextureAtlas`]
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.textures.is_empty()
|
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> {
|
pub fn get_texture_index(&self, texture: impl Into<AssetId<Image>>) -> Option<usize> {
|
||||||
let id = texture.into();
|
let id = texture.into();
|
||||||
self.texture_handles
|
self.texture_handles
|
||||||
@ -157,3 +147,20 @@ impl TextureAtlas {
|
|||||||
.and_then(|texture_handles| texture_handles.get(&id).cloned())
|
.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_asset::{AssetId, Assets};
|
||||||
use bevy_log::{debug, error, warn};
|
use bevy_log::{debug, error, warn};
|
||||||
use bevy_math::{Rect, UVec2, Vec2};
|
use bevy_math::{Rect, UVec2, Vec2};
|
||||||
@ -13,7 +14,7 @@ use rectangle_pack::{
|
|||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::texture_atlas::TextureAtlas;
|
use crate::TextureAtlasLayout;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum TextureAtlasBuilderError {
|
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
|
/// 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
|
/// into a new texture.
|
||||||
/// hold a strong handle to the texture afterwards else it will exist twice
|
/// It is not useful to hold a strong handle to the texture afterwards else
|
||||||
/// in memory.
|
/// 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
|
/// # Errors
|
||||||
///
|
///
|
||||||
@ -160,7 +190,7 @@ impl TextureAtlasBuilder {
|
|||||||
pub fn finish(
|
pub fn finish(
|
||||||
self,
|
self,
|
||||||
textures: &mut Assets<Image>,
|
textures: &mut Assets<Image>,
|
||||||
) -> Result<TextureAtlas, TextureAtlasBuilderError> {
|
) -> Result<(TextureAtlasLayout, Handle<Image>), TextureAtlasBuilderError> {
|
||||||
let initial_width = self.initial_size.x as u32;
|
let initial_width = self.initial_size.x as u32;
|
||||||
let initial_height = self.initial_size.y as u32;
|
let initial_height = self.initial_size.y as u32;
|
||||||
let max_width = self.max_size.x 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);
|
self.copy_converted_texture(&mut atlas_texture, texture, packed_location);
|
||||||
}
|
}
|
||||||
Ok(TextureAtlas {
|
|
||||||
size: atlas_texture.size_f32(),
|
Ok((
|
||||||
texture: textures.add(atlas_texture),
|
TextureAtlasLayout {
|
||||||
textures: texture_rects,
|
size: atlas_texture.size_f32(),
|
||||||
texture_handles: Some(texture_ids),
|
textures: texture_rects,
|
||||||
})
|
texture_handles: Some(texture_ids),
|
||||||
|
},
|
||||||
|
textures.add(atlas_texture),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ use bevy_render::{
|
|||||||
render_resource::{Extent3d, TextureDimension, TextureFormat},
|
render_resource::{Extent3d, TextureDimension, TextureFormat},
|
||||||
texture::Image,
|
texture::Image,
|
||||||
};
|
};
|
||||||
use bevy_sprite::{DynamicTextureAtlasBuilder, TextureAtlas};
|
use bevy_sprite::{DynamicTextureAtlasBuilder, TextureAtlasLayout};
|
||||||
use bevy_utils::HashMap;
|
use bevy_utils::HashMap;
|
||||||
|
|
||||||
#[cfg(feature = "subpixel_glyph_atlas")]
|
#[cfg(feature = "subpixel_glyph_atlas")]
|
||||||
@ -43,16 +43,17 @@ impl From<Point> for SubpixelOffset {
|
|||||||
pub struct FontAtlas {
|
pub struct FontAtlas {
|
||||||
pub dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder,
|
pub dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder,
|
||||||
pub glyph_to_atlas_index: HashMap<(GlyphId, SubpixelOffset), usize>,
|
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 {
|
impl FontAtlas {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
textures: &mut Assets<Image>,
|
textures: &mut Assets<Image>,
|
||||||
texture_atlases: &mut Assets<TextureAtlas>,
|
texture_atlases: &mut Assets<TextureAtlasLayout>,
|
||||||
size: Vec2,
|
size: Vec2,
|
||||||
) -> FontAtlas {
|
) -> FontAtlas {
|
||||||
let atlas_texture = textures.add(Image::new_fill(
|
let texture = textures.add(Image::new_fill(
|
||||||
Extent3d {
|
Extent3d {
|
||||||
width: size.x as u32,
|
width: size.x as u32,
|
||||||
height: size.y 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
|
// Need to keep this image CPU persistent in order to add additional glyphs later on
|
||||||
RenderAssetPersistencePolicy::Keep,
|
RenderAssetPersistencePolicy::Keep,
|
||||||
));
|
));
|
||||||
let texture_atlas = TextureAtlas::new_empty(atlas_texture, size);
|
let texture_atlas = TextureAtlasLayout::new_empty(size);
|
||||||
Self {
|
Self {
|
||||||
texture_atlas: texture_atlases.add(texture_atlas),
|
texture_atlas: texture_atlases.add(texture_atlas),
|
||||||
glyph_to_atlas_index: HashMap::default(),
|
glyph_to_atlas_index: HashMap::default(),
|
||||||
dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder::new(size, 0),
|
dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder::new(size, 0),
|
||||||
|
texture,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,16 +92,18 @@ impl FontAtlas {
|
|||||||
pub fn add_glyph(
|
pub fn add_glyph(
|
||||||
&mut self,
|
&mut self,
|
||||||
textures: &mut Assets<Image>,
|
textures: &mut Assets<Image>,
|
||||||
texture_atlases: &mut Assets<TextureAtlas>,
|
texture_atlases: &mut Assets<TextureAtlasLayout>,
|
||||||
glyph_id: GlyphId,
|
glyph_id: GlyphId,
|
||||||
subpixel_offset: SubpixelOffset,
|
subpixel_offset: SubpixelOffset,
|
||||||
texture: &Image,
|
texture: &Image,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let texture_atlas = texture_atlases.get_mut(&self.texture_atlas).unwrap();
|
let texture_atlas = texture_atlases.get_mut(&self.texture_atlas).unwrap();
|
||||||
if let Some(index) =
|
if let Some(index) = self.dynamic_texture_atlas_builder.add_texture(
|
||||||
self.dynamic_texture_atlas_builder
|
texture_atlas,
|
||||||
.add_texture(texture_atlas, textures, texture)
|
textures,
|
||||||
{
|
texture,
|
||||||
|
&self.texture,
|
||||||
|
) {
|
||||||
self.glyph_to_atlas_index
|
self.glyph_to_atlas_index
|
||||||
.insert((glyph_id, subpixel_offset), index);
|
.insert((glyph_id, subpixel_offset), index);
|
||||||
true
|
true
|
||||||
|
@ -6,7 +6,7 @@ use bevy_ecs::prelude::*;
|
|||||||
use bevy_math::Vec2;
|
use bevy_math::Vec2;
|
||||||
use bevy_reflect::Reflect;
|
use bevy_reflect::Reflect;
|
||||||
use bevy_render::texture::Image;
|
use bevy_render::texture::Image;
|
||||||
use bevy_sprite::TextureAtlas;
|
use bevy_sprite::TextureAtlasLayout;
|
||||||
use bevy_utils::FloatOrd;
|
use bevy_utils::FloatOrd;
|
||||||
use bevy_utils::HashMap;
|
use bevy_utils::HashMap;
|
||||||
|
|
||||||
@ -43,7 +43,8 @@ pub struct FontAtlasSet {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Reflect)]
|
#[derive(Debug, Clone, Reflect)]
|
||||||
pub struct GlyphAtlasInfo {
|
pub struct GlyphAtlasInfo {
|
||||||
pub texture_atlas: Handle<TextureAtlas>,
|
pub texture_atlas: Handle<TextureAtlasLayout>,
|
||||||
|
pub texture: Handle<Image>,
|
||||||
pub glyph_index: usize,
|
pub glyph_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +73,7 @@ impl FontAtlasSet {
|
|||||||
|
|
||||||
pub fn add_glyph_to_atlas(
|
pub fn add_glyph_to_atlas(
|
||||||
&mut self,
|
&mut self,
|
||||||
texture_atlases: &mut Assets<TextureAtlas>,
|
texture_atlases: &mut Assets<TextureAtlasLayout>,
|
||||||
textures: &mut Assets<Image>,
|
textures: &mut Assets<Image>,
|
||||||
outlined_glyph: OutlinedGlyph,
|
outlined_glyph: OutlinedGlyph,
|
||||||
) -> Result<GlyphAtlasInfo, TextError> {
|
) -> Result<GlyphAtlasInfo, TextError> {
|
||||||
@ -145,10 +146,17 @@ impl FontAtlasSet {
|
|||||||
.find_map(|atlas| {
|
.find_map(|atlas| {
|
||||||
atlas
|
atlas
|
||||||
.get_glyph_index(glyph_id, position.into())
|
.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_atlas,
|
||||||
|
texture,
|
||||||
glyph_index,
|
glyph_index,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -3,7 +3,7 @@ use bevy_asset::{AssetId, Assets};
|
|||||||
use bevy_math::{Rect, Vec2};
|
use bevy_math::{Rect, Vec2};
|
||||||
use bevy_reflect::Reflect;
|
use bevy_reflect::Reflect;
|
||||||
use bevy_render::texture::Image;
|
use bevy_render::texture::Image;
|
||||||
use bevy_sprite::TextureAtlas;
|
use bevy_sprite::TextureAtlasLayout;
|
||||||
use bevy_utils::tracing::warn;
|
use bevy_utils::tracing::warn;
|
||||||
use glyph_brush_layout::{
|
use glyph_brush_layout::{
|
||||||
BuiltInLineBreaker, FontId, GlyphPositioner, Layout, SectionGeometry, SectionGlyph,
|
BuiltInLineBreaker, FontId, GlyphPositioner, Layout, SectionGeometry, SectionGlyph,
|
||||||
@ -60,7 +60,7 @@ impl GlyphBrush {
|
|||||||
sections: &[SectionText],
|
sections: &[SectionText],
|
||||||
font_atlas_sets: &mut FontAtlasSets,
|
font_atlas_sets: &mut FontAtlasSets,
|
||||||
fonts: &Assets<Font>,
|
fonts: &Assets<Font>,
|
||||||
texture_atlases: &mut Assets<TextureAtlas>,
|
texture_atlases: &mut Assets<TextureAtlasLayout>,
|
||||||
textures: &mut Assets<Image>,
|
textures: &mut Assets<Image>,
|
||||||
text_settings: &TextSettings,
|
text_settings: &TextSettings,
|
||||||
font_atlas_warning: &mut FontAtlasWarning,
|
font_atlas_warning: &mut FontAtlasWarning,
|
||||||
|
@ -12,7 +12,7 @@ use bevy_math::Vec2;
|
|||||||
use bevy_reflect::prelude::ReflectDefault;
|
use bevy_reflect::prelude::ReflectDefault;
|
||||||
use bevy_reflect::Reflect;
|
use bevy_reflect::Reflect;
|
||||||
use bevy_render::texture::Image;
|
use bevy_render::texture::Image;
|
||||||
use bevy_sprite::TextureAtlas;
|
use bevy_sprite::TextureAtlasLayout;
|
||||||
use bevy_utils::HashMap;
|
use bevy_utils::HashMap;
|
||||||
use glyph_brush_layout::{FontId, GlyphPositioner, SectionGeometry, SectionText, ToSectionText};
|
use glyph_brush_layout::{FontId, GlyphPositioner, SectionGeometry, SectionText, ToSectionText};
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ impl TextPipeline {
|
|||||||
linebreak_behavior: BreakLineOn,
|
linebreak_behavior: BreakLineOn,
|
||||||
bounds: Vec2,
|
bounds: Vec2,
|
||||||
font_atlas_sets: &mut FontAtlasSets,
|
font_atlas_sets: &mut FontAtlasSets,
|
||||||
texture_atlases: &mut Assets<TextureAtlas>,
|
texture_atlases: &mut Assets<TextureAtlasLayout>,
|
||||||
textures: &mut Assets<Image>,
|
textures: &mut Assets<Image>,
|
||||||
text_settings: &TextSettings,
|
text_settings: &TextSettings,
|
||||||
font_atlas_warning: &mut FontAtlasWarning,
|
font_atlas_warning: &mut FontAtlasWarning,
|
||||||
|
@ -21,7 +21,7 @@ use bevy_render::{
|
|||||||
view::{InheritedVisibility, ViewVisibility, Visibility},
|
view::{InheritedVisibility, ViewVisibility, Visibility},
|
||||||
Extract,
|
Extract,
|
||||||
};
|
};
|
||||||
use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, TextureAtlas};
|
use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, TextureAtlasLayout};
|
||||||
use bevy_transform::prelude::{GlobalTransform, Transform};
|
use bevy_transform::prelude::{GlobalTransform, Transform};
|
||||||
use bevy_utils::HashSet;
|
use bevy_utils::HashSet;
|
||||||
use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged};
|
use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged};
|
||||||
@ -83,7 +83,7 @@ pub struct Text2dBundle {
|
|||||||
pub fn extract_text2d_sprite(
|
pub fn extract_text2d_sprite(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut extracted_sprites: ResMut<ExtractedSprites>,
|
mut extracted_sprites: ResMut<ExtractedSprites>,
|
||||||
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
|
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
|
||||||
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
|
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
|
||||||
text2d_query: Extract<
|
text2d_query: Extract<
|
||||||
Query<(
|
Query<(
|
||||||
@ -138,7 +138,7 @@ pub fn extract_text2d_sprite(
|
|||||||
color,
|
color,
|
||||||
rect: Some(atlas.textures[atlas_info.glyph_index]),
|
rect: Some(atlas.textures[atlas_info.glyph_index]),
|
||||||
custom_size: None,
|
custom_size: None,
|
||||||
image_handle_id: atlas.texture.id(),
|
image_handle_id: atlas_info.texture.id(),
|
||||||
flip_x: false,
|
flip_x: false,
|
||||||
flip_y: false,
|
flip_y: false,
|
||||||
anchor: Anchor::Center.as_vec(),
|
anchor: Anchor::Center.as_vec(),
|
||||||
@ -166,7 +166,7 @@ pub fn update_text2d_layout(
|
|||||||
mut font_atlas_warning: ResMut<FontAtlasWarning>,
|
mut font_atlas_warning: ResMut<FontAtlasWarning>,
|
||||||
windows: Query<&Window, With<PrimaryWindow>>,
|
windows: Query<&Window, With<PrimaryWindow>>,
|
||||||
mut scale_factor_changed: EventReader<WindowScaleFactorChanged>,
|
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 font_atlas_sets: ResMut<FontAtlasSets>,
|
||||||
mut text_pipeline: ResMut<TextPipeline>,
|
mut text_pipeline: ResMut<TextPipeline>,
|
||||||
mut text_query: Query<(Entity, Ref<Text>, Ref<Text2dBounds>, &mut TextLayoutInfo)>,
|
mut text_query: Query<(Entity, Ref<Text>, Ref<Text2dBounds>, &mut TextLayoutInfo)>,
|
||||||
|
@ -118,7 +118,6 @@ impl Plugin for UiPlugin {
|
|||||||
.register_type::<UiImageSize>()
|
.register_type::<UiImageSize>()
|
||||||
.register_type::<UiRect>()
|
.register_type::<UiRect>()
|
||||||
.register_type::<UiScale>()
|
.register_type::<UiScale>()
|
||||||
.register_type::<UiTextureAtlasImage>()
|
|
||||||
.register_type::<Val>()
|
.register_type::<Val>()
|
||||||
.register_type::<BorderColor>()
|
.register_type::<BorderColor>()
|
||||||
.register_type::<widget::Button>()
|
.register_type::<widget::Button>()
|
||||||
@ -151,8 +150,7 @@ impl Plugin for UiPlugin {
|
|||||||
.ambiguous_with(bevy_text::update_text2d_layout)
|
.ambiguous_with(bevy_text::update_text2d_layout)
|
||||||
// We assume Text is on disjoint UI entities to UiImage and UiTextureAtlasImage
|
// 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.
|
// 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_image_content_size_system),
|
||||||
.ambiguous_with(widget::update_atlas_content_size_system),
|
|
||||||
widget::text_system
|
widget::text_system
|
||||||
.after(UiSystem::Layout)
|
.after(UiSystem::Layout)
|
||||||
.after(bevy_text::remove_dropped_font_atlas_sets)
|
.after(bevy_text::remove_dropped_font_atlas_sets)
|
||||||
@ -163,11 +161,7 @@ impl Plugin for UiPlugin {
|
|||||||
#[cfg(feature = "bevy_text")]
|
#[cfg(feature = "bevy_text")]
|
||||||
app.add_plugins(accessibility::AccessibilityPlugin);
|
app.add_plugins(accessibility::AccessibilityPlugin);
|
||||||
app.add_systems(PostUpdate, {
|
app.add_systems(PostUpdate, {
|
||||||
let system = widget::update_image_content_size_system
|
let system = widget::update_image_content_size_system.before(UiSystem::Layout);
|
||||||
.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);
|
|
||||||
// Potential conflicts: `Assets<Image>`
|
// Potential conflicts: `Assets<Image>`
|
||||||
// They run independently since `widget::image_node_system` will only ever observe
|
// They run independently since `widget::image_node_system` will only ever observe
|
||||||
// its own UiImage, and `widget::text_system` & `bevy_text::update_text2d_layout`
|
// its own UiImage, and `widget::text_system` & `bevy_text::update_text2d_layout`
|
||||||
@ -183,11 +177,7 @@ impl Plugin for UiPlugin {
|
|||||||
app.add_systems(
|
app.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
(
|
(
|
||||||
(
|
update_target_camera_system.before(UiSystem::Layout),
|
||||||
widget::update_atlas_content_size_system,
|
|
||||||
update_target_camera_system,
|
|
||||||
)
|
|
||||||
.before(UiSystem::Layout),
|
|
||||||
apply_deferred
|
apply_deferred
|
||||||
.after(update_target_camera_system)
|
.after(update_target_camera_system)
|
||||||
.before(UiSystem::Layout),
|
.before(UiSystem::Layout),
|
||||||
|
@ -5,7 +5,7 @@ use crate::widget::TextFlags;
|
|||||||
use crate::{
|
use crate::{
|
||||||
widget::{Button, UiImageSize},
|
widget::{Button, UiImageSize},
|
||||||
BackgroundColor, BorderColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage,
|
BackgroundColor, BorderColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage,
|
||||||
UiMaterial, UiTextureAtlasImage, ZIndex,
|
UiMaterial, ZIndex,
|
||||||
};
|
};
|
||||||
use bevy_asset::Handle;
|
use bevy_asset::Handle;
|
||||||
use bevy_ecs::bundle::Bundle;
|
use bevy_ecs::bundle::Bundle;
|
||||||
@ -115,6 +115,8 @@ pub struct ImageBundle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A UI node that is a texture atlas sprite
|
/// A UI node that is a texture atlas sprite
|
||||||
|
///
|
||||||
|
/// This bundle is identical to [`ImageBundle`] with an additional [`TextureAtlas`] component.
|
||||||
#[derive(Bundle, Debug, Default)]
|
#[derive(Bundle, Debug, Default)]
|
||||||
pub struct AtlasImageBundle {
|
pub struct AtlasImageBundle {
|
||||||
/// Describes the logical size of the node
|
/// Describes the logical size of the node
|
||||||
@ -128,10 +130,10 @@ pub struct AtlasImageBundle {
|
|||||||
///
|
///
|
||||||
/// Combines with `UiImage` to tint the provided image.
|
/// Combines with `UiImage` to tint the provided image.
|
||||||
pub background_color: BackgroundColor,
|
pub background_color: BackgroundColor,
|
||||||
|
/// The image of the node
|
||||||
|
pub image: UiImage,
|
||||||
/// A handle to the texture atlas to use for this Ui Node
|
/// A handle to the texture atlas to use for this Ui Node
|
||||||
pub texture_atlas: Handle<TextureAtlas>,
|
pub texture_atlas: TextureAtlas,
|
||||||
/// The descriptor for which sprite to use from the given texture atlas
|
|
||||||
pub texture_atlas_image: UiTextureAtlasImage,
|
|
||||||
/// Whether this node should block interaction with lower nodes
|
/// Whether this node should block interaction with lower nodes
|
||||||
pub focus_policy: FocusPolicy,
|
pub focus_policy: FocusPolicy,
|
||||||
/// The size of the image in pixels
|
/// The size of the image in pixels
|
||||||
|
@ -8,13 +8,13 @@ use bevy_render::{
|
|||||||
render_phase::PhaseItem, render_resource::BindGroupEntries, view::ViewVisibility,
|
render_phase::PhaseItem, render_resource::BindGroupEntries, view::ViewVisibility,
|
||||||
ExtractSchedule, Render,
|
ExtractSchedule, Render,
|
||||||
};
|
};
|
||||||
|
use bevy_sprite::{SpriteAssetEvents, TextureAtlas};
|
||||||
pub use pipeline::*;
|
pub use pipeline::*;
|
||||||
pub use render_pass::*;
|
pub use render_pass::*;
|
||||||
pub use ui_material_pipeline::*;
|
pub use ui_material_pipeline::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
BackgroundColor, BorderColor, CalculatedClip, ContentSize, Node, Style, UiImage, UiScale,
|
BackgroundColor, BorderColor, CalculatedClip, ContentSize, Node, Style, UiImage, UiScale, Val,
|
||||||
UiTextureAtlasImage, Val,
|
|
||||||
};
|
};
|
||||||
use crate::{DefaultUiCamera, Outline, TargetCamera};
|
use crate::{DefaultUiCamera, Outline, TargetCamera};
|
||||||
|
|
||||||
@ -34,7 +34,8 @@ use bevy_render::{
|
|||||||
view::{ExtractedView, ViewUniforms},
|
view::{ExtractedView, ViewUniforms},
|
||||||
Extract, RenderApp, RenderSet,
|
Extract, RenderApp, RenderSet,
|
||||||
};
|
};
|
||||||
use bevy_sprite::{SpriteAssetEvents, TextureAtlas};
|
#[cfg(feature = "bevy_text")]
|
||||||
|
use bevy_sprite::TextureAtlasLayout;
|
||||||
#[cfg(feature = "bevy_text")]
|
#[cfg(feature = "bevy_text")]
|
||||||
use bevy_text::{PositionedGlyph, Text, TextLayoutInfo};
|
use bevy_text::{PositionedGlyph, Text, TextLayoutInfo};
|
||||||
use bevy_transform::components::GlobalTransform;
|
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::<Camera2d>,
|
||||||
extract_default_ui_camera_view::<Camera3d>,
|
extract_default_ui_camera_view::<Camera3d>,
|
||||||
extract_uinodes.in_set(RenderUiSystem::ExtractNode),
|
extract_uinodes.in_set(RenderUiSystem::ExtractNode),
|
||||||
extract_atlas_uinodes
|
|
||||||
.in_set(RenderUiSystem::ExtractAtlasNode)
|
|
||||||
.after(RenderUiSystem::ExtractNode),
|
|
||||||
extract_uinode_borders.after(RenderUiSystem::ExtractAtlasNode),
|
extract_uinode_borders.after(RenderUiSystem::ExtractAtlasNode),
|
||||||
#[cfg(feature = "bevy_text")]
|
#[cfg(feature = "bevy_text")]
|
||||||
extract_text_uinodes.after(RenderUiSystem::ExtractAtlasNode),
|
extract_text_uinodes.after(RenderUiSystem::ExtractAtlasNode),
|
||||||
@ -174,99 +172,6 @@ pub struct ExtractedUiNodes {
|
|||||||
pub uinodes: EntityHashMap<Entity, ExtractedUiNode>,
|
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 {
|
pub(crate) fn resolve_border_thickness(value: Val, parent_width: f32, viewport_size: Vec2) -> f32 {
|
||||||
match value {
|
match value {
|
||||||
Val::Auto => 0.,
|
Val::Auto => 0.,
|
||||||
@ -495,24 +400,23 @@ pub fn extract_uinode_outlines(
|
|||||||
pub fn extract_uinodes(
|
pub fn extract_uinodes(
|
||||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||||
images: Extract<Res<Assets<Image>>>,
|
images: Extract<Res<Assets<Image>>>,
|
||||||
|
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
|
||||||
default_ui_camera: Extract<DefaultUiCamera>,
|
default_ui_camera: Extract<DefaultUiCamera>,
|
||||||
uinode_query: Extract<
|
uinode_query: Extract<
|
||||||
Query<
|
Query<(
|
||||||
(
|
Entity,
|
||||||
Entity,
|
&Node,
|
||||||
&Node,
|
&GlobalTransform,
|
||||||
&GlobalTransform,
|
&BackgroundColor,
|
||||||
&BackgroundColor,
|
Option<&UiImage>,
|
||||||
Option<&UiImage>,
|
&ViewVisibility,
|
||||||
&ViewVisibility,
|
Option<&CalculatedClip>,
|
||||||
Option<&CalculatedClip>,
|
Option<&TextureAtlas>,
|
||||||
Option<&TargetCamera>,
|
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()
|
uinode_query.iter()
|
||||||
{
|
{
|
||||||
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
|
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)
|
(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(
|
extracted_uinodes.uinodes.insert(
|
||||||
entity,
|
entity,
|
||||||
ExtractedUiNode {
|
ExtractedUiNode {
|
||||||
stack_index: uinode.stack_index,
|
stack_index: uinode.stack_index,
|
||||||
transform: transform.compute_matrix(),
|
transform: transform.compute_matrix(),
|
||||||
color: color.0,
|
color: color.0,
|
||||||
rect: Rect {
|
rect,
|
||||||
min: Vec2::ZERO,
|
|
||||||
max: uinode.calculated_size,
|
|
||||||
},
|
|
||||||
clip: clip.map(|clip| clip.clip),
|
clip: clip.map(|clip| clip.clip),
|
||||||
image,
|
image,
|
||||||
atlas_size: None,
|
atlas_size,
|
||||||
flip_x,
|
flip_x,
|
||||||
flip_y,
|
flip_y,
|
||||||
camera_entity,
|
camera_entity,
|
||||||
@ -635,7 +559,7 @@ pub fn extract_text_uinodes(
|
|||||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||||
camera_query: Extract<Query<(Entity, &Camera)>>,
|
camera_query: Extract<Query<(Entity, &Camera)>>,
|
||||||
default_ui_camera: Extract<DefaultUiCamera>,
|
default_ui_camera: Extract<DefaultUiCamera>,
|
||||||
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
|
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
|
||||||
ui_scale: Extract<Res<UiScale>>,
|
ui_scale: Extract<Res<UiScale>>,
|
||||||
uinode_query: Extract<
|
uinode_query: Extract<
|
||||||
Query<(
|
Query<(
|
||||||
@ -708,7 +632,7 @@ pub fn extract_text_uinodes(
|
|||||||
* Mat4::from_translation(position.extend(0.) * inverse_scale_factor),
|
* Mat4::from_translation(position.extend(0.) * inverse_scale_factor),
|
||||||
color,
|
color,
|
||||||
rect,
|
rect,
|
||||||
image: atlas.texture.id(),
|
image: atlas_info.texture.id(),
|
||||||
atlas_size: Some(atlas.size * inverse_scale_factor),
|
atlas_size: Some(atlas.size * inverse_scale_factor),
|
||||||
clip: clip.map(|clip| clip.clip),
|
clip: clip.map(|clip| clip.clip),
|
||||||
flip_x: false,
|
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.
|
/// The border color of the UI node.
|
||||||
#[derive(Component, Copy, Clone, Debug, Reflect)]
|
#[derive(Component, Copy, Clone, Debug, Reflect)]
|
||||||
#[reflect(Component, Default)]
|
#[reflect(Component, Default)]
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
use crate::{
|
use crate::{measurement::AvailableSpace, ContentSize, Measure, Node, UiImage, UiScale};
|
||||||
measurement::AvailableSpace, ContentSize, Measure, Node, UiImage, UiScale, UiTextureAtlasImage,
|
use bevy_asset::Assets;
|
||||||
};
|
|
||||||
use bevy_asset::{Assets, Handle};
|
|
||||||
|
|
||||||
use bevy_ecs::change_detection::DetectChanges;
|
use bevy_ecs::change_detection::DetectChanges;
|
||||||
use bevy_ecs::query::Without;
|
use bevy_ecs::query::Without;
|
||||||
@ -14,7 +12,7 @@ use bevy_ecs::{
|
|||||||
use bevy_math::Vec2;
|
use bevy_math::Vec2;
|
||||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||||
use bevy_render::texture::Image;
|
use bevy_render::texture::Image;
|
||||||
use bevy_sprite::TextureAtlas;
|
use bevy_sprite::{TextureAtlas, TextureAtlasLayout};
|
||||||
use bevy_window::{PrimaryWindow, Window};
|
use bevy_window::{PrimaryWindow, Window};
|
||||||
|
|
||||||
/// The size of the image's texture
|
/// The size of the image's texture
|
||||||
@ -82,48 +80,15 @@ pub fn update_image_content_size_system(
|
|||||||
windows: Query<&Window, With<PrimaryWindow>>,
|
windows: Query<&Window, With<PrimaryWindow>>,
|
||||||
ui_scale: Res<UiScale>,
|
ui_scale: Res<UiScale>,
|
||||||
textures: Res<Assets<Image>>,
|
textures: Res<Assets<Image>>,
|
||||||
mut query: Query<(&mut ContentSize, &UiImage, &mut UiImageSize), UpdateImageFilter>,
|
atlases: Res<Assets<TextureAtlasLayout>>,
|
||||||
) {
|
mut query: Query<
|
||||||
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<
|
|
||||||
(
|
(
|
||||||
&mut ContentSize,
|
&mut ContentSize,
|
||||||
&Handle<TextureAtlas>,
|
&UiImage,
|
||||||
&UiTextureAtlasImage,
|
|
||||||
&mut UiImageSize,
|
&mut UiImageSize,
|
||||||
|
Option<&TextureAtlas>,
|
||||||
),
|
),
|
||||||
(UpdateImageFilter, Without<UiImage>),
|
UpdateImageFilter,
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
let combined_scale_factor = windows
|
let combined_scale_factor = windows
|
||||||
@ -132,9 +97,11 @@ pub fn update_atlas_content_size_system(
|
|||||||
.unwrap_or(1.)
|
.unwrap_or(1.)
|
||||||
* ui_scale.0;
|
* ui_scale.0;
|
||||||
|
|
||||||
for (mut content_size, atlas, atlas_image, mut image_size) in &mut atlas_query {
|
for (mut content_size, image, mut image_size, atlas_image) in &mut query {
|
||||||
if let Some(atlas) = atlases.get(atlas) {
|
if let Some(size) = match atlas_image {
|
||||||
let size = atlas.textures[atlas_image.index].size();
|
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
|
// Update only if size or scale factor has changed to avoid needless layout calculations
|
||||||
if size != image_size.size
|
if size != image_size.size
|
||||||
|| combined_scale_factor != *previous_combined_scale_factor
|
|| combined_scale_factor != *previous_combined_scale_factor
|
||||||
|
@ -10,7 +10,7 @@ use bevy_ecs::{
|
|||||||
use bevy_math::Vec2;
|
use bevy_math::Vec2;
|
||||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||||
use bevy_render::texture::Image;
|
use bevy_render::texture::Image;
|
||||||
use bevy_sprite::TextureAtlas;
|
use bevy_sprite::TextureAtlasLayout;
|
||||||
use bevy_text::{
|
use bevy_text::{
|
||||||
scale_value, BreakLineOn, Font, FontAtlasSets, FontAtlasWarning, Text, TextError,
|
scale_value, BreakLineOn, Font, FontAtlasSets, FontAtlasWarning, Text, TextError,
|
||||||
TextLayoutInfo, TextMeasureInfo, TextPipeline, TextSettings, YAxisOrientation,
|
TextLayoutInfo, TextMeasureInfo, TextPipeline, TextSettings, YAxisOrientation,
|
||||||
@ -155,7 +155,7 @@ fn queue_text(
|
|||||||
text_pipeline: &mut TextPipeline,
|
text_pipeline: &mut TextPipeline,
|
||||||
font_atlas_warning: &mut FontAtlasWarning,
|
font_atlas_warning: &mut FontAtlasWarning,
|
||||||
font_atlas_sets: &mut FontAtlasSets,
|
font_atlas_sets: &mut FontAtlasSets,
|
||||||
texture_atlases: &mut Assets<TextureAtlas>,
|
texture_atlases: &mut Assets<TextureAtlasLayout>,
|
||||||
textures: &mut Assets<Image>,
|
textures: &mut Assets<Image>,
|
||||||
text_settings: &TextSettings,
|
text_settings: &TextSettings,
|
||||||
scale_factor: f32,
|
scale_factor: f32,
|
||||||
@ -226,7 +226,7 @@ pub fn text_system(
|
|||||||
text_settings: Res<TextSettings>,
|
text_settings: Res<TextSettings>,
|
||||||
mut font_atlas_warning: ResMut<FontAtlasWarning>,
|
mut font_atlas_warning: ResMut<FontAtlasWarning>,
|
||||||
ui_scale: Res<UiScale>,
|
ui_scale: Res<UiScale>,
|
||||||
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
|
||||||
mut font_atlas_sets: ResMut<FontAtlasSets>,
|
mut font_atlas_sets: ResMut<FontAtlasSets>,
|
||||||
mut text_pipeline: ResMut<TextPipeline>,
|
mut text_pipeline: ResMut<TextPipeline>,
|
||||||
mut text_query: Query<(Ref<Node>, &Text, &mut TextLayoutInfo, &mut TextFlags)>,
|
mut text_query: Query<(Ref<Node>, &Text, &mut TextLayoutInfo, &mut TextFlags)>,
|
||||||
|
@ -22,19 +22,15 @@ struct AnimationTimer(Timer);
|
|||||||
|
|
||||||
fn animate_sprite(
|
fn animate_sprite(
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mut query: Query<(
|
mut query: Query<(&AnimationIndices, &mut AnimationTimer, &mut TextureAtlas)>,
|
||||||
&AnimationIndices,
|
|
||||||
&mut AnimationTimer,
|
|
||||||
&mut TextureAtlasSprite,
|
|
||||||
)>,
|
|
||||||
) {
|
) {
|
||||||
for (indices, mut timer, mut sprite) in &mut query {
|
for (indices, mut timer, mut atlas) in &mut query {
|
||||||
timer.tick(time.delta());
|
timer.tick(time.delta());
|
||||||
if timer.just_finished() {
|
if timer.just_finished() {
|
||||||
sprite.index = if sprite.index == indices.last {
|
atlas.index = if atlas.index == indices.last {
|
||||||
indices.first
|
indices.first
|
||||||
} else {
|
} else {
|
||||||
sprite.index + 1
|
atlas.index + 1
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,19 +39,21 @@ fn animate_sprite(
|
|||||||
fn setup(
|
fn setup(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
asset_server: Res<AssetServer>,
|
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 = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png");
|
||||||
let texture_atlas =
|
let atlas = TextureAtlasLayout::from_grid(Vec2::new(24.0, 24.0), 7, 1, None, None);
|
||||||
TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1, None, None);
|
let texture_atlas = texture_atlases.add(atlas);
|
||||||
let texture_atlas_handle = texture_atlases.add(texture_atlas);
|
|
||||||
// Use only the subset of sprites in the sheet that make up the run animation
|
// Use only the subset of sprites in the sheet that make up the run animation
|
||||||
let animation_indices = AnimationIndices { first: 1, last: 6 };
|
let animation_indices = AnimationIndices { first: 1, last: 6 };
|
||||||
commands.spawn(Camera2dBundle::default());
|
commands.spawn(Camera2dBundle::default());
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
SpriteSheetBundle {
|
SpriteSheetBundle {
|
||||||
texture_atlas: texture_atlas_handle,
|
texture,
|
||||||
sprite: TextureAtlasSprite::new(animation_indices.first),
|
atlas: TextureAtlas {
|
||||||
|
layout: texture_atlas,
|
||||||
|
index: animation_indices.first,
|
||||||
|
},
|
||||||
transform: Transform::from_scale(Vec3::splat(6.0)),
|
transform: Transform::from_scale(Vec3::splat(6.0)),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
|
@ -52,15 +52,15 @@ fn setup(
|
|||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
rpg_sprite_handles: Res<RpgSpriteFolder>,
|
rpg_sprite_handles: Res<RpgSpriteFolder>,
|
||||||
asset_server: Res<AssetServer>,
|
asset_server: Res<AssetServer>,
|
||||||
|
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
|
||||||
loaded_folders: Res<Assets<LoadedFolder>>,
|
loaded_folders: Res<Assets<LoadedFolder>>,
|
||||||
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
|
||||||
mut textures: ResMut<Assets<Image>>,
|
mut textures: ResMut<Assets<Image>>,
|
||||||
) {
|
) {
|
||||||
let loaded_folder = loaded_folders.get(&rpg_sprite_handles.0).unwrap();
|
let loaded_folder = loaded_folders.get(&rpg_sprite_handles.0).unwrap();
|
||||||
|
|
||||||
// create texture atlases with different padding and sampling
|
// 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,
|
loaded_folder,
|
||||||
None,
|
None,
|
||||||
Some(ImageSampler::linear()),
|
Some(ImageSampler::linear()),
|
||||||
@ -68,7 +68,7 @@ fn setup(
|
|||||||
);
|
);
|
||||||
let atlas_linear_handle = texture_atlases.add(texture_atlas_linear.clone());
|
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,
|
loaded_folder,
|
||||||
None,
|
None,
|
||||||
Some(ImageSampler::nearest()),
|
Some(ImageSampler::nearest()),
|
||||||
@ -76,7 +76,7 @@ fn setup(
|
|||||||
);
|
);
|
||||||
let atlas_nearest_handle = texture_atlases.add(texture_atlas_nearest);
|
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,
|
loaded_folder,
|
||||||
Some(UVec2::new(6, 6)),
|
Some(UVec2::new(6, 6)),
|
||||||
Some(ImageSampler::linear()),
|
Some(ImageSampler::linear()),
|
||||||
@ -84,7 +84,7 @@ fn setup(
|
|||||||
);
|
);
|
||||||
let atlas_linear_padded_handle = texture_atlases.add(texture_atlas_linear_padded.clone());
|
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,
|
loaded_folder,
|
||||||
Some(UVec2::new(6, 6)),
|
Some(UVec2::new(6, 6)),
|
||||||
Some(ImageSampler::nearest()),
|
Some(ImageSampler::nearest()),
|
||||||
@ -99,7 +99,7 @@ fn setup(
|
|||||||
|
|
||||||
// draw unpadded texture atlas
|
// draw unpadded texture atlas
|
||||||
commands.spawn(SpriteBundle {
|
commands.spawn(SpriteBundle {
|
||||||
texture: texture_atlas_linear_padded.texture.clone(),
|
texture: linear_texture.clone(),
|
||||||
transform: Transform {
|
transform: Transform {
|
||||||
translation: Vec3::new(-250.0, -130.0, 0.0),
|
translation: Vec3::new(-250.0, -130.0, 0.0),
|
||||||
scale: Vec3::splat(0.8),
|
scale: Vec3::splat(0.8),
|
||||||
@ -110,7 +110,7 @@ fn setup(
|
|||||||
|
|
||||||
// draw padded texture atlas
|
// draw padded texture atlas
|
||||||
commands.spawn(SpriteBundle {
|
commands.spawn(SpriteBundle {
|
||||||
texture: texture_atlas_linear_padded.texture,
|
texture: linear_padded_texture.clone(),
|
||||||
transform: Transform {
|
transform: Transform {
|
||||||
translation: Vec3::new(250.0, -130.0, 0.0),
|
translation: Vec3::new(250.0, -130.0, 0.0),
|
||||||
scale: Vec3::splat(0.8),
|
scale: Vec3::splat(0.8),
|
||||||
@ -153,11 +153,21 @@ fn setup(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// configuration array to render sprites through iteration
|
// configuration array to render sprites through iteration
|
||||||
let configurations: [(&str, Handle<TextureAtlas>, f32); 4] = [
|
let configurations: [(&str, Handle<TextureAtlasLayout>, Handle<Image>, f32); 4] = [
|
||||||
("Linear", atlas_linear_handle, -350.0),
|
("Linear", atlas_linear_handle, linear_texture, -350.0),
|
||||||
("Nearest", atlas_nearest_handle, -150.0),
|
("Nearest", atlas_nearest_handle, nearest_texture, -150.0),
|
||||||
("Linear", atlas_linear_padded_handle, 150.0),
|
(
|
||||||
("Nearest", atlas_nearest_padded_handle, 350.0),
|
"Linear",
|
||||||
|
atlas_linear_padded_handle,
|
||||||
|
linear_padded_texture,
|
||||||
|
150.0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Nearest",
|
||||||
|
atlas_nearest_padded_handle,
|
||||||
|
nearest_padded_texture,
|
||||||
|
350.0,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
// label text style
|
// label text style
|
||||||
@ -169,9 +179,15 @@ fn setup(
|
|||||||
|
|
||||||
let base_y = 170.0; // y position of the sprites
|
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
|
// 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
|
// render a label to indicate the sampling setting
|
||||||
create_label(
|
create_label(
|
||||||
@ -190,7 +206,7 @@ fn create_texture_atlas(
|
|||||||
padding: Option<UVec2>,
|
padding: Option<UVec2>,
|
||||||
sampling: Option<ImageSampler>,
|
sampling: Option<ImageSampler>,
|
||||||
textures: &mut ResMut<Assets<Image>>,
|
textures: &mut ResMut<Assets<Image>>,
|
||||||
) -> TextureAtlas {
|
) -> (TextureAtlasLayout, Handle<Image>) {
|
||||||
// Build a `TextureAtlas` using the individual sprites
|
// Build a `TextureAtlas` using the individual sprites
|
||||||
let mut texture_atlas_builder =
|
let mut texture_atlas_builder =
|
||||||
TextureAtlasBuilder::default().padding(padding.unwrap_or_default());
|
TextureAtlasBuilder::default().padding(padding.unwrap_or_default());
|
||||||
@ -207,13 +223,13 @@ fn create_texture_atlas(
|
|||||||
texture_atlas_builder.add_texture(id, texture);
|
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
|
// 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();
|
image.sampler = sampling.unwrap_or_default();
|
||||||
|
|
||||||
texture_atlas
|
(texture_atlas, texture)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create and spawn a sprite from a texture atlas
|
/// Create and spawn a sprite from a texture atlas
|
||||||
@ -221,7 +237,8 @@ fn create_sprite_from_atlas(
|
|||||||
commands: &mut Commands,
|
commands: &mut Commands,
|
||||||
translation: (f32, f32, f32),
|
translation: (f32, f32, f32),
|
||||||
sprite_index: usize,
|
sprite_index: usize,
|
||||||
atlas_handle: Handle<TextureAtlas>,
|
atlas_handle: Handle<TextureAtlasLayout>,
|
||||||
|
texture: Handle<Image>,
|
||||||
) {
|
) {
|
||||||
commands.spawn(SpriteSheetBundle {
|
commands.spawn(SpriteSheetBundle {
|
||||||
transform: Transform {
|
transform: Transform {
|
||||||
@ -229,8 +246,11 @@ fn create_sprite_from_atlas(
|
|||||||
scale: Vec3::splat(3.0),
|
scale: Vec3::splat(3.0),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
sprite: TextureAtlasSprite::new(sprite_index),
|
texture,
|
||||||
texture_atlas: atlas_handle,
|
atlas: TextureAtlas {
|
||||||
|
index: sprite_index,
|
||||||
|
layout: atlas_handle,
|
||||||
|
},
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ fn main() {
|
|||||||
fn setup(
|
fn setup(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
assets: Res<AssetServer>,
|
assets: Res<AssetServer>,
|
||||||
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
|
||||||
) {
|
) {
|
||||||
warn!(include_str!("warning_string.txt"));
|
warn!(include_str!("warning_string.txt"));
|
||||||
|
|
||||||
@ -61,8 +61,7 @@ fn setup(
|
|||||||
let half_y = (map_size.y / 2.0) as i32;
|
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_handle = assets.load("textures/rpg/chars/gabe/gabe-idle-run.png");
|
||||||
let texture_atlas =
|
let texture_atlas = TextureAtlasLayout::from_grid(Vec2::new(24.0, 24.0), 7, 1, None, None);
|
||||||
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_atlas_handle = texture_atlases.add(texture_atlas);
|
||||||
|
|
||||||
// Spawns the camera
|
// Spawns the camera
|
||||||
@ -81,13 +80,17 @@ fn setup(
|
|||||||
|
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
SpriteSheetBundle {
|
SpriteSheetBundle {
|
||||||
texture_atlas: texture_atlas_handle.clone(),
|
texture: texture_handle.clone(),
|
||||||
|
atlas: TextureAtlas {
|
||||||
|
layout: texture_atlas_handle.clone(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
transform: Transform {
|
transform: Transform {
|
||||||
translation,
|
translation,
|
||||||
rotation,
|
rotation,
|
||||||
scale,
|
scale,
|
||||||
},
|
},
|
||||||
sprite: TextureAtlasSprite {
|
sprite: Sprite {
|
||||||
custom_size: Some(tile_size),
|
custom_size: Some(tile_size),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
@ -112,18 +115,14 @@ struct AnimationTimer(Timer);
|
|||||||
|
|
||||||
fn animate_sprite(
|
fn animate_sprite(
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
texture_atlases: Res<Assets<TextureAtlas>>,
|
texture_atlases: Res<Assets<TextureAtlasLayout>>,
|
||||||
mut query: Query<(
|
mut query: Query<(&mut AnimationTimer, &mut TextureAtlas)>,
|
||||||
&mut AnimationTimer,
|
|
||||||
&mut TextureAtlasSprite,
|
|
||||||
&Handle<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());
|
timer.tick(time.delta());
|
||||||
if timer.just_finished() {
|
if timer.just_finished() {
|
||||||
let texture_atlas = texture_atlases.get(texture_atlas_handle).unwrap();
|
let texture_atlas = texture_atlases.get(&sheet.layout).unwrap();
|
||||||
sprite.index = (sprite.index + 1) % texture_atlas.textures.len();
|
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
|
// System for printing the number of sprites on every tick of the timer
|
||||||
fn print_sprite_count(
|
fn print_sprite_count(time: Res<Time>, mut timer: Local<PrintingTimer>, sprites: Query<&Sprite>) {
|
||||||
time: Res<Time>,
|
|
||||||
mut timer: Local<PrintingTimer>,
|
|
||||||
sprites: Query<&TextureAtlasSprite>,
|
|
||||||
) {
|
|
||||||
timer.tick(time.delta());
|
timer.tick(time.delta());
|
||||||
|
|
||||||
if timer.just_finished() {
|
if timer.just_finished() {
|
||||||
|
@ -34,7 +34,6 @@ fn atlas_render_system(
|
|||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut state: ResMut<State>,
|
mut state: ResMut<State>,
|
||||||
font_atlas_sets: Res<FontAtlasSets>,
|
font_atlas_sets: Res<FontAtlasSets>,
|
||||||
texture_atlases: Res<Assets<TextureAtlas>>,
|
|
||||||
) {
|
) {
|
||||||
if let Some(set) = font_atlas_sets.get(&state.handle) {
|
if let Some(set) = font_atlas_sets.get(&state.handle) {
|
||||||
if let Some((_size, font_atlas)) = set.iter().next() {
|
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 {
|
if state.atlas_count == font_atlas.len() as u32 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let texture_atlas = texture_atlases
|
let font_atlas = &font_atlas[state.atlas_count as usize];
|
||||||
.get(&font_atlas[state.atlas_count as usize].texture_atlas)
|
|
||||||
.unwrap();
|
|
||||||
state.atlas_count += 1;
|
state.atlas_count += 1;
|
||||||
commands.spawn(ImageBundle {
|
commands.spawn(ImageBundle {
|
||||||
image: texture_atlas.texture.clone().into(),
|
image: font_atlas.texture.clone().into(),
|
||||||
style: Style {
|
style: Style {
|
||||||
position_type: PositionType::Absolute,
|
position_type: PositionType::Absolute,
|
||||||
top: Val::ZERO,
|
top: Val::ZERO,
|
||||||
|
@ -20,7 +20,7 @@ fn main() {
|
|||||||
fn setup(
|
fn setup(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
asset_server: Res<AssetServer>,
|
asset_server: Res<AssetServer>,
|
||||||
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
|
||||||
) {
|
) {
|
||||||
// Camera
|
// Camera
|
||||||
commands.spawn(Camera2dBundle::default());
|
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_handle = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png");
|
||||||
let texture_atlas =
|
let texture_atlas = TextureAtlasLayout::from_grid(Vec2::new(24.0, 24.0), 7, 1, None, None);
|
||||||
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_atlas_handle = texture_atlases.add(texture_atlas);
|
||||||
|
|
||||||
// root node
|
// root node
|
||||||
@ -56,8 +55,8 @@ fn setup(
|
|||||||
height: Val::Px(256.),
|
height: Val::Px(256.),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
texture_atlas: texture_atlas_handle,
|
texture_atlas: texture_atlas_handle.into(),
|
||||||
texture_atlas_image: UiTextureAtlasImage::default(),
|
image: UiImage::new(texture_handle),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
parent.spawn(TextBundle::from_sections([
|
parent.spawn(TextBundle::from_sections([
|
||||||
@ -75,7 +74,7 @@ fn setup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn increment_atlas_index(
|
fn increment_atlas_index(
|
||||||
mut atlas_images: Query<&mut UiTextureAtlasImage>,
|
mut atlas_images: Query<&mut TextureAtlas>,
|
||||||
keyboard: Res<ButtonInput<KeyCode>>,
|
keyboard: Res<ButtonInput<KeyCode>>,
|
||||||
) {
|
) {
|
||||||
if keyboard.just_pressed(KeyCode::Space) {
|
if keyboard.just_pressed(KeyCode::Space) {
|
||||||
|
Loading…
Reference in New Issue
Block a user