mirror of
https://github.com/bevyengine/bevy.git
synced 2026-06-30 07:45:50 -04:00
51a80d0a14
# Objective - Fixes https://github.com/bevyengine/bevy/issues/24141 ## Solution - Added Two additional keys (R & F) for local space Y velocity. - following the [Blender Walk/Fly Mode](https://docs.blender.org/manual/en/latest/editors/3dview/navigate/walk_fly.html) controls. - Added `FreeCamera::invert_world_local_control()` for alternative keybinds where Q/E is local Space instead, and R/F is world Space. - Some user probably more familiar with Q/E as local space up. - Updated Examples to resolve key conflict with scroll factor controls - from F/G to G/H Note: This is my first contribution. If there's anything I am missing please point it out :) ## Testing - Did you test these changes? If so, how? - I tested with `free_camera_controller` example - Are there any parts that need more testing? - Don't think so. - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Don't think so. Straightforward change. - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? - tested on Windows 11. It's not platform specific so should be ok. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
293 lines
9.7 KiB
Rust
293 lines
9.7 KiB
Rust
//! This example showcases the default `FreeCamera` camera controller.
|
|
//!
|
|
//! The default `FreeCamera` controller is useful for exploring large scenes, debugging and editing purposes. To use it,
|
|
//! simply add the [`FreeCameraPlugin`] to your [`App`] and attach the [`FreeCamera`] component to the camera entity you
|
|
//! wish to control.
|
|
//!
|
|
//! ## Default Controls
|
|
//!
|
|
//! This controller has a simple 6-axis control scheme, and mouse controls for camera orientation. There are also
|
|
//! bindings for capturing the mouse, both while holding the button and toggle, a run feature that increases the
|
|
//! max speed, and scrolling changes the movement speed. All keybinds can be changed by editing the [`FreeCamera`]
|
|
//! component.
|
|
//!
|
|
//! | Default Key Binding | Action |
|
|
//! |:--------------------|:-----------------------|
|
|
//! | Mouse | Look around |
|
|
//! | Right click | Capture mouse (hold) |
|
|
//! | M | Capture mouse (toggle) |
|
|
//! | WASD | Horizontal movement |
|
|
//! | QE | Vertical movement |
|
|
//! | Left shift | Run |
|
|
//! | Scroll wheel | Change movement speed |
|
|
//! | Numpad1 | Snap to front |
|
|
//! | `LCtrl` + Numpad1 | Snap to back |
|
|
//! | Numpad3 | Snap to right |
|
|
//! | `LCtrl` + Numpad3 | Snap to left |
|
|
//! | Numpad7 | Snap to top |
|
|
//! | `LCtrl` + Numpad7 | Snap to bottom |
|
|
//!
|
|
//! The movement speed, sensitivity and friction can also be changed by the [`FreeCamera`] component.
|
|
//!
|
|
//! ## Example controls
|
|
//!
|
|
//! This example also provides a few extra keybinds to change the camera sensitivity, friction (how fast the camera
|
|
//! stops), scroll factor (how much scrolling changes speed) and enabling/disabling the controller.
|
|
//!
|
|
//! | Key Binding | Action |
|
|
//! |:------------|:------------------------------|
|
|
//! | Z | Decrease sensitivity |
|
|
//! | X | Increase sensitivity |
|
|
//! | C | Decrease friction |
|
|
//! | V | Increase friction |
|
|
//! | F | Decrease scroll factor |
|
|
//! | G | Increase scroll factor |
|
|
//! | B | Enable/Disable |
|
|
//! | T | World/Local vertical movement |
|
|
|
|
use std::f32::consts::{FRAC_PI_4, PI};
|
|
|
|
use bevy::{
|
|
camera_controller::free_camera::{
|
|
FreeCamera, FreeCameraPlugin, FreeCameraState, VerticalMovementAxis,
|
|
},
|
|
color::palettes::tailwind,
|
|
prelude::*,
|
|
};
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(DefaultPlugins)
|
|
// Plugin that enables FreeCamera functionality
|
|
.add_plugins(FreeCameraPlugin)
|
|
// Example code plugins
|
|
.add_plugins((CameraPlugin, CameraSettingsPlugin, ScenePlugin))
|
|
.run();
|
|
}
|
|
|
|
// Plugin that spawns the camera.
|
|
struct CameraPlugin;
|
|
impl Plugin for CameraPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.add_systems(Startup, spawn_camera);
|
|
}
|
|
}
|
|
|
|
fn spawn_camera(mut commands: Commands) {
|
|
commands.spawn((
|
|
Camera3d::default(),
|
|
Transform::from_xyz(0.0, 1.0, 0.0).looking_to(Vec3::X, Vec3::Y),
|
|
// This component stores all camera settings and state, which is used by the FreeCameraPlugin to
|
|
// control it. These properties can be changed at runtime, but beware the controller system is
|
|
// constantly using and modifying those values unless the enabled field is false.
|
|
FreeCamera {
|
|
sensitivity: 0.2,
|
|
friction: 25.0,
|
|
walk_speed: 3.0,
|
|
run_speed: 9.0,
|
|
..default()
|
|
},
|
|
));
|
|
}
|
|
|
|
// Plugin that handles camera settings controls and information text
|
|
struct CameraSettingsPlugin;
|
|
impl Plugin for CameraSettingsPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.add_systems(PostStartup, spawn_text)
|
|
.add_systems(Update, (update_camera_settings, update_text));
|
|
}
|
|
}
|
|
|
|
#[derive(Component)]
|
|
struct InfoText;
|
|
|
|
fn spawn_text(mut commands: Commands, free_camera_query: Query<&FreeCamera>) {
|
|
commands.spawn((
|
|
Node {
|
|
position_type: PositionType::Absolute,
|
|
top: px(-16),
|
|
left: px(12),
|
|
..default()
|
|
},
|
|
children![Text::new(format!(
|
|
"{}",
|
|
free_camera_query.single().unwrap(),
|
|
))],
|
|
));
|
|
commands.spawn((
|
|
Node {
|
|
position_type: PositionType::Absolute,
|
|
bottom: px(12),
|
|
left: px(12),
|
|
..default()
|
|
},
|
|
children![Text::new(concat![
|
|
"Z/X: decrease/increase sensitivity\n",
|
|
"C/V: decrease/increase friction\n",
|
|
"F/G: decrease/increase scroll factor\n",
|
|
"B: enable/disable controller\n",
|
|
"T: world/local vertical movement"
|
|
]),],
|
|
));
|
|
|
|
// Mutable text marked with component
|
|
commands.spawn((
|
|
Node {
|
|
position_type: PositionType::Absolute,
|
|
top: px(12),
|
|
right: px(12),
|
|
..default()
|
|
},
|
|
children![(InfoText, Text::new(""))],
|
|
));
|
|
}
|
|
|
|
fn update_camera_settings(
|
|
mut camera_query: Query<(&mut FreeCamera, &mut FreeCameraState)>,
|
|
input: Res<ButtonInput<KeyCode>>,
|
|
) {
|
|
let (mut free_camera, mut free_camera_state) = camera_query.single_mut().unwrap();
|
|
|
|
if input.pressed(KeyCode::KeyZ) {
|
|
free_camera.sensitivity = (free_camera.sensitivity - 0.005).max(0.005);
|
|
}
|
|
if input.pressed(KeyCode::KeyX) {
|
|
free_camera.sensitivity += 0.005;
|
|
}
|
|
if input.pressed(KeyCode::KeyC) {
|
|
free_camera.friction = (free_camera.friction - 0.2).max(0.0);
|
|
}
|
|
if input.pressed(KeyCode::KeyV) {
|
|
free_camera.friction += 0.2;
|
|
}
|
|
if input.pressed(KeyCode::KeyF) {
|
|
free_camera.scroll_factor = (free_camera.scroll_factor - 0.02).max(0.02);
|
|
}
|
|
if input.pressed(KeyCode::KeyG) {
|
|
free_camera.scroll_factor += 0.02;
|
|
}
|
|
if input.just_pressed(KeyCode::KeyB) {
|
|
free_camera_state.enabled = !free_camera_state.enabled;
|
|
}
|
|
if input.just_pressed(KeyCode::KeyT) {
|
|
free_camera.vertical_movement_axis = match free_camera.vertical_movement_axis {
|
|
VerticalMovementAxis::World => VerticalMovementAxis::Local,
|
|
VerticalMovementAxis::Local => VerticalMovementAxis::World,
|
|
};
|
|
}
|
|
}
|
|
|
|
fn update_text(
|
|
mut text_query: Query<&mut Text, With<InfoText>>,
|
|
camera_query: Query<(&FreeCamera, &FreeCameraState)>,
|
|
) {
|
|
let mut text = text_query.single_mut().unwrap();
|
|
|
|
let (free_camera, free_camera_state) = camera_query.single().unwrap();
|
|
|
|
text.0 = format!(
|
|
"Enabled: {},\nSensitivity: {:.03}\nFriction: {:.01}\nScroll factor: {:.02}\nWalk Speed: {:.02}\nRun Speed: {:.02}\nSpeed: {:.02}",
|
|
free_camera_state.enabled,
|
|
free_camera.sensitivity,
|
|
free_camera.friction,
|
|
free_camera.scroll_factor,
|
|
free_camera.walk_speed * free_camera_state.speed_multiplier,
|
|
free_camera.run_speed * free_camera_state.speed_multiplier,
|
|
free_camera_state.velocity.length(),
|
|
);
|
|
}
|
|
|
|
// Plugin that spawns the scene and lighting.
|
|
struct ScenePlugin;
|
|
impl Plugin for ScenePlugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.add_systems(Startup, (spawn_lights, spawn_world));
|
|
}
|
|
}
|
|
|
|
fn spawn_lights(mut commands: Commands) {
|
|
// Main light
|
|
commands.spawn((
|
|
PointLight {
|
|
color: Color::from(tailwind::ORANGE_300),
|
|
shadow_maps_enabled: true,
|
|
..default()
|
|
},
|
|
Transform::from_xyz(0.0, 3.0, 0.0),
|
|
));
|
|
// Light behind wall
|
|
commands.spawn((
|
|
PointLight {
|
|
color: Color::WHITE,
|
|
shadow_maps_enabled: true,
|
|
..default()
|
|
},
|
|
Transform::from_xyz(-3.5, 3.0, 0.0),
|
|
));
|
|
// Light under floor
|
|
commands.spawn((
|
|
PointLight {
|
|
color: Color::from(tailwind::RED_300),
|
|
shadow_maps_enabled: true,
|
|
..default()
|
|
},
|
|
Transform::from_xyz(0.0, -0.5, 0.0),
|
|
));
|
|
}
|
|
|
|
fn spawn_world(
|
|
mut commands: Commands,
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
) {
|
|
let cube = meshes.add(Cuboid::new(1.0, 1.0, 1.0));
|
|
let floor = meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(10.0)));
|
|
let sphere = meshes.add(Sphere::new(0.5));
|
|
let wall = meshes.add(Cuboid::new(0.2, 4.0, 3.0));
|
|
|
|
let blue_material = materials.add(Color::from(tailwind::BLUE_700));
|
|
let red_material = materials.add(Color::from(tailwind::RED_950));
|
|
let white_material = materials.add(Color::WHITE);
|
|
|
|
// Top side of floor
|
|
commands.spawn((
|
|
Mesh3d(floor.clone()),
|
|
MeshMaterial3d(white_material.clone()),
|
|
));
|
|
// Under side of floor
|
|
commands.spawn((
|
|
Mesh3d(floor.clone()),
|
|
MeshMaterial3d(white_material.clone()),
|
|
Transform::from_xyz(0.0, -0.01, 0.0).with_rotation(Quat::from_rotation_x(PI)),
|
|
));
|
|
// Blue sphere
|
|
commands.spawn((
|
|
Mesh3d(sphere.clone()),
|
|
MeshMaterial3d(blue_material.clone()),
|
|
Transform::from_xyz(3.0, 1.5, 0.0),
|
|
));
|
|
// Tall wall
|
|
commands.spawn((
|
|
Mesh3d(wall.clone()),
|
|
MeshMaterial3d(white_material.clone()),
|
|
Transform::from_xyz(-3.0, 2.0, 0.0),
|
|
));
|
|
// Cube behind wall
|
|
commands.spawn((
|
|
Mesh3d(cube.clone()),
|
|
MeshMaterial3d(blue_material.clone()),
|
|
Transform::from_xyz(-4.2, 0.5, 0.0),
|
|
));
|
|
// Hidden cube under floor
|
|
commands.spawn((
|
|
Mesh3d(cube.clone()),
|
|
MeshMaterial3d(red_material.clone()),
|
|
Transform {
|
|
translation: Vec3::new(3.0, -2.0, 0.0),
|
|
rotation: Quat::from_euler(EulerRot::YXZEx, FRAC_PI_4, FRAC_PI_4, 0.0),
|
|
..default()
|
|
},
|
|
));
|
|
}
|