docs/bevy/examples/reflection/mutation_by_reflection.rs 12.4 K · 243 lines · raw · history
| 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 | |
| 11 | use bevy::{prelude::*, reflect::ReflectMut}; |
| 12 | |
| 13 | fn 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 | |
| 25 | fn 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 = "\ |
| 33 | Press 'T' to select the Transform component's y-translation for modification |
| 34 | Press 'S' to select the Sprite component's alpha value for modification |
| 35 | Press 'Up Arrow' to increase the selected component's value |
| 36 | Press '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)] |
| 52 | enum 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. |
| 60 | fn 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`. |
| 95 | fn 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 | } |