场景就绪后改材质
glTF 加载的材质是自动映射的 StandardMaterial。有时候你想在加载后修改某个材质——比如给某个部件染色、替换贴图、调整金属度。问题在于:WorldAssetRoot 展开实体是异步的,spawn 之后立刻去改子实体,还没长出来。
WorldInstanceReady:等场景长完
WorldInstanceReady 是 bevy_scene 提供的一个实体事件,在 WorldAssetRoot 的所有子实体就绪后触发。配合 Observer(第 8 章),可以精确地在场景展开完成后执行修改:
rust
use bevy::{gltf::GltfMaterialName, prelude::*, world_serialization::WorldInstanceReady};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_observer(override_material)
.run();
}
#[derive(Component)]
struct RedTint;
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
));
commands.spawn((
DirectionalLight {
contact_shadows_enabled: true,
..default()
},
Transform::from_xyz(4.0, 25.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
));
let helmet =
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));
commands.spawn(WorldAssetRoot(helmet.clone()));
commands.spawn((
WorldAssetRoot(helmet),
Transform::from_xyz(-1.5, 0.0, 0.0),
RedTint,
));
}
fn override_material(
trigger: On<WorldInstanceReady>,
mut commands: Commands,
children: Query<&Children>,
has_tint: Query<&RedTint>,
mesh_mats: Query<(&MeshMaterial3d<StandardMaterial>, &GltfMaterialName)>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let Ok(_) = has_tint.get(trigger.entity) else {
return;
};
for desc in children.iter_descendants(trigger.entity) {
let Ok((mat_id, name)) = mesh_mats.get(desc) else {
continue;
};
let Some(mat) = materials.get(mat_id.id()) else {
continue;
};
if name.0 == "LeatherPartsMat" {
let mut new_mat = mat.clone();
new_mat.base_color = Color::srgb(0.9, 0.2, 0.2);
commands
.entity(desc)
.insert(MeshMaterial3d(materials.add(new_mat)));
}
}
}Listing 23-6:用 Observer + WorldInstanceReady 给特定材质染色(examples/listing-23-06.rs)
console
cargo run -p ch23-gltf --example listing-23-06第二只头盔的皮革部件被染成红色。
工作原理
setup里 spawn 两个WorldAssetRoot,第二个带RedTint标记组件add_observer(override_material)注册一个全局 Observer,监听WorldInstanceReady事件- 场景就绪后,Observer 拿到触发事件的实体,检查有没有
RedTint - 遍历该实体的所有后代(
children.iter_descendants),找到带GltfMaterialName的网格 - 匹配材质名
"LeatherPartsMat",克隆材质、改颜色、换回去
这个模式的核心是 iter_descendants——glTF 场景展开后是一个实体树,每个网格实体都带着 MeshMaterial3d<StandardMaterial> 和 GltfMaterialName 组件。遍历这棵树就能找到任何你想修改的部件。
直接改 vs 换提货单
改材质有两种策略:
- 改资产本体:
materials.get_mut(handle)直接修改Assets<StandardMaterial>里的数据。所有引用这个 Handle 的实例都会受影响——第 14 章"改资产,所有提货单跟着变"的规矩。 - 换提货单:克隆一份新材质、改好、
materials.add入库拿到新 Handle,然后commands.entity(desc).insert(MeshMaterial3d(new_handle))替换组件。只有这一个实例受影响。
Listing 23-6 用的是后者——只染第二只头盔,第一只不受影响。