bevyengine / bevyengine/bevy

Combine multiple sprites into a single texture atlas

Open
#10,027 2 comments 0 reactions 0 assignees View on GitHub
A-Rendering C-Feature
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 16h
Merged PRs (30d)
171

Description

## What problem does this solve or what need does it fill?

You have two sprite sheets
* 16 16x16 sprites
![16x16](https://github.com/bevyengine/bevy/assets/27962798/5480b7f7-21b3-4926-b4fd-eadf6a56c4d3)

* 4 32x32 sprites
![32x32](https://github.com/bevyengine/bevy/assets/27962798/7fdc0cc0-bef1-4783-9862-843286c9a38c)

For both convenience and performance, you would like to load the two sprite sheets individually and then have Bevy combine them into a single texture atlas. But unfortunately, Bevy only supports loading homogeneous sprite sheets from a single source image.

## What solution would you like?

A method that allows you to take two (or more) sprite sheets and combine them into a single texture atlas:

![atlas](https://github.com/bevyengine/bevy/assets/27962798/51079180-0050-4a41-aaf9-ae6cf0569e6c)

Rough sketch of a possible API:
```bevy
pub enum SpriteSheetItem {
Grid {
texture: Handle,
tile_size: Vec2,
columns: usize,
rows: usize,
padding: Option,
offset: Option
},
SingleImage {
texture: Handle,
padding: Option,
},
}

impl TextureAtlas {
pub fn from_grids(
items: impl IntoIterator,
) {
//.. merge the atlases
}
}
```

Prior art: I wrote this function a while that merges multiple existing texture atlases:

```rust
pub fn merge_atlases<'a, I>(
image_assets: &mut Assets,
atlas_assets: &mut Assets,
atlas_handles: I,
) -> Handle
where
I: IntoIterator>,
{
let mut builder = TextureAtlasBuilder::default();
let atlases = atlas_handles
.into_iter()
.map(|h| atlas_assets.get(h).expect("Missing texture atlas"))
.collect::>();
for atlas in &atlases {
let image = image_assets
.get(&atlas.texture)
.expect("Missing atlas texture");
builder.add_texture(atlas.texture.clone_weak(), image);
}
let mut meta_atlas = builder.finish(image_assets).unwrap();
let meta_rects = std::mem::take(&mut meta_atlas.textures);
let meta_handles = std::mem::take(&mut meta_atlas.texture_handles).unwrap();
for atlas in &atlases {
let meta_rect_index = meta_handles[&atlas.texture];
let meta_rect = meta_rects[meta_rect_index];
for &rect in atlas.textures.iter() {
let meta_sub_rect = Rect {
min: meta_rect.min + rect.min,
max: meta_rect.min + rect.max,
};
meta_atlas.add_texture(meta_sub_rect);
}
}
atlas_assets.add(meta_atlas)
}
```
It has problems though:
* It no longer works since 0.11 changed the visibility of the texture handles field to `pub(crate)`. This wouldn't be a problem if it's added to the bevy_sprite crate.
* It only merges already existing texture atlases. This means that to create the texture atlas in my example above you would have to generate two intermediate texture atlases from the sprite sheet images to pass into `merge_atlases`, which isn't ideal.

A more ambitious implementation could chop up the sprite sheets and then add the sprites individually into a `TextureAtlasBuilder`. This would allow you to add padding to unpadded source sprite sheet images and might reduce the size of the output texture atlas.

Contributor guide

Open the contributing guide

Research direction

Start by reading TextureAtlasBuilder and the TextureAtlas and Image asset APIs in the bevy_sprite crate, especially how texture handles and atlas rectangles are represented. Compare the proposed SpriteSheetItem API with the existing merge_atlases example and determine how grid inputs, padding, offsets, and multiple source images should be supported. Done means multiple sprite sheets can produce one usable texture atlas without requiring intermediate atlases.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
game-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.