bevyengine / bevyengine/bevy

Allow ExtendedMaterials that share a common base material to be queried together

Open
#20,070 0 comments 0 reactions 0 assignees View on GitHub
A-ECS A-Rendering C-Feature S-Needs-Design
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.