# Objective
- In 0.18, we had 10 different functions that load assets (I'm not even
counting `load_folder`).
- In 0.19, we've even added `load_erased` - but it unfortunately doesn't
support all the features that the other variants support.
- We apparently needed `load_acquire_with_settings_override` which 1)
loads the asset, 2) uses the settings provided, 3) allows reading
unapproved asset paths, and 4) drops a guard once the load completes.
- That's fine if that's necessary. But we needed to create an explicit
variant for that.
- We need fewer load paths!
## Solution
- Create a builder.
- Store all these options dynamically instead of statically handling
each case.
- Have the caller choose a particular "kind" of load when they are
ready: `load`, `load_erased`, `load_untyped`, or `load_untyped_async`.
- I intentionally didn't provide a `load_async` or `load_erased_async`,
since those can be replicated using `load`/`load_erased` +
`AssetServer::wait_for_asset_id` to get the exact same effect.
I am also intentionally leaving `NestedLoader` untouched in this PR, but
a followup will duplicate this API for `NestedLoader`, which should make
it easier to understand.
Unlike the `NestedLoader` API, we aren't doing any type-state craziness,
so the docs are much more clear: users don't need to understand how
type-state stuff works, they just call the handful of methods on the
type. The "cost" here is we now need to be careful about including the
cross product of loads between static asset type, runtime asset type, or
dynamic asset type, crossed with deferred or async. In theory, if we
added more kinds on either side, we would need to expand this cross
product a lot. In practice though, it seems unlikely there will be any
more variants there. (maybe there could be a blocking variant? I don't
think this is a popular opinion though).
A big con here is some somewhat common calls are now more verbose.
Specifically, `asset_server.load_with_settings()` has become
`asset_server.load_builder().with_settings().load()`. I am not really
concerned about this though, since it really isn't that painful.
## Testing
- Tests all pass!
---
## Showcase
Now instead of:
```rust
asset_server.load_acquire_with_settings_override("some_path", |settings: &mut GltfLoaderSettings| { ... }, my_lock_guard);
```
You can instead do:
```rust
asset_server.load_builder()
.with_guard(my_lock_guard)
.with_settings(|settings: &mut GltfLoaderSettings| { ... })
.override_unapproved()
.load("some_path");
```
We also now cover more variants! For example, you can now load an asset
untyped with a guard, or with override_unapproved, etc.
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
# Objective
- `glam`, `hexasphere` & `rand` have released their latest versions,
update Bevy to support them.
## Solution
- The above have been updated to their compatible versions. `rand_distr`
updated as well to match `rand` v0.10 support.
- `rand_chacha` is soft deprecated and no longer used by `rand`, so its
usage has been changed to `chacha20` to match `rand` dep tree.
- `uuid` is in the process of updating to `getrandom` v0.4, which `rand`
v0.10 supports. This PR remains in draft until a new `uuid` release hits
crates.io.
- `RngCore` is now `Rng`, and `Rng` is now `RngExt`, so this required
updating across many files.
- `choose_multiple` method is deprecated, changed to `sample`.
## Testing
- Chase all compiler errors, since this should not regress any already
existing behaviour.
- This must pass CI without regressions.
## Additional Notes
`getrandom` v0.4 doesn't add anything new for Web WASM support, so the
same `wasm_js` feature is used.
# Objective
If you want to use a `TilemapChunk` (or more generally use a
`texture2DArray` in a shader), you have to implement a mechanism that
waits for your texture to load, then calls
`Image::reinterpret_stacked_2d_as_array`.
## Solution
Have the loader do it instead.
Closes#20799, which does very similar things and should be remade if
more functionality is needed.
## Testing
- Ran the updated examples
---
## Showcase
```rs
let array_texture = asset_server.load_with_settings(
"textures/array_texture.png",
|settings: &mut ImageLoaderSettings| {
settings.array_layout = Some(ImageArrayLayout::RowCount(4));
},
);
```
# Objective
Remove panics from image reinterpretation methods.
## Solution
Introduce new errors and emit those instead of panicking.
## Testing
- Did you test these changes? If so, how?
- CI + changed examples
- Are there any parts that need more testing?
- No
- How can other people (reviewers) test your changes? Is there anything
specific they need to know?
- examples, no
- If relevant, what platforms did you test these changes on, and are
there any important ones you can't test?
- linux, amd cpu, amd gpu
---
Moved out of #20536
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
# Objective
Placing a player relative to a `TilemapChunk` but not in the tilemap
itself (for example, Pokemon's Trainer) requires calculating the
Transform of the relevant tile.
Animating these values (ex: Pokemon Trainer walking across tiles) also
requires Transforms
## Solution
Add a helper to calculate the transforms of tiles.
## Testing
```
cargo run --example tilemap_chunk
```
```rust
let mut transform = chunk.calculate_tile_transform(UVec2::new(5, 6));
transform.translation.z = 1.;
commands.spawn((
Mesh2d(meshes.add(Rectangle::new(8., 8.))),
MeshMaterial2d(materials.add(Color::from(RED_400))),
transform,
));
```
## Showcase
https://github.com/user-attachments/assets/97efeba0-fb09-4b77-9907-17710272d601
---
## Other
Maybe the function should return a z-index of 1 by default? I found
myself "raising" the z-index basically immediately... or Transform
should have a `with_z` function like the underlying Translation does.
# Objective
When using the new tilemap chunk facilities, getting the data associated
with a specific tile is not currently exposed via API, making users do
the math themselves.
## Solution
Add a helper function to calculate the index position of `TileData`
given a chunk size and a position
## Testing
This example now includes a log that logs out when a specific tile's
index changes
```
cargo run --example tilemap_chunk
```
```
2025-09-21T07:36:23.489006Z INFO tilemap_chunk: tile_data changed tile_data=TileData { tileset_index: 2, color: LinearRgba(LinearRgba { red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0 }), visible: true }
2025-09-21T07:36:32.514408Z INFO tilemap_chunk: tile_data changed tile_data=TileData { tileset_index: 3, color: LinearRgba(LinearRgba { red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0 }), visible: true }
2025-09-21T07:36:37.040941Z INFO tilemap_chunk: tile_data changed tile_data=TileData { tileset_index: 2, color: LinearRgba(LinearRgba { red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0 }), visible: true }
```
---
## Showcase
```rust
fn log_tile(tilemap: Single<(&TilemapChunk, &TilemapChunkTileData)>) {
let (chunk, data) = tilemap.into_inner();
let Some(tile_data) = data.tile_data_from_tile_pos(chunk.chunk_size, UVec2::new(3, 4)) else {
return;
};
// do something with tile_data
}
```
---
I alternatively considered storing the chunk size in the
`TilemapChunkTileData` as a private field, since the component hooks
guarantee the chunk_size needs to match between TilemapChunk and the
data. This would eliminate the chunk_size argument from the function but
also require re-setting the chunk size to reuse the same data with a
different chunk_size with the same length.
We could take this alternative approach, but I figured the simpler
helper would have a higher chance of making it into 0.17.
The function could also be named something like `data.at(chunk_size,
position)` instead of the longer name here.
prior art for storage helpers on arrays that are stored like this:
https://github.com/StarArawn/bevy_ecs_tilemap/blob/c2c1d076bd70d132edb6410e835a2ba6bdbaab5c/src/tiles/storage.rs#L43
This renames the concept of `BufferedEvent` to `Message`, and updates
our APIs, comments, and documentation to refer to these types as
"messages" instead of "events". It also removes/updates anything that
considers messages to be "observable", "listenable", or "triggerable".
This is a followup to https://github.com/bevyengine/bevy/pull/20731,
which omitted the `BufferedEvent -> Message` rename for brevity.
See that post for rationale.
# Objective
New `rand` version, which means updating `glam` and `encase` to support
the newer ecosystem update. Does mean that this changes how WASM builds
need to be done in order to configure `getrandom` correctly, but this
can be remedied with updated docs.
## Solution
Updating all needed dependencies to their compatible versions. ~~This PR
is currently blocked by `encase`, which is waiting on [this
PR](https://github.com/teoxoy/encase/pull/88) to be merged and then a
new version published.~~ ~~This PR is no longer blocked~~,
~~`hexasphere` is blocking this PR now due to not yet having a new
release with the latest `glam` version support~~, The PR is all good to
go now, everything in order across glam/rand deps.
## Testing
- Must pass CI for all checks, tests, not introduce breaking changes.
---
## Migration Guide
With newer versions of `glam` & `encase`, the updated versions don't
seem to have introduced breakages, though as always, best to consult
their docs [1](https://docs.rs/glam/latest/glam/)
[2](https://docs.rs/encase/0.11.0/encase/) for any changes.
`rand` changes are more extensive, with changes such as `thread_rng()`
-> `rng()`, `from_entropy()` -> `from_os_rng()`, and so forth. `RngCore`
is now split into infallible `RngCore` and fallible `TryRngCore`, and
the `distributions` module has been renamed to `distr`. Most of this
affects only internals, and doesn't directly affect Bevy's APIs. For the
full set of changes, see `rand` [migration
notes](https://rust-random.github.io/book/update-0.9.html).
`getrandom` is also updated, and will require additional configuration
when building Bevy for WASM/Web (if also using `rand`). The full details
of how to do this is in the `getrandom` docs
[1](https://github.com/rust-random/getrandom?tab=readme-ov-file#opt-in-backends)
[2](https://github.com/rust-random/getrandom?tab=readme-ov-file#webassembly-support).
---------
Co-authored-by: François Mockers <francois.mockers@vleue.com>
# Objective
- Use a single quad to render a TilemapChunk
- Add support for tile color and visibility
## Testing
- Tested using example -- there doesn't appear to be any visual tile
bleeding or rounding issues. Open to ideas on further testing
# Objective
An attempt to start building a base for first-party tilemaps (#13782).
The objective is to create a very simple tilemap chunk rendering plugin
that can be used as a building block for 3rd-party tilemap crates, and
eventually a first-party tilemap implementation.
## Solution
- Introduces two user-facing components, `TilemapChunk` and
`TilemapChunkIndices`, and a new material `TilemapChunkMaterial`.
- `TilemapChunk` holds the chunk and tile sizes, and the tileset image
- The tileset image is expected to be a layered image for use with
`texture_2d_array`, with the assumption that atlases or multiple images
would go through an asset loader/processor. Not sure if that should be
part of this PR or not..
- `TilemapChunkIndices` holds a 1d representation of all of the tile's
Option<u32> index into the tileset image.
- Indices are fixed to the size of tiles in a chunk (though maybe this
should just be an assertion instead?)
- Indices are cloned and sent to the shader through a u32 texture.
## Testing
- Initial testing done with the `tilemap_chunk` example, though I need
to include some way to update indices as part of it.
- Tested wasm with webgl2 and webgpu
- I'm thinking it would probably be good to do some basic perf testing.
---
## Showcase
```rust
let chunk_size = UVec2::splat(64);
let tile_size = UVec2::splat(16);
let indices: Vec<Option<u32>> = (0..chunk_size.x * chunk_size.y)
.map(|_| rng.gen_range(0..5))
.map(|i| if i == 0 { None } else { Some(i - 1) })
.collect();
commands.spawn((
TilemapChunk {
chunk_size,
tile_size,
tileset,
},
TilemapChunkIndices(indices),
));
```
