Allow ExtendedMaterials that share a common base material to be queried together
- 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?
I'm making a game where different monster types use different materials, but _all_ of these materials extend a common base material. (The most common use case would be extending StandardMaterial.) For example, monster A might use `ExtendedMaterial`, and monster B might use `ExtendedMaterial`. Frequently I need to query all my monsters and update some common field within `MyBaseMaterial`, e.g. an `opacity` field that fades the character in or out.
But right now, each different extended material needs its own separate query and separate mutable resource. I need to do something like:
```rust
fn my_system(
material_a_query: Query<
&MeshMaterial3d>,
>,
material_b_query: Query<
&MeshMaterial3d>,
>,
/* ... repeat for each different material extension */
mut materials_a: ResMut>>,
mut materials_b: ResMut>>,
/* ...repeat for each different material extension */
) {
for material_handle in material_a_query.iter() {
if let Some(material) = materials_a.get_mut(material_handle) {
material.base.opacity = opacity;
}
}
for material_handle in material_b_query.iter() {
if let Some(material) = materials_b.get_mut(material_handle) {
material.base.opacity = opacity;
}
}
/* ...repeat for each different material extension */
}
```
_Note: The code above is even more verbose when using GLTF models, because each of the material queries becomes a nested query._
## What solution would you like?
It would be great if there was a way to:
1. Use a single query to capture all materials that extend `MyBaseMaterial`.
2. Have a single resource or resource API that can mutably access `MyBaseMaterial`.
For example, something like:
```rust
fn my_system(
base_material_query: Query<&HasMaterialExtension>,
mut materials: ResMut>,
) {
for material_handle in material_query.iter() {
if let Some(material) = materials.get_mut(material_handle) {
material.base.opacity = opacity;
}
}
}
```
## What alternative(s) have you considered?
For just the querying part, I could probably achieve this with a trait-based query approach like https://github.com/joseph-gio/bevy-trait-query. Two issues with this approach, though:
First, it requires registration onto the app, like this:
```rust
app
.register_component_as::, ExtendedMaterial>>()
.register_component_as::, ExtendedMaterial>>()
/* ...repeat for each different material extension */
```
This seems repetitive, since extended materials are _already_ registered similarly, like this:
```rust
app
.add_plugins(MaterialPlugin::<
ExtendedMaterial,
>::default())
```
So perhaps Bevy could do a similar trait-based registration internally at this point.
But the bigger issue is mutating the material resource. Even if I used the trait-based query library, this doesn't actually help, because I still need to include a new `ResMut>` in my system for each different material extension, and correctly map each queried entity with the proper mutable material asset.
My other options are:
1) Live with having huge systems to individually query for each different material extension.
2) Create another component (e.g. "Opacity" in this case), then create a new system for each different extended material that reads the opacity value and updates the material accordingly. Lots of boilerplate here, but this would simplify systems that needs to do these kinds of updates.
3) Create macros to reduce the boilerplate of my original example or of the previous alternative option.
4) Give up on having unique procedural materials for different monsters and just use a common material. :)
For now, I'll probably do approaches 2 and 3. But, I think extending materials (especially StandardMaterial) is a pretty common use-case in Bevy, and it would be nice to have a more scalable way to modify base materials.
Contributor guide
Research direction
Start by inspecting ExtendedMaterial and MaterialPlugin registration, then trace how material queries and Assets resources are represented. Compare the requested single query and mutable base-material access against the current registration model and the trait-query alternative. Done should be defined as one way to query all extensions of a common base and mutate that base material without one resource per extension.
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
- 25/100