bevy
bevy copied to clipboard
Implement images with layout for TextureAtlasBuilder
Objective
- Closes #10027
- Partially #9986
- Doesn't add spacing but allows a way to solve the issue.
Solution
- Allow TextureAtlasBuilder to accept an atlas layout. Given image is sliced into sub-images to generate the final atlas.
Testing
Example code
use bevy::{asset::LoadedFolder, prelude::*};
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) // fallback to nearest sampling
.init_state::<AppState>()
.add_systems(OnEnter(AppState::Setup), load_textures)
.add_systems(Update, check_textures.run_if(in_state(AppState::Setup)))
.add_systems(OnEnter(AppState::Finished), setup)
.run();
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, States)]
enum AppState {
#[default]
Setup,
Finished,
}
#[derive(Resource, Default)]
struct RpgSpriteFolder(Handle<LoadedFolder>);
fn load_textures(mut commands: Commands, asset_server: Res<AssetServer>) {
// load multiple, individual sprites from a folder
commands.insert_resource(RpgSpriteFolder(asset_server.load_folder("textures/rpg")));
}
fn check_textures(
mut next_state: ResMut<NextState<AppState>>,
rpg_sprite_folder: Res<RpgSpriteFolder>,
mut events: EventReader<AssetEvent<LoadedFolder>>,
) {
// Advance the `AppState` once all sprite handles have been loaded by the `AssetServer`
for event in events.read() {
if event.is_loaded_with_dependencies(&rpg_sprite_folder.0) {
next_state.set(AppState::Finished);
}
}
}
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 textures: ResMut<Assets<Image>>,
) {
let gabe = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png");
let mani = asset_server.load("textures/rpg/chars/mani/mani-idle-run.png");
commands.spawn(Camera2dBundle::default());
let mut atlas_builder = TextureAtlasBuilder::default().padding(UVec2::splat(0));
atlas_builder.add_texture_with_layout(
Some(gabe.id()),
textures.get(gabe.id()).unwrap(),
TextureAtlasLayout::from_grid(UVec2::splat(24), 7, 1, None, None),
);
atlas_builder.add_texture_with_layout(
Some(mani.id()),
textures.get(mani.id()).unwrap(),
TextureAtlasLayout::from_grid(UVec2::splat(24), 7, 1, None, None),
);
let (layout, image) = atlas_builder.finish().unwrap();
let texture = textures.add(image);
let gabe_layout = texture_atlases.add(layout.sub_layout(gabe.id()).unwrap());
//let mani_indexes = layout.get_texture_index(mani.id()).unwrap();
//let layout_handle = texture_atlases.add(layout.clone());
commands
.spawn(SpriteBundle {
texture: texture.clone(),
transform: Transform::from_xyz(0.0, 48.0, 0.0),
..default()
})
.insert(TextureAtlas {
layout: gabe_layout,
index: 6,
});
commands.spawn(SpriteBundle {
texture,
..default()
});
}
Changelog
Added
- Image::try_sub_image
Generates a subimage based on the texture and the given rectangular region.
- TextureAtlasBuilder::add_texture_with_layout
Iterates over each rectangle defined in the layout and attempts to extract sub-images from the provided image based on the layout. The sub-images are then added to the textures to place in the texture atlas builder.
- TextureAtlasLayout::sub_layout
Generates the TextureAtlasLayout from the given Asset<Image> in the method above.
Changed
- get_texture_index(&self, texture: impl Into<AssetId<Image>>) -> Option<usize>
+ get_texture_index(&self, texture: impl Into<AssetId<Image>>) -> Option<&[usize]>
Migration Guide
let image_id: AssetId<Image>
let layout: TextureAtlasLayout
- let layout_index = layout.get_texture_index(image_id);
+ let layout_index = layout.get_texture_index(image_id)[0];