bevy/examples/tools/gamepad_viewer.rs
Joona Aalto 0166db33f7
Deprecate shapes in bevy_render::mesh::shape (#11773)
# Objective

#11431 and #11688 implemented meshing support for Bevy's new geometric
primitives. The next step is to deprecate the shapes in
`bevy_render::mesh::shape` and to later remove them completely for 0.14.

## Solution

Deprecate the shapes and reduce code duplication by utilizing the
primitive meshing API for the old shapes where possible.

Note that some shapes have behavior that can't be exactly reproduced
with the new primitives yet:

- `Box` is more of an AABB with min/max extents
- `Plane` supports a subdivision count
- `Quad` has a `flipped` property

These types have not been changed to utilize the new primitives yet.

---

## Changelog

- Deprecated all shapes in `bevy_render::mesh::shape`
- Changed all examples to use new primitives for meshing

## Migration Guide

Bevy has previously used rendering-specific types like `UVSphere` and
`Quad` for primitive mesh shapes. These have now been deprecated to use
the geometric primitives newly introduced in version 0.13.

Some examples:

```rust
let before = meshes.add(shape::Box::new(5.0, 0.15, 5.0));
let after = meshes.add(Cuboid::new(5.0, 0.15, 5.0));

let before = meshes.add(shape::Quad::default());
let after = meshes.add(Rectangle::default());

let before = meshes.add(shape::Plane::from_size(5.0));
// The surface normal can now also be specified when using `new`
let after = meshes.add(Plane3d::default().mesh().size(5.0, 5.0));

let before = meshes.add(
    Mesh::try_from(shape::Icosphere {
        radius: 0.5,
        subdivisions: 5,
    })
    .unwrap(),
);
let after = meshes.add(Sphere::new(0.5).mesh().ico(5).unwrap());
```
2024-02-08 18:01:34 +00:00

528 lines
16 KiB
Rust

//! Shows a visualization of gamepad buttons, sticks, and triggers
use std::f32::consts::PI;
use bevy::{
input::gamepad::{
GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent, GamepadSettings,
},
prelude::*,
sprite::{Anchor, MaterialMesh2dBundle, Mesh2dHandle},
};
const BUTTON_RADIUS: f32 = 25.;
const BUTTON_CLUSTER_RADIUS: f32 = 50.;
const START_SIZE: Vec2 = Vec2::new(30., 15.);
const TRIGGER_SIZE: Vec2 = Vec2::new(70., 20.);
const STICK_BOUNDS_SIZE: f32 = 100.;
const BUTTONS_X: f32 = 150.;
const BUTTONS_Y: f32 = 80.;
const STICKS_X: f32 = 150.;
const STICKS_Y: f32 = -135.;
const NORMAL_BUTTON_COLOR: Color = Color::rgb(0.3, 0.3, 0.3);
const ACTIVE_BUTTON_COLOR: Color = Color::PURPLE;
const LIVE_COLOR: Color = Color::rgb(0.4, 0.4, 0.4);
const DEAD_COLOR: Color = Color::rgb(0.13, 0.13, 0.13);
#[derive(Component, Deref)]
struct ReactTo(GamepadButtonType);
#[derive(Component)]
struct MoveWithAxes {
x_axis: GamepadAxisType,
y_axis: GamepadAxisType,
scale: f32,
}
#[derive(Component)]
struct TextWithAxes {
x_axis: GamepadAxisType,
y_axis: GamepadAxisType,
}
#[derive(Component, Deref)]
struct TextWithButtonValue(GamepadButtonType);
#[derive(Component)]
struct ConnectedGamepadsText;
#[derive(Resource)]
struct ButtonMaterials {
normal: Handle<ColorMaterial>,
active: Handle<ColorMaterial>,
}
impl FromWorld for ButtonMaterials {
fn from_world(world: &mut World) -> Self {
let mut materials = world.resource_mut::<Assets<ColorMaterial>>();
Self {
normal: materials.add(NORMAL_BUTTON_COLOR),
active: materials.add(ACTIVE_BUTTON_COLOR),
}
}
}
#[derive(Resource)]
struct ButtonMeshes {
circle: Mesh2dHandle,
triangle: Mesh2dHandle,
start_pause: Mesh2dHandle,
trigger: Mesh2dHandle,
}
impl FromWorld for ButtonMeshes {
fn from_world(world: &mut World) -> Self {
let mut meshes = world.resource_mut::<Assets<Mesh>>();
Self {
circle: meshes.add(Circle::new(BUTTON_RADIUS)).into(),
triangle: meshes.add(RegularPolygon::new(BUTTON_RADIUS, 3)).into(),
start_pause: meshes.add(Rectangle::from_size(START_SIZE)).into(),
trigger: meshes.add(Rectangle::from_size(TRIGGER_SIZE)).into(),
}
}
}
#[derive(Bundle)]
struct GamepadButtonBundle {
mesh_bundle: MaterialMesh2dBundle<ColorMaterial>,
react_to: ReactTo,
}
impl GamepadButtonBundle {
pub fn new(
button_type: GamepadButtonType,
mesh: Mesh2dHandle,
material: Handle<ColorMaterial>,
x: f32,
y: f32,
) -> Self {
Self {
mesh_bundle: MaterialMesh2dBundle {
mesh,
material,
transform: Transform::from_xyz(x, y, 0.),
..default()
},
react_to: ReactTo(button_type),
}
}
pub fn with_rotation(mut self, angle: f32) -> Self {
self.mesh_bundle.transform.rotation = Quat::from_rotation_z(angle);
self
}
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_resource::<ButtonMaterials>()
.init_resource::<ButtonMeshes>()
.add_systems(
Startup,
(setup, setup_sticks, setup_triggers, setup_connected),
)
.add_systems(
Update,
(
update_buttons,
update_button_values,
update_axes,
update_connected,
),
)
.run();
}
fn setup(mut commands: Commands, meshes: Res<ButtonMeshes>, materials: Res<ButtonMaterials>) {
commands.spawn(Camera2dBundle::default());
// Buttons
commands
.spawn(SpatialBundle {
transform: Transform::from_xyz(BUTTONS_X, BUTTONS_Y, 0.),
..default()
})
.with_children(|parent| {
parent.spawn(GamepadButtonBundle::new(
GamepadButtonType::North,
meshes.circle.clone(),
materials.normal.clone(),
0.,
BUTTON_CLUSTER_RADIUS,
));
parent.spawn(GamepadButtonBundle::new(
GamepadButtonType::South,
meshes.circle.clone(),
materials.normal.clone(),
0.,
-BUTTON_CLUSTER_RADIUS,
));
parent.spawn(GamepadButtonBundle::new(
GamepadButtonType::West,
meshes.circle.clone(),
materials.normal.clone(),
-BUTTON_CLUSTER_RADIUS,
0.,
));
parent.spawn(GamepadButtonBundle::new(
GamepadButtonType::East,
meshes.circle.clone(),
materials.normal.clone(),
BUTTON_CLUSTER_RADIUS,
0.,
));
});
// Start and Pause
commands.spawn(GamepadButtonBundle::new(
GamepadButtonType::Select,
meshes.start_pause.clone(),
materials.normal.clone(),
-30.,
BUTTONS_Y,
));
commands.spawn(GamepadButtonBundle::new(
GamepadButtonType::Start,
meshes.start_pause.clone(),
materials.normal.clone(),
30.,
BUTTONS_Y,
));
// D-Pad
commands
.spawn(SpatialBundle {
transform: Transform::from_xyz(-BUTTONS_X, BUTTONS_Y, 0.),
..default()
})
.with_children(|parent| {
parent.spawn(GamepadButtonBundle::new(
GamepadButtonType::DPadUp,
meshes.triangle.clone(),
materials.normal.clone(),
0.,
BUTTON_CLUSTER_RADIUS,
));
parent.spawn(
GamepadButtonBundle::new(
GamepadButtonType::DPadDown,
meshes.triangle.clone(),
materials.normal.clone(),
0.,
-BUTTON_CLUSTER_RADIUS,
)
.with_rotation(PI),
);
parent.spawn(
GamepadButtonBundle::new(
GamepadButtonType::DPadLeft,
meshes.triangle.clone(),
materials.normal.clone(),
-BUTTON_CLUSTER_RADIUS,
0.,
)
.with_rotation(PI / 2.),
);
parent.spawn(
GamepadButtonBundle::new(
GamepadButtonType::DPadRight,
meshes.triangle.clone(),
materials.normal.clone(),
BUTTON_CLUSTER_RADIUS,
0.,
)
.with_rotation(-PI / 2.),
);
});
// Triggers
commands.spawn(GamepadButtonBundle::new(
GamepadButtonType::LeftTrigger,
meshes.trigger.clone(),
materials.normal.clone(),
-BUTTONS_X,
BUTTONS_Y + 115.,
));
commands.spawn(GamepadButtonBundle::new(
GamepadButtonType::RightTrigger,
meshes.trigger.clone(),
materials.normal.clone(),
BUTTONS_X,
BUTTONS_Y + 115.,
));
}
fn setup_sticks(
mut commands: Commands,
meshes: Res<ButtonMeshes>,
materials: Res<ButtonMaterials>,
gamepad_settings: Res<GamepadSettings>,
) {
let dead_upper =
STICK_BOUNDS_SIZE * gamepad_settings.default_axis_settings.deadzone_upperbound();
let dead_lower =
STICK_BOUNDS_SIZE * gamepad_settings.default_axis_settings.deadzone_lowerbound();
let dead_size = dead_lower.abs() + dead_upper.abs();
let dead_mid = (dead_lower + dead_upper) / 2.0;
let live_upper =
STICK_BOUNDS_SIZE * gamepad_settings.default_axis_settings.livezone_upperbound();
let live_lower =
STICK_BOUNDS_SIZE * gamepad_settings.default_axis_settings.livezone_lowerbound();
let live_size = live_lower.abs() + live_upper.abs();
let live_mid = (live_lower + live_upper) / 2.0;
let mut spawn_stick = |x_pos, y_pos, x_axis, y_axis, button| {
commands
.spawn(SpatialBundle {
transform: Transform::from_xyz(x_pos, y_pos, 0.),
..default()
})
.with_children(|parent| {
// full extent
parent.spawn(SpriteBundle {
sprite: Sprite {
custom_size: Some(Vec2::splat(STICK_BOUNDS_SIZE * 2.)),
color: DEAD_COLOR,
..default()
},
..default()
});
// live zone
parent.spawn(SpriteBundle {
transform: Transform::from_xyz(live_mid, live_mid, 2.),
sprite: Sprite {
custom_size: Some(Vec2::new(live_size, live_size)),
color: LIVE_COLOR,
..default()
},
..default()
});
// dead zone
parent.spawn(SpriteBundle {
transform: Transform::from_xyz(dead_mid, dead_mid, 3.),
sprite: Sprite {
custom_size: Some(Vec2::new(dead_size, dead_size)),
color: DEAD_COLOR,
..default()
},
..default()
});
// text
let style = TextStyle {
font_size: 16.,
..default()
};
parent.spawn((
Text2dBundle {
transform: Transform::from_xyz(0., STICK_BOUNDS_SIZE + 2., 4.),
text: Text::from_sections([
TextSection {
value: format!("{:.3}", 0.),
style: style.clone(),
},
TextSection {
value: ", ".to_string(),
style: style.clone(),
},
TextSection {
value: format!("{:.3}", 0.),
style,
},
]),
text_anchor: Anchor::BottomCenter,
..default()
},
TextWithAxes { x_axis, y_axis },
));
// cursor
parent.spawn((
MaterialMesh2dBundle {
mesh: meshes.circle.clone(),
material: materials.normal.clone(),
transform: Transform::from_xyz(0., 0., 5.)
.with_scale(Vec2::splat(0.15).extend(1.)),
..default()
},
MoveWithAxes {
x_axis,
y_axis,
scale: STICK_BOUNDS_SIZE,
},
ReactTo(button),
));
});
};
spawn_stick(
-STICKS_X,
STICKS_Y,
GamepadAxisType::LeftStickX,
GamepadAxisType::LeftStickY,
GamepadButtonType::LeftThumb,
);
spawn_stick(
STICKS_X,
STICKS_Y,
GamepadAxisType::RightStickX,
GamepadAxisType::RightStickY,
GamepadButtonType::RightThumb,
);
}
fn setup_triggers(
mut commands: Commands,
meshes: Res<ButtonMeshes>,
materials: Res<ButtonMaterials>,
) {
let mut spawn_trigger = |x, y, button_type| {
commands
.spawn(GamepadButtonBundle::new(
button_type,
meshes.trigger.clone(),
materials.normal.clone(),
x,
y,
))
.with_children(|parent| {
parent.spawn((
Text2dBundle {
transform: Transform::from_xyz(0., 0., 1.),
text: Text::from_section(
format!("{:.3}", 0.),
TextStyle {
font_size: 16.,
..default()
},
),
..default()
},
TextWithButtonValue(button_type),
));
});
};
spawn_trigger(
-BUTTONS_X,
BUTTONS_Y + 145.,
GamepadButtonType::LeftTrigger2,
);
spawn_trigger(
BUTTONS_X,
BUTTONS_Y + 145.,
GamepadButtonType::RightTrigger2,
);
}
fn setup_connected(mut commands: Commands) {
let text_style = TextStyle {
font_size: 20.,
..default()
};
commands.spawn((
TextBundle {
text: Text::from_sections([
TextSection {
value: "Connected Gamepads:\n".to_string(),
style: text_style.clone(),
},
TextSection {
value: "None".to_string(),
style: text_style,
},
]),
style: Style {
position_type: PositionType::Absolute,
top: Val::Px(12.),
left: Val::Px(12.),
..default()
},
..default()
},
ConnectedGamepadsText,
));
}
fn update_buttons(
gamepads: Res<Gamepads>,
button_inputs: Res<ButtonInput<GamepadButton>>,
materials: Res<ButtonMaterials>,
mut query: Query<(&mut Handle<ColorMaterial>, &ReactTo)>,
) {
for gamepad in gamepads.iter() {
for (mut handle, react_to) in query.iter_mut() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, **react_to)) {
*handle = materials.active.clone();
}
if button_inputs.just_released(GamepadButton::new(gamepad, **react_to)) {
*handle = materials.normal.clone();
}
}
}
}
fn update_button_values(
mut events: EventReader<GamepadButtonChangedEvent>,
mut query: Query<(&mut Text, &TextWithButtonValue)>,
) {
for button_event in events.read() {
for (mut text, text_with_button_value) in query.iter_mut() {
if button_event.button_type == **text_with_button_value {
text.sections[0].value = format!("{:.3}", button_event.value);
}
}
}
}
fn update_axes(
mut axis_events: EventReader<GamepadAxisChangedEvent>,
mut query: Query<(&mut Transform, &MoveWithAxes)>,
mut text_query: Query<(&mut Text, &TextWithAxes)>,
) {
for axis_event in axis_events.read() {
let axis_type = axis_event.axis_type;
let value = axis_event.value;
for (mut transform, move_with) in query.iter_mut() {
if axis_type == move_with.x_axis {
transform.translation.x = value * move_with.scale;
}
if axis_type == move_with.y_axis {
transform.translation.y = value * move_with.scale;
}
}
for (mut text, text_with_axes) in text_query.iter_mut() {
if axis_type == text_with_axes.x_axis {
text.sections[0].value = format!("{value:.3}");
}
if axis_type == text_with_axes.y_axis {
text.sections[2].value = format!("{value:.3}");
}
}
}
}
fn update_connected(
gamepads: Res<Gamepads>,
mut query: Query<&mut Text, With<ConnectedGamepadsText>>,
) {
if !gamepads.is_changed() {
return;
}
let mut text = query.single_mut();
let formatted = gamepads
.iter()
.map(|g| format!("- {}", gamepads.name(g).unwrap()))
.collect::<Vec<_>>()
.join("\n");
text.sections[1].value = if !formatted.is_empty() {
formatted
} else {
"None".to_string()
}
}