Skip to main content

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}