use std::f32::consts::TAU; use std::ops::RangeInclusive; use bevy::ecs::component::{ComponentHooks, StorageType}; use bevy::input::mouse::{MouseMotion, MouseWheel}; use bevy::prelude::*; use bevy::render::camera::RenderTarget; use bevy::window::{PrimaryWindow, WindowRef}; #[derive(Debug, Reflect, Clone)] pub struct DebugCamera { enabled: bool, anchor: Option, origin: Option, } impl Default for DebugCamera { fn default() -> Self { Self { enabled: true, anchor: None, origin: None, } } } impl Component for DebugCamera { const STORAGE_TYPE: StorageType = StorageType::Table; fn register_component_hooks(hooks: &mut ComponentHooks) { hooks.on_add(|mut world, entity, _| { let options = world.resource::(); if options.remember_original_transform { let transform = world .get::(entity) .cloned() .expect("added 'DebugCamera' to entity without 'Transform'"); let mut debug_camera = world.get_mut::(entity).unwrap(); let _ = debug_camera.origin.insert(transform); } }); } } #[derive(Debug, Reflect, Clone, Resource)] pub struct DebugCameraOptions { /// Whether the debug camera is enabled globally. Particular Cameras can /// still be disabled individually if this is enabled, but the opposite /// is not true. Defaults to `false`. pub enabled: bool, /// Whether `DebugCamera`s should remember their initial `Transform` when /// added or enabled. Defaults to `true`. pub remember_original_transform: bool, /// The movement speed of a camera in meters per second. /// Defaults to `2.0` m/s. pub movement_speed: f32, /// The fast movement speed a camera in meters per second. /// Defaults to `3.0` m/s. pub fast_movement_speed: f32, /// The angle to be rotated on view direction change in radians per second. /// The intensity of mouse movements is adjustable in both the horizontal /// and vertical direction through this value. Defaults to 60° in /// both directions, or `Vec2::splat(-TAU / 60.0)`. pub turning_speed: Vec2, /// Defaults to `2.0`. pub zoom_speed: f32, /// Defaults to `0.1..=100.0`. pub zoom_distance_range: RangeInclusive, pub input_options: InputOptions, /// The range of angles in radians wherein the camera is allowed freely /// rotate up and down. Rotating outside this range will clamp the looking /// direction back into it. Zero is in the direction of /// `Transform::forward`. Providing `None` disables clamping completely. /// Defaults to 45° up and down, or `Some((-TAU / 8.0)..=(TAU / 8.0))` pub vertical_fov: Option>, } impl Default for DebugCameraOptions { fn default() -> Self { Self { enabled: false, remember_original_transform: true, movement_speed: 2.0, fast_movement_speed: 3.0, turning_speed: Vec2::splat(-TAU / 60.0), zoom_speed: 2.0, zoom_distance_range: 0.1..=100.0, input_options: InputOptions::default(), vertical_fov: Some((-TAU / 8.0)..=(TAU / 8.0)), } } } impl DebugCameraOptions { pub fn default_with_keybindings() -> Self { Self { input_options: InputOptions { keybindings: KeyBindings::default(), ..Default::default() }, ..Default::default() } } } #[derive(Debug, Reflect, Clone)] #[non_exhaustive] pub struct InputOptions { pub keybindings: KeyBindings, } impl Default for InputOptions { fn default() -> Self { Self { keybindings: KeyBindings::EMPTY, } } } #[derive(Debug, Reflect, Clone)] pub struct KeyBindings { pub forward: Option, pub back: Option, pub left: Option, pub right: Option, pub up: Option, pub down: Option, pub global_up: Option, pub global_down: Option, } impl Default for KeyBindings { fn default() -> Self { Self::DEFAULT } } impl KeyBindings { pub const EMPTY: Self = Self { forward: None, back: None, left: None, right: None, up: None, down: None, global_up: None, global_down: None, }; pub const DEFAULT: Self = Self { forward: Some(KeyCode::KeyW), back: Some(KeyCode::KeyS), left: Some(KeyCode::KeyA), right: Some(KeyCode::KeyD), up: Some(KeyCode::KeyQ), down: Some(KeyCode::KeyE), global_up: Some(KeyCode::KeyR), global_down: Some(KeyCode::KeyF), }; } fn debug_camera_is_globally_enabled(o: Res) -> bool { o.enabled } #[derive(Debug, Default)] pub struct DebugCameraPlugin { debug_camera_options: DebugCameraOptions, } impl Plugin for DebugCameraPlugin { fn build(&self, app: &mut App) { app.insert_resource(self.debug_camera_options.clone()) .add_systems( Update, ( move_mouse_to_rotate, mouse_scroll_to_move_radially_or_zoom, keyboard_input_to_movements, ) .run_if(debug_camera_is_globally_enabled), ) .add_systems( PostUpdate, ( clamp_camera_rotation_vertically .run_if(|options: Res| options.vertical_fov.is_some()), force_camera_up_in_y_forward_plane, ) .run_if(debug_camera_is_globally_enabled), ); } } impl DebugCameraPlugin { pub fn new_with_keybindings() -> Self { Self { debug_camera_options: DebugCameraOptions::default_with_keybindings(), } } pub fn enable_by_default(mut self) -> Self { self.debug_camera_options.enabled = true; return self; } } fn move_mouse_to_rotate( mut mouse_motion: EventReader, debug_camera_options: Res, mut debug_cameras: Query<(&DebugCamera, &mut Transform)>, time: Res