动态读写
有了反射能力(derive(Reflect))和类型目录(TypeRegistry),第三块拼图是在运行时读写字段。这是反射最实际的价值:Scene 加载时把文件里的值写进组件、Inspector 面板让你拖滑条改属性、BRP 远程修改实体数据——本质上都是同一套操作。
通过字符串名读写字段
derive(Reflect) 为结构体生成了 get_field::<T>(name) 和 get_field_mut::<T>(name) 方法,可以直接按字段名拿到具体类型的引用。当你不确定字段类型,或者想用泛型逻辑处理任意字段时,走 trait 方法更灵活:
let mut monster = Monster {
name: "Goblin".into(),
hp: 30,
speed: 5.0,
};
if let ReflectMut::Struct(s) = monster.reflect_mut() {
if let Some(hp) = s.field_mut("hp") {
if let Some(hp_val) = hp.try_downcast_mut::<i32>() {
*hp_val -= 10;
}
}
}
println!("受伤后 HP: {}", monster.hp);reflect_mut() 返回 ReflectMut 枚举,匹配 Struct variant 后拿到 &mut dyn Struct——可以用 field_mut("hp") 拿到字段的 &mut dyn PartialReflect,再 try_downcast_mut::<i32>() 转回具体类型。
apply:用动态值打补丁
apply 是反射的"写入"原语:把一个 dyn PartialReflect 的值"涂"到另一个 Reflect 值上。对于结构体,它按字段名匹配——只覆盖同名且同类型的字段,忽略其余:
let mut patch = DynamicStruct::default();
patch.insert("hp", 999i32);
patch.insert("speed", 99.0f32);
monster.apply(&patch);
print_fields("补丁后", &monster);DynamicStruct 是运行时构造的"匿名结构体"——它实现了 Struct 和 PartialReflect,可以当补丁用。Scene 系统加载 .scn 文件时,反序列化出来的就是 DynamicStruct,然后 apply 到真实组件上。
这个机制的妙处是部分更新:补丁里只写了 hp 和 speed,name 字段不受影响。这正是 Scene 和 BRP 能"只修改部分属性"的底层原理。
to_dynamic:快照
to_dynamic() 把一个具体类型的值转成对应的动态表示——结构体变 DynamicStruct,列表变 DynamicList,枚举变 DynamicEnum:
let snapshot = monster.to_dynamic();
println!("快照是 dynamic: {}", snapshot.is_dynamic());
println!("快照 represent: {:?}", snapshot.get_represented_type_info().map(|i| i.type_path()));动态值保留了原始类型的信息(get_represented_type_info()),所以你可以用它做序列化、网络传输,或者存下来以后 apply 回去。is_dynamic() 返回 true 表明这是一个运行时构造的值,而非具体类型。
嵌套类型的反射
反射是递归的——如果你的结构体字段本身也是 Reflect 类型,反射会自动穿透进去:
#[derive(Reflect)]
struct Transform2D {
translation: Vec2,
rotation: f32,
}
#[derive(Reflect)]
struct Sprite {
transform: Transform2D,
color: Color,
z_index: i32,
}let sprite = Sprite {
transform: Transform2D {
translation: Vec2::new(100.0, 200.0),
rotation: 0.5,
},
color: Color::srgb(1.0, 0.0, 0.0),
z_index: 10,
};
if let ReflectRef::Struct(outer) = sprite.reflect_ref() {
let transform = outer.field("transform").unwrap();
if let ReflectRef::Struct(inner) = transform.reflect_ref() {
let tx = inner.field("translation").unwrap();
println!("translation = {tx:?}");
}
}apply 同样支持嵌套——补丁里的 transform 字段本身是一个 DynamicStruct,它会递归地应用到 Transform2D 上:
let mut enemy = Sprite {
transform: Transform2D {
translation: Vec2::ZERO,
rotation: 0.0,
},
color: Color::WHITE,
z_index: 0,
};
let mut patch_transform = bevy::reflect::structs::DynamicStruct::default();
patch_transform.insert("translation", Vec2::new(50.0, 50.0));
let mut patch = bevy::reflect::structs::DynamicStruct::default();
patch.insert_boxed("transform", Box::new(patch_transform));
patch.insert("z_index", 100i32);
enemy.apply(&patch);
println!("enemy.translation = {:?}", enemy.transform.translation);
println!("enemy.z_index = {}", enemy.z_index);标准容器类型(Vec<T>、HashMap<K,V>、Option<T>)也都实现了 Reflect。Vec 是 List 种类,HashMap 是 Map 种类——它们的 apply 语义各不相同:List 按索引逐元素应用,Map 按键匹配:
let mut items: Vec<i32> = vec![1, 2, 3];
let mut patch_list = bevy::reflect::list::DynamicList::default();
patch_list.push(10i32);
patch_list.push(20i32);
patch_list.push(30i32);
items.apply(&patch_list);
println!("items = {items:?}");运行两个 listing 看效果:
cargo run -p ch31-reflect --example listing-31-03
cargo run -p ch31-reflect --example listing-31-04下一节把这些能力串起来:看 Scene、Inspector、Remote 为什么全都依赖反射。