use bevy::prelude::*; use bevy::input::mouse::MouseMotion; use bevy::window::PrimaryWindow; use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings}; use bevy::core_pipeline::tonemapping::Tonemapping; use bevy_xpbd_3d::prelude::*; use std::f32::consts::*; use crate::{settings, audio, actor}; pub struct CameraControllerPlugin; impl Plugin for CameraControllerPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, setup_camera); app.add_systems(Update, handle_input); app.add_systems(Update, manage_player_actor); app.add_systems(Update, sync_camera_to_player); app.add_systems(Update, run_camera_controller); } } // Based on Valorant's default sensitivity, not entirely sure why it is exactly 1.0 / 180.0, // but I'm guessing it is a misunderstanding between degrees/radians and then sticking with // it because it felt nice. pub const RADIANS_PER_DOT: f32 = 1.0 / 180.0; #[derive(Component)] pub struct CameraController { pub enabled: bool, pub initialized: bool, pub pitch: f32, pub yaw: f32, } impl Default for CameraController { fn default() -> Self { Self { enabled: true, initialized: false, pitch: 1.0, // pitch=0/yaw=0 -> face sun yaw: 0.3, } } } fn setup_camera( mut commands: Commands, ) { // Add player commands.spawn(( Camera3dBundle { camera: Camera { hdr: true, // HDR is required for bloom ..default() }, tonemapping: Tonemapping::TonyMcMapface, transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }, CameraController::default(), BloomSettings { composite_mode: BloomCompositeMode::EnergyConserving, ..default() }, )); } pub fn sync_camera_to_player( settings: Res, mut q_camera: Query<&mut Transform, With>, q_playercam: Query<(&actor::Actor, &Transform), (With, Without, Without)>, ) { if q_camera.is_empty() || q_playercam.is_empty() { return; } let mut camera_transform = q_camera.get_single_mut().unwrap(); let (actor, player_transform) = q_playercam.get_single().unwrap(); // Rotation camera_transform.rotation = player_transform.rotation * Quat::from_array([0.0, -1.0, 0.0, 0.0]); // Translation if settings.third_person { camera_transform.translation = player_transform.translation + camera_transform.rotation * (actor.camdistance * Vec3::new(0.0, 0.2, 1.0)); } else { camera_transform.translation = player_transform.translation; } } pub fn handle_input( keyboard_input: Res>, mut settings: ResMut, ) { if keyboard_input.just_pressed(settings.key_camera) { settings.third_person ^= true; } } fn manage_player_actor( settings: Res, mut q_playercam: Query<&mut Visibility, With>, mut q_hiddenplayer: Query<&mut Visibility, (With, Without)>, ) { for mut vis in &mut q_playercam { if settings.third_person { *vis = Visibility::Inherited; } else { *vis = Visibility::Hidden; } } for mut vis in &mut q_hiddenplayer { *vis = Visibility::Hidden; } } #[allow(clippy::too_many_arguments)] fn run_camera_controller( time: Res