bevyengine / bevyengine/bevy

Texture atlases should use atlas layouts, not atlas indices

Open
#15,365 2 comments 0 reactions 0 assignees View on GitHub
A-Rendering C-Usability D-Modest S-Ready-For-Implementation X-Contentious
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 22h
Merged PRs (30d)
161

Description

IMO, the way we generate atlases is flawed and wrongly encourages using atlas indices. This is particularly apparent when dealing with sprite animations: have an atlas with two sprite-sheets (gabe and alice) with identical animations (10 frame). You have to hard-code that gabe animation goes from 0-9 and alice from 10-19 which is stupid, both go from 0-9 in their local space!

Instead of using atlas indices we should be encouraging _atlas layouts_:

- Sprites stay in their local space so animations can be reused.
- No longer need a Hashmap in the layout since everything **is** from that sprite.
- In the future, we no longer would need resources to keep track of what sprite is what, instead we could have an asset loader similar to .gltf and return differents part of the atlas e.g. `asset_server.load("atlas.sprites")` would return the atlas image, `asset_server.load("atlas.sprite#gabe")` would return gabe's layout and `asset_server.load("atlas_sprite#gabe/walk")` would return gabe's animation (wouldn't that be neat?)

Working with atlas layouts right now is a bit annoying though, you need to build your atlas, keep the image handles -which sucks when building a spritesheet from other spritesheets- alive (if it drops the handle will not be the same as in the layout, oops!), grab the index, find the next sprite index (or hard-code the number), grab a slice of the rects then build and add your layouts to the asset server. AtlasBuilder is this way because of #2987.

Now, what i would do:

- Separate AtlasBuilder into two, one with an AssetServer version and one without. This way it can be used in asset context
- Personally, i would go with a single struct with a state generic so you would have a `AtlasBuilder` and `AtlasBuilder` that would be built like so: `AtlasBuilder<'a, None>.with_asset_server(self, server: &AssetServer) -> AtlasBuilder<'a, AssetState>`.
- `AtlasBuilder<'a, AssetState>` would have a method `layout(&'a mut self) -> LayoutBuilder<'a>`. LayoutBuilder would have the usual add_texture operations like AtlasBuilder, but on build() it would create a `Handle`, store it in AtlasBuilder and return it to the user.
- `AtlasBuilder<'a, AssetState>.build() -> (Handle, Handle)` would do the same as now and add the proper rects to all the `Handle`.

This is a rough sketch and I'm sure i missed things, but it would look something like this:

```rs
fn build_atlas(mut asset_server: AssetServer, mut sprites: ResMut) {
let mut texture_atlas_builder = TextureAtlasBuilder::default().with_asset_server(&asset_server);
let gabe_handle: Handle = texture_atlas_builder
.layout()
.from_grid("gabe.png", /*parameters here*/)
.build();
// Add gabe handle to sprites or spawn or whatever
let alice_handle: Handle = texture_atlas_builder
.layout()
.add_texture("alice00.png")
.add_texture("alice01.png")
.build();
// Same as above
let (layout, image) = texture_atlas_builder.build();
}
```

_Originally posted by @s-puig in https://github.com/bevyengine/bevy/issues/15344#issuecomment-2366766555_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.