
*This PR description is an edited copy of #5007, written by @alice-i-cecile.* # Objective Follow-up to https://github.com/bevyengine/bevy/pull/2254. The `Resource` trait currently has a blanket implementation for all types that meet its bounds. While ergonomic, this results in several drawbacks: * it is possible to make confusing, silent mistakes such as inserting a function pointer (Foo) rather than a value (Foo::Bar) as a resource * it is challenging to discover if a type is intended to be used as a resource * we cannot later add customization options (see the [RFC](https://github.com/bevyengine/rfcs/blob/main/rfcs/27-derive-component.md) for the equivalent choice for Component). * dependencies can use the same Rust type as a resource in invisibly conflicting ways * raw Rust types used as resources cannot preserve privacy appropriately, as anyone able to access that type can read and write to internal values * we cannot capture a definitive list of possible resources to display to users in an editor ## Notes to reviewers * Review this commit-by-commit; there's effectively no back-tracking and there's a lot of churn in some of these commits. *ira: My commits are not as well organized :')* * I've relaxed the bound on Local to Send + Sync + 'static: I don't think these concerns apply there, so this can keep things simple. Storing e.g. a u32 in a Local is fine, because there's a variable name attached explaining what it does. * I think this is a bad place for the Resource trait to live, but I've left it in place to make reviewing easier. IMO that's best tackled with https://github.com/bevyengine/bevy/issues/4981. ## Changelog `Resource` is no longer automatically implemented for all matching types. Instead, use the new `#[derive(Resource)]` macro. ## Migration Guide Add `#[derive(Resource)]` to all types you are using as a resource. If you are using a third party type as a resource, wrap it in a tuple struct to bypass orphan rules. Consider deriving `Deref` and `DerefMut` to improve ergonomics. `ClearColor` no longer implements `Component`. Using `ClearColor` as a component in 0.8 did nothing. Use the `ClearColorConfig` in the `Camera3d` and `Camera2d` components instead. Co-authored-by: Alice <alice.i.cecile@gmail.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: devil-ira <justthecooldude@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
185 lines
7.5 KiB
Rust
185 lines
7.5 KiB
Rust
//! Illustrates the difference between direction of a translation in respect to local object or
|
|
//! global object Transform.
|
|
|
|
use bevy::{math::Vec3A, prelude::*};
|
|
|
|
// Define a marker for entities that should be changed via their global transform.
|
|
#[derive(Component)]
|
|
struct ChangeGlobal;
|
|
|
|
// Define a marker for entities that should be changed via their local transform.
|
|
#[derive(Component)]
|
|
struct ChangeLocal;
|
|
|
|
// Define a marker for entities that should move.
|
|
#[derive(Component)]
|
|
struct Move;
|
|
|
|
// Define a resource for the current movement direction;
|
|
#[derive(Resource, Default)]
|
|
struct Direction(Vec3);
|
|
|
|
// Define component to decide when an entity should be ignored by the movement systems.
|
|
#[derive(Component)]
|
|
struct ToggledBy(KeyCode);
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(DefaultPlugins)
|
|
.add_startup_system(setup)
|
|
.init_resource::<Direction>()
|
|
.add_system(move_cubes_according_to_global_transform)
|
|
.add_system(move_cubes_according_to_local_transform)
|
|
.add_system(update_directional_input)
|
|
.add_system(toggle_movement)
|
|
.run();
|
|
}
|
|
|
|
// Startup system to setup the scene and spawn all relevant entities.
|
|
fn setup(
|
|
mut commands: Commands,
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
asset_server: Res<AssetServer>,
|
|
) {
|
|
// To show the difference between a local transform (rotation, scale and position in respect to a given entity)
|
|
// and global transform (rotation, scale and position in respect to the base coordinate system of the visible scene)
|
|
// it's helpful to add multiple entities that are attached to each other.
|
|
// This way we'll see that the transform in respect to an entity's parent is different to the
|
|
// global transform within the visible scene.
|
|
// This example focuses on translation only to clearly demonstrate the differences.
|
|
|
|
// Spawn a basic cube to have an entity as reference.
|
|
let mut main_entity = commands.spawn();
|
|
main_entity
|
|
.insert_bundle(PbrBundle {
|
|
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
|
material: materials.add(Color::YELLOW.into()),
|
|
..default()
|
|
})
|
|
.insert(ChangeGlobal)
|
|
.insert(Move)
|
|
.insert(ToggledBy(KeyCode::Key1));
|
|
|
|
// Spawn two entities as children above the original main entity.
|
|
// The red entity spawned here will be changed via its global transform
|
|
// where the green one will be changed via its local transform.
|
|
main_entity.with_children(|child_builder| {
|
|
// also see parenting example
|
|
child_builder
|
|
.spawn_bundle(PbrBundle {
|
|
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.5 })),
|
|
material: materials.add(Color::RED.into()),
|
|
transform: Transform::from_translation(Vec3::Y - Vec3::Z),
|
|
..default()
|
|
})
|
|
.insert(ChangeGlobal)
|
|
.insert(Move)
|
|
.insert(ToggledBy(KeyCode::Key2));
|
|
child_builder
|
|
.spawn_bundle(PbrBundle {
|
|
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.5 })),
|
|
material: materials.add(Color::GREEN.into()),
|
|
transform: Transform::from_translation(Vec3::Y + Vec3::Z),
|
|
..default()
|
|
})
|
|
.insert(ChangeLocal)
|
|
.insert(Move)
|
|
.insert(ToggledBy(KeyCode::Key3));
|
|
});
|
|
|
|
// Spawn a camera looking at the entities to show what's happening in this example.
|
|
commands.spawn_bundle(Camera3dBundle {
|
|
transform: Transform::from_xyz(0.0, 10.0, 20.0).looking_at(Vec3::ZERO, Vec3::Y),
|
|
..default()
|
|
});
|
|
|
|
// Add a light source for better 3d visibility.
|
|
commands.spawn_bundle(PointLightBundle {
|
|
transform: Transform::from_translation(Vec3::splat(3.0)),
|
|
..default()
|
|
});
|
|
|
|
// Add text to explain inputs and what is happening.
|
|
commands.spawn_bundle(TextBundle::from_section(
|
|
"Press the arrow keys to move the cubes. Toggle movement for yellow (1), red (2) and green (3) cubes via number keys.
|
|
|
|
Notice how the green cube will translate further in respect to the yellow in contrast to the red cube.
|
|
This is due to the use of its LocalTransform that is relative to the yellow cubes transform instead of the GlobalTransform as in the case of the red cube.
|
|
The red cube is moved through its GlobalTransform and thus is unaffected by the yellows transform.",
|
|
TextStyle {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
|
font_size: 22.0,
|
|
color: Color::WHITE,
|
|
},
|
|
));
|
|
}
|
|
|
|
// This system will move all cubes that are marked as ChangeGlobal according to their global transform.
|
|
fn move_cubes_according_to_global_transform(
|
|
mut cubes: Query<&mut GlobalTransform, (With<ChangeGlobal>, With<Move>)>,
|
|
direction: Res<Direction>,
|
|
timer: Res<Time>,
|
|
) {
|
|
for mut global_transform in &mut cubes {
|
|
*global_transform.translation_mut() += Vec3A::from(direction.0) * timer.delta_seconds();
|
|
}
|
|
}
|
|
|
|
// This system will move all cubes that are marked as ChangeLocal according to their local transform.
|
|
fn move_cubes_according_to_local_transform(
|
|
mut cubes: Query<&mut Transform, (With<ChangeLocal>, With<Move>)>,
|
|
direction: Res<Direction>,
|
|
timer: Res<Time>,
|
|
) {
|
|
for mut transform in &mut cubes {
|
|
transform.translation += direction.0 * timer.delta_seconds();
|
|
}
|
|
}
|
|
|
|
// This system updates a resource that defines in which direction the cubes should move.
|
|
// The direction is defined by the input of arrow keys and is only in left/right and up/down direction.
|
|
fn update_directional_input(mut direction: ResMut<Direction>, keyboard_input: Res<Input<KeyCode>>) {
|
|
let horizontal_movement = Vec3::X
|
|
* (keyboard_input.pressed(KeyCode::Right) as i32
|
|
- keyboard_input.pressed(KeyCode::Left) as i32) as f32;
|
|
let vertical_movement = Vec3::Y
|
|
* (keyboard_input.pressed(KeyCode::Up) as i32
|
|
- keyboard_input.pressed(KeyCode::Down) as i32) as f32;
|
|
direction.0 = horizontal_movement + vertical_movement;
|
|
}
|
|
|
|
// This system enables and disables the movement for each entity if their assigned key is pressed.
|
|
fn toggle_movement(
|
|
mut commands: Commands,
|
|
movable_entities: Query<(Entity, &Handle<StandardMaterial>, &ToggledBy), With<Move>>,
|
|
static_entities: Query<(Entity, &Handle<StandardMaterial>, &ToggledBy), Without<Move>>,
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
keyboard_input: Res<Input<KeyCode>>,
|
|
) {
|
|
// Update the currently movable entities and remove their Move component if the assigned key was pressed to disable their movement.
|
|
// This will also make them transparent so they can be identified as 'disabled' in the scene.
|
|
for (entity, material_handle, toggled_by) in &movable_entities {
|
|
if keyboard_input.just_pressed(toggled_by.0) {
|
|
materials
|
|
.get_mut(material_handle)
|
|
.unwrap()
|
|
.base_color
|
|
.set_a(0.5);
|
|
commands.entity(entity).remove::<Move>();
|
|
}
|
|
}
|
|
// Update the currently non-movable entities and add a Move component if the assigned key was pressed to enable their movement.
|
|
// This will also make them opaque so they can be identified as 'enabled' in the scene.
|
|
for (entity, material_handle, toggled_by) in &static_entities {
|
|
if keyboard_input.just_pressed(toggled_by.0) {
|
|
materials
|
|
.get_mut(material_handle)
|
|
.unwrap()
|
|
.base_color
|
|
.set_a(1.0);
|
|
commands.entity(entity).insert(Move);
|
|
}
|
|
}
|
|
}
|