mutation_by_reflection/mutation_by_reflection.rs
1//! Demonstrates how to modify component values in a type-erased way,
2//! using Bevy's runtime type reflection functionality
3//! to operate generically over any component type or shape of data.
4//!
5//! This path is useful when building tools like inspectors or for integrating scripting languages,
6//! where you want to modify component values without knowing the type at compile time.
7//! It will be *much* slower than modifying values directly,
8//! so it should only be used when you have no other choice,
9//! or when flexibility is the most important consideration.
10
11use bevy::{prelude::*, reflect::ReflectMut};
12
13fn main() {
14 App::new()
15 .add_plugins(DefaultPlugins)
16 .init_resource::<SelectedComponent>()
17 .add_systems(Startup, setup)
18 .add_systems(
19 Update,
20 (select_component_to_modify, modify_selected_component),
21 )
22 .run();
23}
24
25fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
26 commands.spawn(Camera2d);
27 commands.spawn(Sprite {
28 image: asset_server.load("branding/bevy_logo_dark.png"),
29 ..Default::default()
30 });
31
32 let instructions = "\
33Press 'T' to select the Transform component's y-translation for modification
34Press 'S' to select the Sprite component's alpha value for modification
35Press 'Up Arrow' to increase the selected component's value
36Press 'Down Arrow' to decrease the selected component's value"
37 .to_string();
38
39 commands.spawn((
40 Text::new(instructions),
41 Node {
42 position_type: PositionType::Absolute,
43 top: px(12),
44 left: px(12),
45 ..default()
46 },
47 ));
48}
49
50// The component type to modify should generally be selected via UI.
51#[derive(Resource, Default, Clone)]
52enum SelectedComponent {
53 #[default]
54 Transform,
55 Sprite,
56}
57
58// Quickly select which component to modify via keyboard input,
59// avoiding the need for a full UI in this example.
60fn select_component_to_modify(
61 keyboard_input: Res<ButtonInput<KeyCode>>,
62 mut selected: ResMut<SelectedComponent>,
63) {
64 if keyboard_input.just_pressed(KeyCode::KeyT) {
65 *selected = SelectedComponent::Transform;
66 info!("Selected Transform component for modification");
67 } else if keyboard_input.just_pressed(KeyCode::KeyS) {
68 *selected = SelectedComponent::Sprite;
69 info!("Selected Sprite component for modification");
70 }
71}
72
73/// This function demonstrates the core logic of modifying a component value via reflection.
74///
75/// Because we're operating over *any* component type,
76/// we require full `&mut` access to [`World`].
77///
78/// To mutate a component value via reflection:
79/// 1. Determine the entity whose component you want to modify.
80/// 2. Determine the `TypeId` of the component type to modify. A real tool starts from a
81/// type *name*, so we resolve it to a `TypeId` through the [`AppTypeRegistry`].
82/// 3. Get a mutable reference to the component as [`&mut dyn Reflect`](Reflect).
83/// 4. Construct a replacement value based on the existing value.
84/// 5. Modify the existing value using [`PartialReflect::apply`] or its relatives.
85///
86/// If you want to access a specific field on a component, between steps 3 and 4 you need to:
87///
88/// 1. Determine the shape of the type using reflection, by converting that to a [`ReflectMut`] object.
89/// 2. Find the field(s) you want to modify by walking the tree of type metadata.
90/// 3. Downcast each field to a concrete type (e.g. with `try_downcast_ref`) to read its value.
91///
92/// [`GetPath`], and its cached variant [`ParsedPath`](bevy::reflect::ParsedPath), can be used to simplify this process.
93/// For example, calling `.path_mut::<f32>("translation.y")` on a `ReflectMut` object that stores a transform will return a
94/// `&mut f32` to the `y` field of a `Vec3` inside a `Transform`.
95fn modify_selected_component(world: &mut World) {
96 // We're using keyboard input to trigger modifications for simplicity.
97 let button_input = world.resource::<ButtonInput<KeyCode>>();
98 let direction_of_modification = if button_input.pressed(KeyCode::ArrowUp) {
99 1.0
100 } else if button_input.pressed(KeyCode::ArrowDown) {
101 -1.0
102 } else {
103 return; // No modification requested
104 };
105
106 let selected = world.resource::<SelectedComponent>().clone();
107
108 let mut sprite_query = world.query_filtered::<Entity, With<Sprite>>();
109
110 // This entity should generally be gathered via UI selection in a real application
111 let entity = sprite_query.iter(world).next().unwrap();
112
113 // We could cheat and use `TypeId::of::<T>()` to get the type ID of a known type,
114 // but real applications identify types by name (from a UI dropdown, text entry or a script).
115 let type_name = match selected {
116 // Note that Bevy's native types are registered under their full subcrate paths:
117 // `bevy_transform`, not `bevy::transform`, `bevy::prelude`, or `bevy_transform::prelude`.
118 // You can use `<T as TypePath>::type_path()` to look this up.
119 SelectedComponent::Transform => "bevy_transform::components::transform::Transform",
120 SelectedComponent::Sprite => "bevy_sprite::sprite::Sprite",
121 };
122
123 // Then, we need to use a type registry to resolve the type name to a `TypeId`.
124 // Types are (for the most part) registered automatically by Bevy,
125 // but you can also register your own types using `App::register_type`.
126 // Generic types always need to be registered manually;
127 // if a type is not showing up in your tool, check if that's the problem.
128 // You can check which types are registered by calling `TypeRegistry::iter()`,
129 // and then use the `Debug` impl for `TypeRegistration` objects to see their names and paths.
130 let app_registry = world.resource::<AppTypeRegistry>().clone();
131 let type_id = app_registry
132 .read()
133 .get_with_type_path(type_name)
134 .expect("Type was not registered, or its full path was ambiguous")
135 .type_id();
136
137 let mut reflected_component: Mut<dyn Reflect> = world.get_reflect_mut(entity, type_id).unwrap();
138
139 match selected {
140 // Downcasting is the easy path:
141 // if you happen to know the type, you can downcast and modify directly.
142 // The problem is that each of these paths would need to be hard-coded (or rely on extensive code-gen),
143 // largely defeating the purpose of using reflection in the first place.
144 SelectedComponent::Sprite => {
145 // Make sure that the type matches the component type you requested to modify.
146 // In a real project, you would want to handle this gracefully.
147 // Downcasting converts the value *directly* into a specified concrete type,
148 // allowing you to escape back into faster, strongly-typed code.
149 let downcast_sprite: &mut Sprite =
150 reflected_component.downcast_mut::<Sprite>().unwrap();
151 // Be careful not to modify a copy of the color — use `&mut`!
152 let color = &mut downcast_sprite.color;
153
154 let new_alpha = (color.alpha() + 0.01 * direction_of_modification).clamp(0.0, 1.0);
155 color.set_alpha(new_alpha);
156 }
157 // This arm demonstrates the more realistic, generic pattern:
158 // walking the reflected type info to find fields to modify.
159 // The benefit is that we can use these patterns
160 // to operate over *any* data based on our knowledge of its shape (recorded using reflection),
161 // without needing to know the concrete type at compile time.
162 SelectedComponent::Transform => {
163 let reflect_mut: ReflectMut<'_> = reflected_component.reflect_mut();
164 // In the fully generic case, we would need to match on the `ReflectMut` variants
165 // and handle each of the arms exhaustively.
166 // `struct_mut` is of type `&mut dyn Struct`, one of a number
167 // of traits that encodes the logic of Rust's type system into a runtime representation.
168 let ReflectMut::Struct(struct_mut) = reflect_mut else {
169 error!("Expected the Transform component type to be a struct");
170 return;
171 };
172
173 // Get the `translation` field as a `&mut dyn PartialReflect`,
174 // which is a type-erased representation of a value that can be modified.
175 let translation_field = struct_mut.field_mut("translation").unwrap();
176
177 // Now, we can repeat the process to get the `y` field of the `translation` Vec3
178 // In a real application, this would probably be done via a recursive function!
179 let ReflectMut::Struct(translation_struct) = translation_field.reflect_mut() else {
180 error!("Expected the translation field to be a struct");
181 return;
182 };
183
184 // We could downcast to an f32 again here, but that would be cheating!
185 // How do you generalize this sort of operation, if, for example,
186 // you wanted to build a generic inspector that could modify any numeric field of any component type?
187 // The solution lies in the way that Bevy can reflect *traits* as well as types,
188 // allowing type owners to define and register additional behavior for their types.
189 // This data is registered automatically at compile time using an inventory-like solution,
190 // and operates on a per-type x per-trait basis,
191 // just like ordinary type reflection.
192 //
193 // We want to increase or decrease the value here,
194 // so we need the `AddAssign` trait
195 // which are already implemented for f32.
196 //
197 // But `AddAssign` is not a supertrait of `PartialReflect`!
198 // We don't have access to its methods! How could that possibly work?
199 //
200 // The solution is again to register the compile time information that we want to use at runtime;
201 // storing function pointers to the trait methods in the type registry.
202 // In order to make this work, we need shadow "reflect" versions of the traits we want to use at runtime.
203 // Bevy provides a few of these out of the box, including `ReflectAddAssign` and `ReflectSubAssign`.
204 // That's what the `#[reflect(Add)]` attributes that you see scattered about in Bevy's source code are doing:
205 // generating implementations of the reflect versions of the traits, so they can later be registered and used at runtime.
206 //
207 // For more information, see the `type_data` example.
208 let y_field: &mut dyn PartialReflect = translation_struct.field_mut("y").unwrap();
209 let field_type_id = y_field
210 .get_represented_type_info()
211 .expect("Found a dynamic type unexpectedly")
212 .type_id();
213
214 let add_assign_trait_data = app_registry
215 .read()
216 .get_type_data::<ReflectAddAssign>(field_type_id)
217 .expect("f32 failed to register ReflectAddAssign")
218 .clone();
219
220 // We need to operate on the value as a concrete type,
221 // so we need to convert it into the more powerful &dyn Reflect type.
222 let y_field: &mut dyn Reflect = y_field.try_as_reflect_mut().expect(
223 "Found a dynamic type unexpectedly, but we need a concrete type to modify it",
224 );
225
226 // We're still cheating a bit here!
227 // By doing this we just *assume* that there's an f32 value when trying to determine what to add to the field.
228 // We *could* try all of the common numeric types, but that would be slow and non-extensible.
229 //
230 // In a real workflow, you would want a dedicated trait with additional methods that exposes
231 // something like dedicated `increment` and `decrement` methods, which handle the type-specific logic of how to modify the value.
232 // Remember to register that trait, and create your own analog of `ReflectAddAssign` for it!
233 //
234 // We don't do that here to avoid making this example *even more* complicated.
235 const MAGNITUDE_OF_MODIFICATION: f32 = 10.;
236 let delta = direction_of_modification * MAGNITUDE_OF_MODIFICATION;
237 let boxed_delta: Box<dyn PartialReflect> = Box::new(delta);
238 add_assign_trait_data
239 .add_assign(y_field, boxed_delta)
240 .expect("We cheated and know the types match, so this should always succeed.");
241 }
242 }
243}