diff --git a/src/gamepad.rs b/src/gamepad.rs index b2eb2a1..b3f9030 100644 --- a/src/gamepad.rs +++ b/src/gamepad.rs @@ -1,7 +1,6 @@ -use std::f32::consts::PI; - use crate::{zoom_condition, ThirdPersonCamera}; use bevy::{prelude::*, window::PrimaryWindow}; +use std::f32::consts::PI; pub struct GamePadPlugin; @@ -73,29 +72,22 @@ pub fn orbit_gamepad( if cam.mouse_orbit_button_enabled && !gamepad.pressed(cam.gamepad_settings.mouse_orbit_button) { return; } - - let x_axis = gamepad.right_stick().x; - let y_axis = gamepad.right_stick().y; + let (x, y) = (gamepad.right_stick().x, gamepad.right_stick().y); let deadzone = 0.5; let mut rotation = Vec2::ZERO; - let (x, y) = (x_axis, y_axis); if x.abs() > deadzone || y.abs() > deadzone { rotation = Vec2::new(x, y); } if rotation.length_squared() > 0.0 { let window = window_q.single().unwrap(); - let delta_x = { - let delta = rotation.x / window.width() - * std::f32::consts::PI - * 2.0 - * cam.gamepad_settings.sensitivity.x; - delta - }; + let delta_x = rotation.x / window.width() * PI * 2.0 * cam.gamepad_settings.sensitivity.x; let delta_y = -rotation.y / window.height() * PI * cam.gamepad_settings.sensitivity.y; + let yaw = Quat::from_rotation_y(-delta_x); let pitch = Quat::from_rotation_x(-delta_y); + cam_transform.rotation = yaw * cam_transform.rotation; // rotate around global y axis let new_rotation = cam_transform.rotation * pitch; diff --git a/src/lib.rs b/src/lib.rs index 34fbf33..1dc869a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ mod gamepad; mod mouse; +use std::f32::consts::PI; + use bevy::{ prelude::*, window::{CursorGrabMode, PrimaryWindow}, @@ -13,9 +15,7 @@ use mouse::MousePlugin; /// ``` /// use bevy::prelude::*; /// use bevy_third_person_camera::ThirdPersonCameraPlugin; -/// fn main() { -/// App::new().add_plugins(ThirdPersonCameraPlugin); -/// } +/// App::new().add_plugins(ThirdPersonCameraPlugin); /// ``` pub struct ThirdPersonCameraPlugin; @@ -70,6 +70,10 @@ pub struct ThirdPersonCamera { /// The smaller the value, the greater the zoom distance. 0.1 would essentially look like 'first person'. /// Default is 0.7 pub aim_zoom: f32, + /// Boundaries for orbiting. + /// Can be used for a floor bound, side change or artistic dramatic effect + /// where you could only move camera in a narrow corridor. + pub bounds: Vec, /// Flag to indicate if the cursor lock toggle functionality is turned on. /// When enabled and the cursor lock is NOT active, the mouse can freely move about the window without the camera's transform changing. /// Example usage: Browsing a character inventory without moving the camera. @@ -110,6 +114,9 @@ pub struct ThirdPersonCamera { /// The speed at which the x offset will transition. /// Default is 5.0 pub offset_toggle_speed: f32, + /// Pitch limit in radians. + /// Defaults to just under 90 degrees (PI/2 - 0.05) = 89.5 + pub pitch_limit: f32, /// Flag to indicate whether a camera zoom is applied or not. /// Default is true pub zoom_enabled: bool, @@ -131,6 +138,7 @@ impl Default for ThirdPersonCamera { aim_button: MouseButton::Right, aim_speed: 3.0, aim_zoom: 0.7, + bounds: vec![Bound::NO_FLIP], cursor_lock_key: KeyCode::Space, cursor_lock_toggle_enabled: true, gamepad_settings: CustomGamepadSettings::default(), @@ -143,6 +151,7 @@ impl Default for ThirdPersonCamera { offset_toggle_enabled: false, offset_toggle_speed: 5.0, offset_toggle_key: KeyCode::KeyE, + pitch_limit: PI / 2.0 - 0.05, zoom_enabled: true, zoom: Zoom::new(1.5, 3.0), zoom_sensitivity: 1.0, @@ -150,6 +159,54 @@ impl Default for ThirdPersonCamera { } } +impl ThirdPersonCamera { + pub fn with_custom_settings(mut self, gamepad_settings: CustomGamepadSettings) -> Self { + self.gamepad_settings = gamepad_settings; + self + } +} + +/// Bounds to restrict camera movement +/// +/// Example: +/// ```rust,no_run +/// +/// let bounds = vec![ +/// // Ground plane: don't go below y = 0 +/// Bound { +/// normal: Vec3::Y, +/// point: Vec3::ZERO, +/// }, +/// // Left wall: x ≥ -5.0 +/// Bound { +/// normal: Vec3::X, +/// point: Vec3::new(-5.0, 0.0, 0.0), +/// }, +/// // Right wall: x ≤ 5.0 +/// Bound { +/// normal: -Vec3::X, +/// point: Vec3::new(5.0, 0.0, 0.0), +/// }, +/// ], +/// ``` +pub struct Bound { + pub normal: Vec3, + pub point: Vec3, +} + +impl Bound { + // Ensures camera doesn't flip downside + pub const NO_FLIP: Bound = Bound { + normal: Vec3::NEG_Y, // pointing downward + point: Vec3::ZERO, // since this is an orientation-only check, point can be origin + }; + // Ensures camera doesn't go lower than floor + pub const ABOVE_FLOOR: Bound = Bound { + normal: Vec3::Y, // pointing downward + point: Vec3::ZERO, // since this is an orientation-only check, point can be origin + }; +} + /// Sets the zoom bounds (min & max) pub struct Zoom { pub min: f32, @@ -195,19 +252,9 @@ impl Offset { /// use bevy::prelude::*; /// use bevy_third_person_camera::{CustomGamepadSettings, ThirdPersonCamera}; /// fn spawn_camera(mut commands: Commands) { -/// let gamepad = Gamepad::new(0); +/// let settings = CustomGamepadSettings::default() /// commands.spawn(( -/// ThirdPersonCamera { -/// gamepad_settings: CustomGamepadSettings { -/// aim_button: GamepadButton::new(gamepad, GamepadButtonType::LeftTrigger2), -/// mouse_orbit_button: GamepadButton::new(gamepad, GamepadButtonType::LeftTrigger), -/// offset_toggle_button: GamepadButton::new(gamepad, GamepadButtonType::DPadRight), -/// sensitivity: Vec2::new(7.0, 4.0), -/// zoom_in_button: GamepadButton::new(gamepad, GamepadButtonType::DPadUp), -/// zoom_out_button: GamepadButton::new(gamepad, GamepadButtonType::DPadDown), -/// }, -/// ..default() -/// }, +/// ThirdPersonCamera::default().with_custom_settings(settings) /// Camera3dBundle::default(), /// )); /// } @@ -299,6 +346,7 @@ fn aim_condition(cam_q: Query<&ThirdPersonCamera, With>) -> b cam.aim_enabled } +#[allow(clippy::type_complexity)] fn aim( mut cam_q: Query< (&mut ThirdPersonCamera, &Transform), @@ -317,17 +365,11 @@ fn aim( return; }; - let gamepad = match gamepad_q.single() { - Ok(value) => Some(value), - Err(_) => None, - }; - - let is_gamepad_aiming = match gamepad { - Some(gp) => gp.pressed(cam.gamepad_settings.aim_button), - None => false, - }; - // check if aim button was pressed + let is_gamepad_aiming = gamepad_q + .single() + .map(|gp| gp.pressed(cam.gamepad_settings.aim_button)) + .unwrap_or_default(); let is_mouse_aiming = mouse.pressed(cam.aim_button); if is_mouse_aiming || is_gamepad_aiming { @@ -351,30 +393,28 @@ fn aim( } else { cam.zoom.radius -= zoom_factor; } - } else { - if let Some(radius_copy) = cam.zoom.radius_copy { - let zoom_factor = (radius_copy / cam.aim_zoom) * cam.aim_speed * time.delta_secs(); - - // stop zooming out if current radius is greater than original radius - if cam.zoom.radius >= radius_copy || cam.zoom.radius + zoom_factor >= radius_copy { - cam.zoom.radius = radius_copy; - cam.zoom.radius_copy = None; - } else { - cam.zoom.radius += (radius_copy / cam.aim_zoom) * cam.aim_speed * time.delta_secs(); - } + } else if let Some(radius_copy) = cam.zoom.radius_copy { + let zoom_factor = (radius_copy / cam.aim_zoom) * cam.aim_speed * time.delta_secs(); + + // stop zooming out if current radius is greater than original radius + if cam.zoom.radius >= radius_copy || cam.zoom.radius + zoom_factor >= radius_copy { + cam.zoom.radius = radius_copy; + cam.zoom.radius_copy = None; + } else { + cam.zoom.radius += (radius_copy / cam.aim_zoom) * cam.aim_speed * time.delta_secs(); } } } -pub fn zoom_condition(cam_q: Query<&ThirdPersonCamera, With>) -> bool { +pub fn zoom_condition(cam_q: Query<&ThirdPersonCamera>) -> bool { let Ok(cam) = cam_q.single() else { return false; }; - return cam.zoom_enabled && cam.cursor_lock_active; + cam.zoom_enabled && cam.cursor_lock_active } // only run toggle_x_offset if `offset_toggle_enabled` is true -fn toggle_x_offset_condition(cam_q: Query<&ThirdPersonCamera, With>) -> bool { +fn toggle_x_offset_condition(cam_q: Query<&ThirdPersonCamera>) -> bool { let Ok(cam) = cam_q.single() else { return false; }; @@ -383,7 +423,7 @@ fn toggle_x_offset_condition(cam_q: Query<&ThirdPersonCamera, With right shoulder view & vice versa fn toggle_x_offset( - mut cam_q: Query<&mut ThirdPersonCamera, With>, + mut cam_q: Query<&mut ThirdPersonCamera>, keys: Res>, time: Res