//! 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>, ) { 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>, 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>, mut meshes: ResMut>, ) { 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() }, )); }